This commit is contained in:
callebtc 2022-10-14 01:01:43 +02:00 committed by dni ⚡
parent 5b1ee554df
commit 53db275fe0
7 changed files with 141 additions and 617 deletions

View File

@ -11,13 +11,16 @@ db = Database("ext_cashu")
import sys
cashu_static_files = [
{
"path": "/cashu/static",
"app": StaticFiles(directory="lnbits/extensions/cashu/static"),
"name": "cashu_static",
}
]
sys.path.append("/Users/cc/git/cashu")
from cashu.mint.ledger import Ledger
# from .crud import LedgerCrud
# db = Database("ext_cashu", LNBITS_DATA_FOLDER)
ledger = Ledger(
db=db,
# seed=MINT_PRIVATE_KEY,
@ -26,17 +29,6 @@ ledger = Ledger(
)
cashu_ext: APIRouter = APIRouter(prefix="/api/v1/cashu", tags=["cashu"])
# from cashu.mint.router import router as cashu_router
# cashu_ext.include_router(router=cashu_router)
cashu_static_files = [
{
"path": "/cashu/static",
"app": StaticFiles(directory="lnbits/extensions/cashu/static"),
"name": "cashu_static",
}
]
def cashu_renderer():

View File

@ -19,46 +19,6 @@ from cashu.core.base import MintKeyset
from lnbits.db import Database, Connection
# class LedgerCrud:
# """
# Database interface for Cashu mint.
# This class needs to be overloaded by any app that imports the Cashu mint.
# """
# async def get_keyset(*args, **kwags):
# return await get_keyset(*args, **kwags)
# async def get_lightning_invoice(*args, **kwags):
# return await get_lightning_invoice(*args, **kwags)
# async def get_proofs_used(*args, **kwags):
# return await get_proofs_used(*args, **kwags)
# async def invalidate_proof(*args, **kwags):
# return await invalidate_proof(*args, **kwags)
# async def store_keyset(*args, **kwags):
# return await store_keyset(*args, **kwags)
# async def store_lightning_invoice(*args, **kwags):
# return await store_lightning_invoice(*args, **kwags)
# async def store_promise(*args, **kwags):
# return await store_promise(*args, **kwags)
# async def update_lightning_invoice(*args, **kwags):
# return await update_lightning_invoice(*args, **kwags)
async def create_cashu(
cashu_id: str, keyset_id: str, wallet_id: str, data: Cashu
) -> Cashu:
@ -85,23 +45,23 @@ async def create_cashu(
return cashu
async def update_cashu_keys(cashu_id, wif: str = None) -> Optional[Cashu]:
entropy = bytes([random.getrandbits(8) for i in range(16)])
mnemonic = bip39.mnemonic_from_bytes(entropy)
seed = bip39.mnemonic_to_seed(mnemonic)
root = bip32.HDKey.from_seed(seed, version=NETWORKS["main"]["xprv"])
# async def update_cashu_keys(cashu_id, wif: str = None) -> Optional[Cashu]:
# entropy = bytes([random.getrandbits(8) for i in range(16)])
# mnemonic = bip39.mnemonic_from_bytes(entropy)
# seed = bip39.mnemonic_to_seed(mnemonic)
# root = bip32.HDKey.from_seed(seed, version=NETWORKS["main"]["xprv"])
bip44_xprv = root.derive("m/44h/1h/0h")
bip44_xpub = bip44_xprv.to_public()
# bip44_xprv = root.derive("m/44h/1h/0h")
# bip44_xpub = bip44_xprv.to_public()
await db.execute(
"UPDATE cashu.cashu SET prv = ?, pub = ? WHERE id = ?",
bip44_xprv.to_base58(),
bip44_xpub.to_base58(),
cashu_id,
)
row = await db.fetchone("SELECT * FROM cashu.cashu WHERE id = ?", (cashu_id,))
return Cashu(**row) if row else None
# await db.execute(
# "UPDATE cashu.cashu SET prv = ?, pub = ? WHERE id = ?",
# bip44_xprv.to_base58(),
# bip44_xpub.to_base58(),
# cashu_id,
# )
# row = await db.fetchone("SELECT * FROM cashu.cashu WHERE id = ?", (cashu_id,))
# return Cashu(**row) if row else None
async def get_cashu(cashu_id) -> Optional[Cashu]:
@ -130,103 +90,103 @@ async def delete_cashu(cashu_id) -> None:
# ##########################################
async def store_promises(
amounts: List[int], B_s: List[str], C_s: List[str], cashu_id: str
):
for amount, B_, C_ in zip(amounts, B_s, C_s):
await store_promise(amount, B_, C_, cashu_id)
# async def store_promises(
# amounts: List[int], B_s: List[str], C_s: List[str], cashu_id: str
# ):
# for amount, B_, C_ in zip(amounts, B_s, C_s):
# await store_promise(amount, B_, C_, cashu_id)
async def store_promise(amount: int, B_: str, C_: str, cashu_id: str):
promise_id = urlsafe_short_hash()
# async def store_promise(amount: int, B_: str, C_: str, cashu_id: str):
# promise_id = urlsafe_short_hash()
await db.execute(
"""
INSERT INTO cashu.promises
(id, amount, B_b, C_b, cashu_id)
VALUES (?, ?, ?, ?, ?)
""",
(promise_id, amount, str(B_), str(C_), cashu_id),
)
# await db.execute(
# """
# INSERT INTO cashu.promises
# (id, amount, B_b, C_b, cashu_id)
# VALUES (?, ?, ?, ?, ?)
# """,
# (promise_id, amount, str(B_), str(C_), cashu_id),
# )
async def get_promises(cashu_id) -> Optional[Cashu]:
row = await db.fetchall(
"SELECT * FROM cashu.promises WHERE cashu_id = ?", (cashu_id,)
)
return Promises(**row) if row else None
# async def get_promises(cashu_id) -> Optional[Cashu]:
# row = await db.fetchall(
# "SELECT * FROM cashu.promises WHERE cashu_id = ?", (cashu_id,)
# )
# return Promises(**row) if row else None
async def get_proofs_used(
db: Database,
conn: Optional[Connection] = None,
):
# async def get_proofs_used(
# db: Database,
# conn: Optional[Connection] = None,
# ):
rows = await (conn or db).fetchall(
"""
SELECT secret from cashu.proofs_used
"""
)
return [row[0] for row in rows]
# rows = await (conn or db).fetchall(
# """
# SELECT secret from cashu.proofs_used
# """
# )
# return [row[0] for row in rows]
async def invalidate_proof(cashu_id: str, proof: Proof):
invalidate_proof_id = urlsafe_short_hash()
await db.execute(
"""
INSERT INTO cashu.proofs_used
(id, amount, C, secret, cashu_id)
VALUES (?, ?, ?, ?, ?)
""",
(invalidate_proof_id, proof.amount, str(proof.C), str(proof.secret), cashu_id),
)
# async def invalidate_proof(cashu_id: str, proof: Proof):
# invalidate_proof_id = urlsafe_short_hash()
# await db.execute(
# """
# INSERT INTO cashu.proofs_used
# (id, amount, C, secret, cashu_id)
# VALUES (?, ?, ?, ?, ?)
# """,
# (invalidate_proof_id, proof.amount, str(proof.C), str(proof.secret), cashu_id),
# )
########################################
############ MINT INVOICES #############
########################################
# ########################################
# ############ MINT INVOICES #############
# ########################################
async def store_lightning_invoice(cashu_id: str, invoice: Invoice):
await db.execute(
"""
INSERT INTO cashu.invoices
(cashu_id, amount, pr, hash, issued)
VALUES (?, ?, ?, ?, ?)
""",
(
cashu_id,
invoice.amount,
invoice.pr,
invoice.hash,
invoice.issued,
),
)
# async def store_lightning_invoice(cashu_id: str, invoice: Invoice):
# await db.execute(
# """
# INSERT INTO cashu.invoices
# (cashu_id, amount, pr, hash, issued)
# VALUES (?, ?, ?, ?, ?)
# """,
# (
# cashu_id,
# invoice.amount,
# invoice.pr,
# invoice.hash,
# invoice.issued,
# ),
# )
async def get_lightning_invoice(cashu_id: str, hash: str):
row = await db.fetchone(
"""
SELECT * from cashu.invoices
WHERE cashu_id =? AND hash = ?
""",
(
cashu_id,
hash,
),
)
return Invoice.from_row(row)
# async def get_lightning_invoice(cashu_id: str, hash: str):
# row = await db.fetchone(
# """
# SELECT * from cashu.invoices
# WHERE cashu_id =? AND hash = ?
# """,
# (
# cashu_id,
# hash,
# ),
# )
# return Invoice.from_row(row)
async def update_lightning_invoice(cashu_id: str, hash: str, issued: bool):
await db.execute(
"UPDATE cashu.invoices SET issued = ? WHERE cashu_id = ? AND hash = ?",
(
issued,
cashu_id,
hash,
),
)
# async def update_lightning_invoice(cashu_id: str, hash: str, issued: bool):
# await db.execute(
# "UPDATE cashu.invoices SET issued = ? WHERE cashu_id = ? AND hash = ?",
# (
# issued,
# cashu_id,
# hash,
# ),
# )
##############################

View File

@ -30,118 +30,3 @@ async def m001_initial(db):
);
"""
)
# async def m001_initial(db):
# await db.execute(
# """
# CREATE TABLE IF NOT EXISTS cashu.promises (
# amount INTEGER NOT NULL,
# B_b TEXT NOT NULL,
# C_b TEXT NOT NULL,
# UNIQUE (B_b)
# );
# """
# )
# await db.execute(
# """
# CREATE TABLE IF NOT EXISTS cashu.proofs_used (
# amount INTEGER NOT NULL,
# C TEXT NOT NULL,
# secret TEXT NOT NULL,
# UNIQUE (secret)
# );
# """
# )
# await db.execute(
# """
# CREATE TABLE IF NOT EXISTS cashu.invoices (
# amount INTEGER NOT NULL,
# pr TEXT NOT NULL,
# hash TEXT NOT NULL,
# issued BOOL NOT NULL,
# UNIQUE (hash)
# );
# """
# )
# await db.execute(
# """
# CREATE VIEW IF NOT EXISTS cashu.balance_issued AS
# SELECT COALESCE(SUM(s), 0) AS balance FROM (
# SELECT SUM(amount) AS s
# FROM cashu.promises
# WHERE amount > 0
# );
# """
# )
# await db.execute(
# """
# CREATE VIEW IF NOT EXISTS cashu.balance_used AS
# SELECT COALESCE(SUM(s), 0) AS balance FROM (
# SELECT SUM(amount) AS s
# FROM cashu.proofs_used
# WHERE amount > 0
# );
# """
# )
# await db.execute(
# """
# CREATE VIEW IF NOT EXISTS cashu.balance AS
# SELECT s_issued - s_used AS balance FROM (
# SELECT bi.balance AS s_issued, bu.balance AS s_used
# FROM cashu.balance_issued bi
# CROSS JOIN balance_used bu
# );
# """
# )
# async def m003_mint_keysets(db):
# """
# Stores mint keysets from different mints and epochs.
# """
# await db.execute(
# f"""
# CREATE TABLE IF NOT EXISTS cashu.keysets (
# id TEXT NOT NULL,
# derivation_path TEXT,
# valid_from TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
# valid_to TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
# first_seen TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
# active BOOL DEFAULT TRUE,
# UNIQUE (derivation_path)
# );
# """
# )
# await db.execute(
# f"""
# CREATE TABLE IF NOT EXISTS cashu.mint_pubkeys (
# id TEXT NOT NULL,
# amount INTEGER NOT NULL,
# pubkey TEXT NOT NULL,
# UNIQUE (id, pubkey)
# );
# """
# )
# async def m004_keysets_add_version(db):
# """
# Column that remembers with which version
# """
# await db.execute("ALTER TABLE cashu.keysets ADD COLUMN version TEXT")

View File

@ -1,155 +0,0 @@
import math
from typing import List, Set
from lnbits import bolt11
from lnbits.core.services import check_transaction_status, fee_reserve, pay_invoice
from lnbits.wallets.base import PaymentStatus
from .core.b_dhke import step2_bob
from .core.base import BlindedMessage, BlindedSignature, Proof
from .core.secp import PublicKey
from .core.split import amount_split
from .crud import get_proofs_used, invalidate_proof
from .mint_helper import (
derive_keys,
derive_pubkeys,
verify_equation_balanced,
verify_no_duplicates,
verify_outputs,
verify_proof,
verify_secret_criteria,
verify_split_amount,
)
from .models import Cashu
# todo: extract const
MAX_ORDER = 64
def get_pubkeys(xpriv: str):
"""Returns public keys for possible amounts."""
keys = derive_keys(xpriv)
pub_keys = derive_pubkeys(keys)
return {a: p.serialize().hex() for a, p in pub_keys.items()}
async def generate_promises(
master_prvkey: str, amounts: List[int], B_s: List[PublicKey]
):
"""Mints a promise for coins for B_."""
for amount in amounts:
if amount not in [2**i for i in range(MAX_ORDER)]:
raise Exception(f"Can only mint amounts up to {2**MAX_ORDER}.")
promises = [
await generate_promise(master_prvkey, amount, B_)
for B_, amount in zip(B_s, amounts)
]
return promises
async def generate_promise(master_prvkey: str, amount: int, B_: PublicKey):
"""Generates a promise for given amount and returns a pair (amount, C')."""
secret_key = derive_keys(master_prvkey)[amount] # Get the correct key
C_ = step2_bob(B_, secret_key)
return BlindedSignature(amount=amount, C_=C_.serialize().hex())
async def melt(cashu: Cashu, proofs: List[Proof], invoice: str):
"""Invalidates proofs and pays a Lightning invoice."""
# Verify proofs
proofs_used: Set[str] = set(await get_proofs_used(cashu.id))
for p in proofs:
await verify_proof(cashu.prvkey, proofs_used, p)
total_provided = sum([p["amount"] for p in proofs])
invoice_obj = bolt11.decode(invoice)
amount = math.ceil(invoice_obj.amount_msat / 1000)
fees_msat = await check_fees(cashu.wallet, invoice_obj)
assert total_provided >= amount + fees_msat / 1000, Exception(
f"Provided proofs ({total_provided} sats) not enough for Lightning payment ({amount + fees_msat} sats)."
)
await pay_invoice(
wallet_id=cashu.wallet,
payment_request=invoice,
description=f"pay cashu invoice",
extra={"tag": "cashu", "cahsu_name": cashu.name},
)
status: PaymentStatus = await check_transaction_status(
cashu.wallet, invoice_obj.payment_hash
)
if status.paid == True:
await invalidate_proofs(cashu.id, proofs)
return status.paid, status.preimage
return False, ""
async def check_fees(wallet_id: str, decoded_invoice):
"""Returns the fees (in msat) required to pay this pr."""
amount = math.ceil(decoded_invoice.amount_msat / 1000)
status: PaymentStatus = await check_transaction_status(
wallet_id, decoded_invoice.payment_hash
)
fees_msat = fee_reserve(amount * 1000) if status.paid != True else 0
return fees_msat
async def split(
cashu: Cashu, proofs: List[Proof], amount: int, outputs: List[BlindedMessage]
):
"""Consumes proofs and prepares new promises based on the amount split."""
total = sum([p.amount for p in proofs])
# verify that amount is kosher
verify_split_amount(amount)
# verify overspending attempt
if amount > total:
raise Exception(
f"split amount ({amount}) is higher than the total sum ({total})."
)
# Verify secret criteria
if not all([verify_secret_criteria(p) for p in proofs]):
raise Exception("secrets do not match criteria.")
# verify that only unique proofs and outputs were used
if not verify_no_duplicates(proofs, outputs):
raise Exception("duplicate proofs or promises.")
# verify that outputs have the correct amount
if not verify_outputs(total, amount, outputs): # ?
raise Exception("split of promises is not as expected.")
# Verify proofs
# Verify proofs
proofs_used: Set[str] = set(await get_proofs_used(cashu.id))
for p in proofs:
await verify_proof(cashu.prvkey, proofs_used, p)
# Mark proofs as used and prepare new promises
await invalidate_proofs(cashu.id, proofs)
outs_fst = amount_split(total - amount)
outs_snd = amount_split(amount)
B_fst = [
PublicKey(bytes.fromhex(od.B_), raw=True) for od in outputs[: len(outs_fst)]
]
B_snd = [
PublicKey(bytes.fromhex(od.B_), raw=True) for od in outputs[len(outs_fst) :]
]
# PublicKey(bytes.fromhex(payload.B_), raw=True)
prom_fst, prom_snd = await generate_promises(
cashu.prvkey, outs_fst, B_fst
), await generate_promises(cashu.prvkey, outs_snd, B_snd)
# verify amounts in produced proofs
verify_equation_balanced(proofs, prom_fst + prom_snd)
return prom_fst, prom_snd
async def invalidate_proofs(cashu_id: str, proofs: List[Proof]):
"""Adds secrets of proofs to the list of knwon secrets and stores them in the db."""
for p in proofs:
await invalidate_proof(cashu_id, p)

View File

@ -1,97 +0,0 @@
import base64
import hashlib
from typing import List, Set
from .core.b_dhke import verify
from .core.base import BlindedSignature
from .core.secp import PrivateKey, PublicKey
from .core.split import amount_split
from .models import BlindedMessage, Proof
# todo: extract const
MAX_ORDER = 64
def derive_keys(master_key: str):
"""Deterministic derivation of keys for 2^n values."""
return {
2
** i: PrivateKey(
hashlib.sha256((str(master_key) + str(i)).encode("utf-8"))
.hexdigest()
.encode("utf-8")[:32],
raw=True,
)
for i in range(MAX_ORDER)
}
def derive_pubkeys(keys: List[PrivateKey]):
return {amt: keys[amt].pubkey for amt in [2**i for i in range(MAX_ORDER)]}
# async required?
async def verify_proof(master_prvkey: str, proofs_used: Set[str], proof: Proof):
"""Verifies that the proof of promise was issued by this ledger."""
if proof.secret in proofs_used:
raise Exception(f"tokens already spent. Secret: {proof.secret}")
secret_key = derive_keys(master_prvkey)[
proof.amount
] # Get the correct key to check against
C = PublicKey(bytes.fromhex(proof.C), raw=True)
secret = base64.standard_b64decode(proof.secret)
print("### secret", secret)
validMintSig = verify(secret_key, C, secret)
if validMintSig != True:
raise Exception(f"tokens not valid. Secret: {proof.secret}")
def verify_split_amount(amount: int):
"""Split amount like output amount can't be negative or too big."""
try:
verify_amount(amount)
except:
# For better error message
raise Exception("invalid split amount: " + str(amount))
def verify_secret_criteria(proof: Proof):
if proof.secret is None or proof.secret == "":
raise Exception("no secret in proof.")
return True
def verify_no_duplicates(proofs: List[Proof], outputs: List[BlindedMessage]):
secrets = [p.secret for p in proofs]
if len(secrets) != len(list(set(secrets))):
return False
B_s = [od.B_ for od in outputs]
if len(B_s) != len(list(set(B_s))):
return False
return True
def verify_outputs(total: int, amount: int, outputs: List[BlindedMessage]):
"""Verifies the expected split was correctly computed"""
frst_amt, scnd_amt = total - amount, amount # we have two amounts to split to
frst_outputs = amount_split(frst_amt)
scnd_outputs = amount_split(scnd_amt)
expected = frst_outputs + scnd_outputs
given = [o.amount for o in outputs]
return given == expected
def verify_amount(amount: int):
"""Any amount used should be a positive integer not larger than 2^MAX_ORDER."""
valid = isinstance(amount, int) and amount > 0 and amount < 2**MAX_ORDER
if not valid:
raise Exception("invalid amount: " + str(amount))
return amount
def verify_equation_balanced(proofs: List[Proof], outs: List[BlindedSignature]):
"""Verify that Σoutputs - Σinputs = 0."""
sum_inputs = sum(verify_amount(p.amount) for p in proofs)
sum_outputs = sum(verify_amount(p.amount) for p in outs)
assert sum_outputs - sum_inputs == 0

View File

@ -1,17 +1,11 @@
import asyncio
import json
from lnbits.core import db as core_db
from lnbits.core.crud import create_payment
from lnbits.core.models import Payment
from lnbits.helpers import urlsafe_short_hash
from lnbits.tasks import internal_invoice_queue, register_invoice_listener
from lnbits.tasks import register_invoice_listener
from .crud import get_cashu
import sys
sys.path.append("/Users/cc/git/cashu")
from cashu.mint import migrations
from cashu.core.migrations import migrate_databases
from . import db, ledger
@ -35,51 +29,6 @@ async def wait_for_paid_invoices():
async def on_invoice_paid(payment: Payment) -> None:
if payment.extra.get("tag") == "cashu" and payment.extra.get("tipSplitted"):
# already splitted, ignore
if not payment.extra.get("tag") == "cashu":
return
# now we make some special internal transfers (from no one to the receiver)
cashu = await get_cashu(payment.extra.get("cashuId"))
tipAmount = payment.extra.get("tipAmount")
if tipAmount is None:
# no tip amount
return
tipAmount = tipAmount * 1000
amount = payment.amount - tipAmount
# mark the original payment with one extra key, "splitted"
# (this prevents us from doing this process again and it's informative)
# and reduce it by the amount we're going to send to the producer
await core_db.execute(
"""
UPDATE apipayments
SET extra = ?, amount = ?
WHERE hash = ?
AND checking_id NOT LIKE 'internal_%'
""",
(
json.dumps(dict(**payment.extra, tipSplitted=True)),
amount,
payment.payment_hash,
),
)
# perform the internal transfer using the same payment_hash
internal_checking_id = f"internal_{urlsafe_short_hash()}"
await create_payment(
wallet_id=cashu.tip_wallet,
checking_id=internal_checking_id,
payment_request="",
payment_hash=payment.payment_hash,
amount=tipAmount,
memo=f"Tip for {payment.memo}",
pending=False,
extra={"tipSplitted": True},
)
# manually send this for now
await internal_invoice_queue.put(internal_checking_id)
return

View File

@ -2,6 +2,7 @@ import json
from http import HTTPStatus
from typing import Union
import math
from typing import Dict, List, Union
import httpx
from fastapi import Query
@ -25,38 +26,22 @@ from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
from lnbits.wallets.base import PaymentStatus
from lnbits.helpers import urlsafe_short_hash
from lnbits.core.crud import check_internal
# --------- extension imports
from . import cashu_ext
from .core.base import CashuError, PostSplitResponse, SplitRequest
from .crud import (
create_cashu,
delete_cashu,
get_cashu,
get_cashus,
get_lightning_invoice,
store_lightning_invoice,
store_promise,
update_lightning_invoice,
)
# from .ledger import mint, request_mint
from .mint import generate_promises, get_pubkeys, melt, split
from .models import (
Cashu,
CheckPayload,
Invoice,
MeltPayload,
MintPayloads,
PayLnurlWData,
Pegs,
SplitPayload,
)
from .models import Cashu
from . import ledger
############### IMPORT CALLE
from typing import Dict, List, Union
from secp256k1 import PublicKey
# -------- cashu imports
from cashu.core.base import (
Proof,
BlindedSignature,
@ -69,9 +54,8 @@ from cashu.core.base import (
MintRequest,
PostSplitResponse,
SplitRequest,
Invoice,
)
from cashu.core.errors import CashuError
from . import db, ledger
LIGHTNING = False
@ -165,7 +149,7 @@ async def mint_coins(
data: MintRequest,
cashu_id: str = Query(None),
payment_hash: str = Query(None),
):
) -> List[BlindedSignature]:
"""
Requests the minting of tokens belonging to a paid payment request.
Call this endpoint after `GET /mint`.
@ -220,7 +204,9 @@ async def mint_coins(
@cashu_ext.post("/{cashu_id}/melt")
async def melt_coins(payload: MeltRequest, cashu_id: str = Query(None)):
async def melt_coins(
payload: MeltRequest, cashu_id: str = Query(None)
) -> GetMeltResponse:
"""Invalidates proofs and pays a Lightning invoice."""
cashu: Union[None, Cashu] = await get_cashu(cashu_id)
if cashu is None:
@ -229,8 +215,6 @@ async def melt_coins(payload: MeltRequest, cashu_id: str = Query(None)):
)
proofs = payload.proofs
invoice = payload.invoice
# async def melt(cashu: Cashu, proofs: List[Proof], invoice: str):
# """Invalidates proofs and pays a Lightning invoice."""
# !!!!!!! MAKE SURE THAT PROOFS ARE ONLY FROM THIS CASHU KEYSET ID
# THIS IS NECESSARY BECAUSE THE CASHU BACKEND WILL ACCEPT ANY VALID
@ -271,18 +255,7 @@ async def melt_coins(payload: MeltRequest, cashu_id: str = Query(None)):
)
if status.paid == True:
await ledger._invalidate_proofs(proofs)
return status.paid, status.preimage
return False, ""
@cashu_ext.post("/melt")
async def melt(payload: MeltRequest) -> GetMeltResponse:
"""
Requests tokens to be destroyed and sent out via Lightning.
"""
ok, preimage = await ledger.melt(payload.proofs, payload.invoice)
resp = GetMeltResponse(paid=ok, preimage=preimage)
return resp
return GetMeltResponse(paid=status.paid, preimage=status.preimage)
@cashu_ext.post("/check")
@ -298,29 +271,46 @@ async def check_fees(payload: CheckFeesRequest) -> CheckFeesResponse:
Used by wallets for figuring out the fees they need to supply.
This is can be useful for checking whether an invoice is internal (Cashu-to-Cashu).
"""
fees_msat = await ledger.check_fees(payload.pr)
invoice_obj = bolt11.decode(payload.pr)
internal_checking_id = await check_internal(invoice_obj.payment_hash)
if not internal_checking_id:
fees_msat = fee_reserve(invoice_obj.amount_msat)
else:
fees_msat = 0
return CheckFeesResponse(fee=fees_msat / 1000)
@cashu_ext.post("/split")
@cashu_ext.post("/{cashu_id}/split")
async def split(
payload: SplitRequest,
) -> Union[CashuError, PostSplitResponse]:
payload: SplitRequest, cashu_id: str = Query(None)
) -> PostSplitResponse:
"""
Requetst a set of tokens with amount "total" to be split into two
newly minted sets with amount "split" and "total-split".
"""
cashu: Union[None, Cashu] = await get_cashu(cashu_id)
if cashu is None:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
)
proofs = payload.proofs
amount = payload.amount
outputs = payload.outputs.blinded_messages if payload.outputs else None
outputs = payload.outputs.blinded_messages
# backwards compatibility with clients < v0.2.2
assert outputs, Exception("no outputs provided.")
try:
split_return = await ledger.split(proofs, amount, outputs)
split_return = await ledger.split(proofs, amount, outputs, cashu.keyset_id)
except Exception as exc:
return CashuError(error=str(exc))
HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(exc),
)
if not split_return:
return CashuError(error="there was an error with the split")
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail="there was an error with the split",
)
frst_promises, scnd_promises = split_return
resp = PostSplitResponse(fst=frst_promises, snd=scnd_promises)
return resp