Skip to content

Commit

Permalink
change response model of NUT-05 to include payment_preimage and chang…
Browse files Browse the repository at this point in the history
…e (NUT-08)
  • Loading branch information
callebtc committed Jun 26, 2024
1 parent 541324b commit 0f9d7fb
Show file tree
Hide file tree
Showing 10 changed files with 65 additions and 39 deletions.
12 changes: 10 additions & 2 deletions cashu/core/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,8 +290,9 @@ class MeltQuote(LedgerEvent):
created_time: Union[int, None] = None
paid_time: Union[int, None] = None
fee_paid: int = 0
proof: str = ""
payment_preimage: str = ""
expiry: Optional[int] = None
change: Optional[List[BlindedSignature]] = None

@classmethod
def from_row(cls, row: Row):
Expand All @@ -304,6 +305,11 @@ def from_row(cls, row: Row):
)
paid_time = int(row["paid_time"].timestamp()) if row["paid_time"] else None

# parse change from row as json
change = None
if row["change"]:
change = json.loads(row["change"])

return cls(
quote=row["quote"],
method=row["method"],
Expand All @@ -317,7 +323,9 @@ def from_row(cls, row: Row):
created_time=created_time,
paid_time=paid_time,
fee_paid=row["fee_paid"],
proof=row["proof"],
change=change,
expiry=row["expiry"],
payment_preimage=row["proof"],
)

@property
Expand Down
12 changes: 4 additions & 8 deletions cashu/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@ class PostMeltQuoteResponse(BaseModel):
paid: bool # whether the request has been paid # DEPRECATED as per NUT PR #136
state: str # state of the quote
expiry: Optional[int] # expiry of the quote
payment_preimage: Optional[str] = None # payment preimage
change: Union[List[BlindedSignature], None] = None

@classmethod
def from_melt_quote(self, melt_quote: MeltQuote) -> "PostMeltQuoteResponse":
Expand All @@ -203,9 +205,9 @@ class PostMeltRequest(BaseModel):
)


class PostMeltResponse(BaseModel):
class PostMeltResponse_deprecated(BaseModel):
paid: Union[bool, None]
payment_preimage: Union[str, None]
preimage: Union[str, None]
change: Union[List[BlindedSignature], None] = None


Expand All @@ -217,12 +219,6 @@ class PostMeltRequest_deprecated(BaseModel):
)


class PostMeltResponse_deprecated(BaseModel):
paid: Union[bool, None]
preimage: Union[str, None]
change: Union[List[BlindedSignature], None] = None


# ------- API: SPLIT -------


Expand Down
14 changes: 9 additions & 5 deletions cashu/mint/crud.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import json
from abc import ABC, abstractmethod
from typing import Any, List, Optional

Expand Down Expand Up @@ -548,8 +549,8 @@ async def store_melt_quote(
await (conn or db).execute(
f"""
INSERT INTO {table_with_schema(db, 'melt_quotes')}
(quote, method, request, checking_id, unit, amount, fee_reserve, paid, state, created_time, paid_time, fee_paid, proof)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
(quote, method, request, checking_id, unit, amount, fee_reserve, paid, state, created_time, paid_time, fee_paid, proof, change, expiry)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
quote.quote,
Expand All @@ -564,7 +565,9 @@ async def store_melt_quote(
timestamp_from_seconds(db, quote.created_time),
timestamp_from_seconds(db, quote.paid_time),
quote.fee_paid,
quote.proof,
quote.payment_preimage,
json.dumps(quote.change) if quote.change else None,
quote.expiry,
),
)

Expand Down Expand Up @@ -612,13 +615,14 @@ async def update_melt_quote(
) -> None:
await (conn or db).execute(
f"UPDATE {table_with_schema(db, 'melt_quotes')} SET paid = ?, state = ?,"
" fee_paid = ?, paid_time = ?, proof = ? WHERE quote = ?",
" fee_paid = ?, paid_time = ?, proof = ?, change = ? WHERE quote = ?",
(
quote.paid,
quote.state.name,
quote.fee_paid,
timestamp_from_seconds(db, quote.paid_time),
quote.proof,
quote.payment_preimage,
json.dumps([s.dict() for s in quote.change]) if quote.change else None,
quote.quote,
),
)
Expand Down
17 changes: 10 additions & 7 deletions cashu/mint/ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ async def _check_pending_proofs_and_melt_quotes(self):
quote.state = MeltQuoteState.paid
if payment.fee:
quote.fee_paid = payment.fee.to(Unit[quote.unit]).amount
quote.proof = payment.preimage or ""
quote.payment_preimage = payment.preimage or ""
await self.crud.update_melt_quote(quote=quote, db=self.db)
# invalidate proofs
await self._invalidate_proofs(
Expand Down Expand Up @@ -740,7 +740,7 @@ async def get_melt_quote(self, quote_id: str) -> MeltQuote:
if status.fee:
melt_quote.fee_paid = status.fee.to(unit).amount
if status.preimage:
melt_quote.proof = status.preimage
melt_quote.payment_preimage = status.preimage
melt_quote.paid_time = int(time.time())
await self.crud.update_melt_quote(quote=melt_quote, db=self.db)
await self.events.submit(melt_quote)
Expand Down Expand Up @@ -831,7 +831,7 @@ async def melt(
proofs: List[Proof],
quote: str,
outputs: Optional[List[BlindedMessage]] = None,
) -> Tuple[str, List[BlindedSignature]]:
) -> PostMeltQuoteResponse:
"""Invalidates proofs and pays a Lightning invoice.
Args:
Expand Down Expand Up @@ -915,13 +915,11 @@ async def melt(
to_unit=unit, round="up"
).amount
if payment.preimage:
melt_quote.proof = payment.preimage
melt_quote.payment_preimage = payment.preimage
# set quote as paid
melt_quote.paid = True
melt_quote.state = MeltQuoteState.paid
melt_quote.paid_time = int(time.time())
await self.crud.update_melt_quote(quote=melt_quote, db=self.db)
await self.events.submit(melt_quote)

# melt successful, invalidate proofs
await self._invalidate_proofs(proofs=proofs, quote_id=melt_quote.quote)
Expand All @@ -936,14 +934,19 @@ async def melt(
keyset=self.keysets[outputs[0].id],
)

melt_quote.change = return_promises

await self.crud.update_melt_quote(quote=melt_quote, db=self.db)
await self.events.submit(melt_quote)

except Exception as e:
logger.trace(f"Melt exception: {e}")
raise e
finally:
# delete proofs from pending list
await self.db_write._unset_proofs_pending(proofs)

return melt_quote.proof or "", return_promises
return PostMeltQuoteResponse.from_melt_quote(melt_quote)

async def split(
self,
Expand Down
10 changes: 10 additions & 0 deletions cashu/mint/migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -815,3 +815,13 @@ async def m020_add_state_to_mint_and_melt_quotes(db: Database):
await conn.execute(
f"UPDATE {table_with_schema(db, 'melt_quotes')} SET state = '{state}' WHERE quote = '{row['quote']}'"
)


async def m021_add_change_and_expiry_to_melt_quotes(db: Database):
async with db.connect() as conn:
await conn.execute(
f"ALTER TABLE {table_with_schema(db, 'melt_quotes')} ADD COLUMN change TEXT"
)
await conn.execute(
f"ALTER TABLE {table_with_schema(db, 'melt_quotes')} ADD COLUMN expiry TIMESTAMP"
)
10 changes: 3 additions & 7 deletions cashu/mint/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
PostMeltQuoteRequest,
PostMeltQuoteResponse,
PostMeltRequest,
PostMeltResponse,
PostMintQuoteRequest,
PostMintQuoteResponse,
PostMintRequest,
Expand Down Expand Up @@ -290,24 +289,21 @@ async def get_melt_quote(request: Request, quote: str) -> PostMeltQuoteResponse:
"Melt tokens for a Bitcoin payment that the mint will make for the user in"
" exchange"
),
response_model=PostMeltResponse,
response_model=PostMeltQuoteResponse,
response_description=(
"The state of the payment, a preimage as proof of payment, and a list of"
" promises for change."
),
)
@limiter.limit(f"{settings.mint_transaction_rate_limit_per_minute}/minute")
async def melt(request: Request, payload: PostMeltRequest) -> PostMeltResponse:
async def melt(request: Request, payload: PostMeltRequest) -> PostMeltQuoteResponse:
"""
Requests tokens to be destroyed and sent out via Lightning.
"""
logger.trace(f"> POST /v1/melt/bolt11: {payload}")
preimage, change_promises = await ledger.melt(
resp = await ledger.melt(
proofs=payload.inputs, quote=payload.quote, outputs=payload.outputs
)
resp = PostMeltResponse(
paid=True, payment_preimage=preimage, change=change_promises
)
logger.trace(f"< POST /v1/melt/bolt11: {resp}")
return resp

Expand Down
4 changes: 2 additions & 2 deletions cashu/mint/router_deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,11 +230,11 @@ async def melt_deprecated(
quote = await ledger.melt_quote(
PostMeltQuoteRequest(request=payload.pr, unit="sat")
)
preimage, change_promises = await ledger.melt(
melt_resp = await ledger.melt(
proofs=payload.proofs, quote=quote.quote, outputs=outputs
)
resp = PostMeltResponse_deprecated(
paid=True, preimage=preimage, change=change_promises
paid=True, preimage=melt_resp.payment_preimage, change=melt_resp.change
)
logger.trace(f"< POST /melt: {resp}")
return resp
Expand Down
20 changes: 15 additions & 5 deletions cashu/wallet/v1_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
PostMeltRequest,
PostMeltRequestOptionMpp,
PostMeltRequestOptions,
PostMeltResponse,
PostMeltResponse_deprecated,
PostMintQuoteRequest,
PostMintQuoteResponse,
Expand Down Expand Up @@ -406,7 +405,7 @@ async def melt(
quote: str,
proofs: List[Proof],
outputs: Optional[List[BlindedMessage]],
) -> PostMeltResponse:
) -> PostMeltQuoteResponse:
"""
Accepts proofs and a lightning invoice to pay in exchange.
"""
Expand Down Expand Up @@ -438,13 +437,24 @@ def _meltrequest_include_fields(
ret: PostMeltResponse_deprecated = await self.melt_deprecated(
proofs=proofs, outputs=outputs, invoice=invoice.bolt11
)
return PostMeltResponse(
paid=ret.paid, payment_preimage=ret.preimage, change=ret.change
return PostMeltQuoteResponse(
quote=quote,
amount=0,
fee_reserve=0,
paid=ret.paid or False,
state=(
MeltQuoteState.paid.value
if ret.paid
else MeltQuoteState.unpaid.value
),
payment_preimage=ret.preimage,
change=ret.change,
expiry=None,
)
# END backwards compatibility < 0.15.0
self.raise_on_error_request(resp)
return_dict = resp.json()
return PostMeltResponse.parse_obj(return_dict)
return PostMeltQuoteResponse.parse_obj(return_dict)

@async_set_httpx_client
@async_ensure_mint_loaded
Expand Down
3 changes: 1 addition & 2 deletions cashu/wallet/wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
from ..core.models import (
PostCheckStateResponse,
PostMeltQuoteResponse,
PostMeltResponse,
)
from ..core.p2pk import Secret
from ..core.settings import settings
Expand Down Expand Up @@ -639,7 +638,7 @@ async def melt_quote(

async def melt(
self, proofs: List[Proof], invoice: str, fee_reserve_sat: int, quote_id: str
) -> PostMeltResponse:
) -> PostMeltQuoteResponse:
"""Pays a lightning invoice and returns the status of the payment.
Args:
Expand Down
2 changes: 1 addition & 1 deletion cashu/wallet/wallet_deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ def _mintrequest_include_fields(outputs: List[BlindedMessage]):
@async_ensure_mint_loaded_deprecated
async def melt_deprecated(
self, proofs: List[Proof], invoice: str, outputs: Optional[List[BlindedMessage]]
):
) -> PostMeltResponse_deprecated:
"""
Accepts proofs and a lightning invoice to pay in exchange.
"""
Expand Down

0 comments on commit 0f9d7fb

Please sign in to comment.