Skip to content

Commit

Permalink
NUT-04/05: add amount, unit, request to melt and mint quote res…
Browse files Browse the repository at this point in the history
…ponses (#719)

* add amount, unit, request to melt and mint responses

* make new fields optional to not break compat with old mints

* make new flags optional for backwards compat
  • Loading branch information
callebtc authored Mar 8, 2025
1 parent 3ab1e1d commit bae4855
Show file tree
Hide file tree
Showing 6 changed files with 41 additions and 2 deletions.
6 changes: 6 additions & 0 deletions cashu/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ class PostMintQuoteRequest(BaseModel):
class PostMintQuoteResponse(BaseModel):
quote: str # quote id
request: str # input payment request
amount: Optional[int] # output amount (optional for backwards compat pre 0.16.6)
unit: Optional[str] # output unit (optional for backwards compat pre 0.16.6)
state: Optional[str] # state of the quote (optional for backwards compat)
expiry: Optional[int] # expiry of the quote
pubkey: Optional[str] = None # NUT-20 quote lock pubkey
Expand Down Expand Up @@ -222,6 +224,10 @@ def mpp_amount(self) -> int:
class PostMeltQuoteResponse(BaseModel):
quote: str # quote id
amount: int # input amount
unit: Optional[str] # input unit (optional for backwards compat pre 0.16.6)
request: Optional[
str
] # output payment request (optional for backwards compat pre 0.16.6)
fee_reserve: int # input fee reserve
paid: Optional[bool] = (
None # whether the request has been paid # DEPRECATED as per NUT PR #136
Expand Down
7 changes: 6 additions & 1 deletion cashu/mint/ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,10 @@ def validate_payment_quote(
if not payment_quote.checking_id:
raise Exception("quote has no checking id")
# verify that payment quote amount is as expected
if melt_quote.is_mpp and melt_quote.mpp_amount != payment_quote.amount.to(Unit.msat).amount:
if (
melt_quote.is_mpp
and melt_quote.mpp_amount != payment_quote.amount.to(Unit.msat).amount
):
raise TransactionError("quote amount not as requested")
# make sure the backend returned the amount with a correct unit
if not payment_quote.amount.unit == unit:
Expand Down Expand Up @@ -759,6 +762,8 @@ async def melt_quote(
return PostMeltQuoteResponse(
quote=quote.quote,
amount=quote.amount,
unit=quote.unit,
request=quote.request,
fee_reserve=quote.fee_reserve,
paid=quote.paid, # deprecated
state=quote.state.value,
Expand Down
8 changes: 7 additions & 1 deletion cashu/mint/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,10 @@ async def mint_quote(
logger.trace(f"> POST /v1/mint/quote/bolt11: payload={payload}")
quote = await ledger.mint_quote(payload)
resp = PostMintQuoteResponse(
request=quote.request,
quote=quote.quote,
request=quote.request,
amount=quote.amount,
unit=quote.unit,
paid=quote.paid, # deprecated
state=quote.state.value,
expiry=quote.expiry,
Expand All @@ -190,6 +192,8 @@ async def get_mint_quote(request: Request, quote: str) -> PostMintQuoteResponse:
resp = PostMintQuoteResponse(
quote=mint_quote.quote,
request=mint_quote.request,
amount=mint_quote.amount,
unit=mint_quote.unit,
paid=mint_quote.paid, # deprecated
state=mint_quote.state.value,
expiry=mint_quote.expiry,
Expand Down Expand Up @@ -290,6 +294,8 @@ async def get_melt_quote(request: Request, quote: str) -> PostMeltQuoteResponse:
resp = PostMeltQuoteResponse(
quote=melt_quote.quote,
amount=melt_quote.amount,
unit=melt_quote.unit,
request=melt_quote.request,
fee_reserve=melt_quote.fee_reserve,
paid=melt_quote.paid,
state=melt_quote.state.value,
Expand Down
4 changes: 4 additions & 0 deletions cashu/wallet/v1_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,8 @@ async def melt_quote(
return PostMeltQuoteResponse(
quote=quote_id,
amount=amount_sat,
unit=unit.name,
request=payment_request,
fee_reserve=ret.fee or 0,
paid=False,
state=MeltQuoteState.unpaid.value,
Expand Down Expand Up @@ -550,6 +552,8 @@ def _meltrequest_include_fields(
return PostMeltQuoteResponse(
quote=quote,
amount=0,
unit="sat",
request="lnbc0",
fee_reserve=0,
paid=ret.paid or False,
state=(
Expand Down
3 changes: 3 additions & 0 deletions cashu/wallet/wallet_deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,9 +257,12 @@ async def request_mint_deprecated(self, amount) -> PostMintQuoteResponse:
return_dict = resp.json()
mint_response = GetMintResponse_deprecated.parse_obj(return_dict)
decoded_invoice = bolt11.decode(mint_response.pr)
assert decoded_invoice.amount_msat, Exception("no amount in invoice")
return PostMintQuoteResponse(
quote=mint_response.hash,
request=mint_response.pr,
amount=decoded_invoice.amount_msat // 1000,
unit="sat",
paid=False,
state=MintQuoteState.unpaid.value,
expiry=decoded_invoice.date + (decoded_invoice.expiry or 0),
Expand Down
15 changes: 15 additions & 0 deletions tests/test_mint_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,9 @@ async def test_mint_quote(ledger: Ledger):
resp_quote = PostMintQuoteResponse(**result)
assert resp_quote.quote == result["quote"]
assert resp_quote.state == MintQuoteState.unpaid.value
assert resp_quote.amount == 100
assert resp_quote.unit == "sat"
assert resp_quote.request == result["request"]

# check if DEPRECATED paid flag is also returned
assert result["paid"] is False
Expand Down Expand Up @@ -230,6 +233,9 @@ async def test_mint_quote(ledger: Ledger):
resp_quote = PostMintQuoteResponse(**result2)
assert resp_quote.quote == result["quote"]
assert resp_quote.state == MintQuoteState.paid.value
assert resp_quote.amount == 100
assert resp_quote.unit == "sat"
assert resp_quote.request == result["request"]

# check if DEPRECATED paid flag is also returned
assert result2["paid"] is True
Expand Down Expand Up @@ -338,6 +344,9 @@ async def test_melt_quote_internal(ledger: Ledger, wallet: Wallet):
assert resp_quote.payment_preimage is None
assert resp_quote.change is None
assert resp_quote.state == MeltQuoteState.unpaid.value
assert resp_quote.amount == 64
assert resp_quote.unit == "sat"
assert resp_quote.request == request

# check if DEPRECATED paid flag is also returned
assert result["paid"] is False
Expand Down Expand Up @@ -446,6 +455,9 @@ async def test_melt_internal(ledger: Ledger, wallet: Wallet):
assert resp_quote.payment_preimage is None
assert resp_quote.change == []
assert resp_quote.state == MeltQuoteState.paid.value
assert resp_quote.amount == 64
assert resp_quote.unit == "sat"
assert resp_quote.request == invoice_payment_request

# check if DEPRECATED paid flag is also returned
assert result["paid"] is True
Expand Down Expand Up @@ -504,6 +516,9 @@ async def test_melt_external(ledger: Ledger, wallet: Wallet):
# deserialize the response
resp_quote = PostMeltQuoteResponse(**result)
assert resp_quote.quote == quote.quote
assert resp_quote.amount == 62
assert resp_quote.unit == "sat"
assert resp_quote.request == invoice_payment_request
assert resp_quote.payment_preimage is not None
assert len(resp_quote.payment_preimage) == 64
assert resp_quote.change is not None
Expand Down

0 comments on commit bae4855

Please sign in to comment.