mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-21 11:04:19 +01:00
Fix: Nut 05 mint response model (#564)
* change response model of NUT-05 to include payment_preimage and change (NUT-08) * fix tests * crud: same expiry as timestamp * fix expiry handling * add api tests to check new models
This commit is contained in:
@@ -290,19 +290,27 @@ class MeltQuote(LedgerEvent):
|
|||||||
created_time: Union[int, None] = None
|
created_time: Union[int, None] = None
|
||||||
paid_time: Union[int, None] = None
|
paid_time: Union[int, None] = None
|
||||||
fee_paid: int = 0
|
fee_paid: int = 0
|
||||||
proof: str = ""
|
payment_preimage: str = ""
|
||||||
expiry: Optional[int] = None
|
expiry: Optional[int] = None
|
||||||
|
change: Optional[List[BlindedSignature]] = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_row(cls, row: Row):
|
def from_row(cls, row: Row):
|
||||||
try:
|
try:
|
||||||
created_time = int(row["created_time"]) if row["created_time"] else None
|
created_time = int(row["created_time"]) if row["created_time"] else None
|
||||||
paid_time = int(row["paid_time"]) if row["paid_time"] else None
|
paid_time = int(row["paid_time"]) if row["paid_time"] else None
|
||||||
|
expiry = int(row["expiry"]) if row["expiry"] else None
|
||||||
except Exception:
|
except Exception:
|
||||||
created_time = (
|
created_time = (
|
||||||
int(row["created_time"].timestamp()) if row["created_time"] else None
|
int(row["created_time"].timestamp()) if row["created_time"] else None
|
||||||
)
|
)
|
||||||
paid_time = int(row["paid_time"].timestamp()) if row["paid_time"] else None
|
paid_time = int(row["paid_time"].timestamp()) if row["paid_time"] else None
|
||||||
|
expiry = int(row["expiry"].timestamp()) if row["expiry"] else None
|
||||||
|
|
||||||
|
# parse change from row as json
|
||||||
|
change = None
|
||||||
|
if row["change"]:
|
||||||
|
change = json.loads(row["change"])
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
quote=row["quote"],
|
quote=row["quote"],
|
||||||
@@ -317,7 +325,9 @@ class MeltQuote(LedgerEvent):
|
|||||||
created_time=created_time,
|
created_time=created_time,
|
||||||
paid_time=paid_time,
|
paid_time=paid_time,
|
||||||
fee_paid=row["fee_paid"],
|
fee_paid=row["fee_paid"],
|
||||||
proof=row["proof"],
|
change=change,
|
||||||
|
expiry=expiry,
|
||||||
|
payment_preimage=row["proof"],
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -183,6 +183,8 @@ class PostMeltQuoteResponse(BaseModel):
|
|||||||
paid: bool # whether the request has been paid # DEPRECATED as per NUT PR #136
|
paid: bool # whether the request has been paid # DEPRECATED as per NUT PR #136
|
||||||
state: str # state of the quote
|
state: str # state of the quote
|
||||||
expiry: Optional[int] # expiry of the quote
|
expiry: Optional[int] # expiry of the quote
|
||||||
|
payment_preimage: Optional[str] = None # payment preimage
|
||||||
|
change: Union[List[BlindedSignature], None] = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_melt_quote(self, melt_quote: MeltQuote) -> "PostMeltQuoteResponse":
|
def from_melt_quote(self, melt_quote: MeltQuote) -> "PostMeltQuoteResponse":
|
||||||
@@ -203,9 +205,9 @@ class PostMeltRequest(BaseModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class PostMeltResponse(BaseModel):
|
class PostMeltResponse_deprecated(BaseModel):
|
||||||
paid: Union[bool, None]
|
paid: Union[bool, None]
|
||||||
payment_preimage: Union[str, None]
|
preimage: Union[str, None]
|
||||||
change: Union[List[BlindedSignature], None] = None
|
change: Union[List[BlindedSignature], None] = None
|
||||||
|
|
||||||
|
|
||||||
@@ -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 -------
|
# ------- API: SPLIT -------
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import json
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import Any, List, Optional
|
from typing import Any, List, Optional
|
||||||
|
|
||||||
@@ -548,8 +549,8 @@ class LedgerCrudSqlite(LedgerCrud):
|
|||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
f"""
|
f"""
|
||||||
INSERT INTO {table_with_schema(db, 'melt_quotes')}
|
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)
|
(quote, method, request, checking_id, unit, amount, fee_reserve, paid, state, created_time, paid_time, fee_paid, proof, change, expiry)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
quote.quote,
|
quote.quote,
|
||||||
@@ -564,7 +565,9 @@ class LedgerCrudSqlite(LedgerCrud):
|
|||||||
timestamp_from_seconds(db, quote.created_time),
|
timestamp_from_seconds(db, quote.created_time),
|
||||||
timestamp_from_seconds(db, quote.paid_time),
|
timestamp_from_seconds(db, quote.paid_time),
|
||||||
quote.fee_paid,
|
quote.fee_paid,
|
||||||
quote.proof,
|
quote.payment_preimage,
|
||||||
|
json.dumps(quote.change) if quote.change else None,
|
||||||
|
timestamp_from_seconds(db, quote.expiry),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -612,13 +615,14 @@ class LedgerCrudSqlite(LedgerCrud):
|
|||||||
) -> None:
|
) -> None:
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
f"UPDATE {table_with_schema(db, 'melt_quotes')} SET paid = ?, state = ?,"
|
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.paid,
|
||||||
quote.state.name,
|
quote.state.name,
|
||||||
quote.fee_paid,
|
quote.fee_paid,
|
||||||
timestamp_from_seconds(db, quote.paid_time),
|
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,
|
quote.quote,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
|||||||
quote.state = MeltQuoteState.paid
|
quote.state = MeltQuoteState.paid
|
||||||
if payment.fee:
|
if payment.fee:
|
||||||
quote.fee_paid = payment.fee.to(Unit[quote.unit]).amount
|
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)
|
await self.crud.update_melt_quote(quote=quote, db=self.db)
|
||||||
# invalidate proofs
|
# invalidate proofs
|
||||||
await self._invalidate_proofs(
|
await self._invalidate_proofs(
|
||||||
@@ -740,7 +740,7 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
|||||||
if status.fee:
|
if status.fee:
|
||||||
melt_quote.fee_paid = status.fee.to(unit).amount
|
melt_quote.fee_paid = status.fee.to(unit).amount
|
||||||
if status.preimage:
|
if status.preimage:
|
||||||
melt_quote.proof = status.preimage
|
melt_quote.payment_preimage = status.preimage
|
||||||
melt_quote.paid_time = int(time.time())
|
melt_quote.paid_time = int(time.time())
|
||||||
await self.crud.update_melt_quote(quote=melt_quote, db=self.db)
|
await self.crud.update_melt_quote(quote=melt_quote, db=self.db)
|
||||||
await self.events.submit(melt_quote)
|
await self.events.submit(melt_quote)
|
||||||
@@ -831,7 +831,7 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
|||||||
proofs: List[Proof],
|
proofs: List[Proof],
|
||||||
quote: str,
|
quote: str,
|
||||||
outputs: Optional[List[BlindedMessage]] = None,
|
outputs: Optional[List[BlindedMessage]] = None,
|
||||||
) -> Tuple[str, List[BlindedSignature]]:
|
) -> PostMeltQuoteResponse:
|
||||||
"""Invalidates proofs and pays a Lightning invoice.
|
"""Invalidates proofs and pays a Lightning invoice.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -915,13 +915,11 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
|||||||
to_unit=unit, round="up"
|
to_unit=unit, round="up"
|
||||||
).amount
|
).amount
|
||||||
if payment.preimage:
|
if payment.preimage:
|
||||||
melt_quote.proof = payment.preimage
|
melt_quote.payment_preimage = payment.preimage
|
||||||
# set quote as paid
|
# set quote as paid
|
||||||
melt_quote.paid = True
|
melt_quote.paid = True
|
||||||
melt_quote.state = MeltQuoteState.paid
|
melt_quote.state = MeltQuoteState.paid
|
||||||
melt_quote.paid_time = int(time.time())
|
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
|
# melt successful, invalidate proofs
|
||||||
await self._invalidate_proofs(proofs=proofs, quote_id=melt_quote.quote)
|
await self._invalidate_proofs(proofs=proofs, quote_id=melt_quote.quote)
|
||||||
@@ -936,6 +934,11 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
|||||||
keyset=self.keysets[outputs[0].id],
|
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:
|
except Exception as e:
|
||||||
logger.trace(f"Melt exception: {e}")
|
logger.trace(f"Melt exception: {e}")
|
||||||
raise e
|
raise e
|
||||||
@@ -943,7 +946,7 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
|||||||
# delete proofs from pending list
|
# delete proofs from pending list
|
||||||
await self.db_write._unset_proofs_pending(proofs)
|
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(
|
async def split(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -815,3 +815,13 @@ async def m020_add_state_to_mint_and_melt_quotes(db: Database):
|
|||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"UPDATE {table_with_schema(db, 'melt_quotes')} SET state = '{state}' WHERE quote = '{row['quote']}'"
|
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"
|
||||||
|
)
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ from ..core.models import (
|
|||||||
PostMeltQuoteRequest,
|
PostMeltQuoteRequest,
|
||||||
PostMeltQuoteResponse,
|
PostMeltQuoteResponse,
|
||||||
PostMeltRequest,
|
PostMeltRequest,
|
||||||
PostMeltResponse,
|
|
||||||
PostMintQuoteRequest,
|
PostMintQuoteRequest,
|
||||||
PostMintQuoteResponse,
|
PostMintQuoteResponse,
|
||||||
PostMintRequest,
|
PostMintRequest,
|
||||||
@@ -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"
|
"Melt tokens for a Bitcoin payment that the mint will make for the user in"
|
||||||
" exchange"
|
" exchange"
|
||||||
),
|
),
|
||||||
response_model=PostMeltResponse,
|
response_model=PostMeltQuoteResponse,
|
||||||
response_description=(
|
response_description=(
|
||||||
"The state of the payment, a preimage as proof of payment, and a list of"
|
"The state of the payment, a preimage as proof of payment, and a list of"
|
||||||
" promises for change."
|
" promises for change."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@limiter.limit(f"{settings.mint_transaction_rate_limit_per_minute}/minute")
|
@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.
|
Requests tokens to be destroyed and sent out via Lightning.
|
||||||
"""
|
"""
|
||||||
logger.trace(f"> POST /v1/melt/bolt11: {payload}")
|
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
|
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}")
|
logger.trace(f"< POST /v1/melt/bolt11: {resp}")
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
|||||||
@@ -230,11 +230,11 @@ async def melt_deprecated(
|
|||||||
quote = await ledger.melt_quote(
|
quote = await ledger.melt_quote(
|
||||||
PostMeltQuoteRequest(request=payload.pr, unit="sat")
|
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
|
proofs=payload.proofs, quote=quote.quote, outputs=outputs
|
||||||
)
|
)
|
||||||
resp = PostMeltResponse_deprecated(
|
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}")
|
logger.trace(f"< POST /melt: {resp}")
|
||||||
return resp
|
return resp
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ from ..core.models import (
|
|||||||
PostMeltRequest,
|
PostMeltRequest,
|
||||||
PostMeltRequestOptionMpp,
|
PostMeltRequestOptionMpp,
|
||||||
PostMeltRequestOptions,
|
PostMeltRequestOptions,
|
||||||
PostMeltResponse,
|
|
||||||
PostMeltResponse_deprecated,
|
PostMeltResponse_deprecated,
|
||||||
PostMintQuoteRequest,
|
PostMintQuoteRequest,
|
||||||
PostMintQuoteResponse,
|
PostMintQuoteResponse,
|
||||||
@@ -406,7 +405,7 @@ class LedgerAPI(LedgerAPIDeprecated, object):
|
|||||||
quote: str,
|
quote: str,
|
||||||
proofs: List[Proof],
|
proofs: List[Proof],
|
||||||
outputs: Optional[List[BlindedMessage]],
|
outputs: Optional[List[BlindedMessage]],
|
||||||
) -> PostMeltResponse:
|
) -> PostMeltQuoteResponse:
|
||||||
"""
|
"""
|
||||||
Accepts proofs and a lightning invoice to pay in exchange.
|
Accepts proofs and a lightning invoice to pay in exchange.
|
||||||
"""
|
"""
|
||||||
@@ -438,13 +437,24 @@ class LedgerAPI(LedgerAPIDeprecated, object):
|
|||||||
ret: PostMeltResponse_deprecated = await self.melt_deprecated(
|
ret: PostMeltResponse_deprecated = await self.melt_deprecated(
|
||||||
proofs=proofs, outputs=outputs, invoice=invoice.bolt11
|
proofs=proofs, outputs=outputs, invoice=invoice.bolt11
|
||||||
)
|
)
|
||||||
return PostMeltResponse(
|
return PostMeltQuoteResponse(
|
||||||
paid=ret.paid, payment_preimage=ret.preimage, change=ret.change
|
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
|
# END backwards compatibility < 0.15.0
|
||||||
self.raise_on_error_request(resp)
|
self.raise_on_error_request(resp)
|
||||||
return_dict = resp.json()
|
return_dict = resp.json()
|
||||||
return PostMeltResponse.parse_obj(return_dict)
|
return PostMeltQuoteResponse.parse_obj(return_dict)
|
||||||
|
|
||||||
@async_set_httpx_client
|
@async_set_httpx_client
|
||||||
@async_ensure_mint_loaded
|
@async_ensure_mint_loaded
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ from ..core.migrations import migrate_databases
|
|||||||
from ..core.models import (
|
from ..core.models import (
|
||||||
PostCheckStateResponse,
|
PostCheckStateResponse,
|
||||||
PostMeltQuoteResponse,
|
PostMeltQuoteResponse,
|
||||||
PostMeltResponse,
|
|
||||||
)
|
)
|
||||||
from ..core.p2pk import Secret
|
from ..core.p2pk import Secret
|
||||||
from ..core.settings import settings
|
from ..core.settings import settings
|
||||||
@@ -639,7 +638,7 @@ class Wallet(
|
|||||||
|
|
||||||
async def melt(
|
async def melt(
|
||||||
self, proofs: List[Proof], invoice: str, fee_reserve_sat: int, quote_id: str
|
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.
|
"""Pays a lightning invoice and returns the status of the payment.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -306,7 +306,7 @@ class LedgerAPIDeprecated(SupportsHttpxClient, SupportsMintURL):
|
|||||||
@async_ensure_mint_loaded_deprecated
|
@async_ensure_mint_loaded_deprecated
|
||||||
async def melt_deprecated(
|
async def melt_deprecated(
|
||||||
self, proofs: List[Proof], invoice: str, outputs: Optional[List[BlindedMessage]]
|
self, proofs: List[Proof], invoice: str, outputs: Optional[List[BlindedMessage]]
|
||||||
):
|
) -> PostMeltResponse_deprecated:
|
||||||
"""
|
"""
|
||||||
Accepts proofs and a lightning invoice to pay in exchange.
|
Accepts proofs and a lightning invoice to pay in exchange.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ import httpx
|
|||||||
import pytest
|
import pytest
|
||||||
import pytest_asyncio
|
import pytest_asyncio
|
||||||
|
|
||||||
from cashu.core.base import ProofSpentState
|
from cashu.core.base import MeltQuoteState, MintQuoteState, ProofSpentState
|
||||||
from cashu.core.models import (
|
from cashu.core.models import (
|
||||||
GetInfoResponse,
|
GetInfoResponse,
|
||||||
MintMeltMethodSetting,
|
MintMeltMethodSetting,
|
||||||
PostCheckStateRequest,
|
PostCheckStateRequest,
|
||||||
PostCheckStateResponse,
|
PostCheckStateResponse,
|
||||||
|
PostMeltQuoteResponse,
|
||||||
|
PostMintQuoteResponse,
|
||||||
PostRestoreRequest,
|
PostRestoreRequest,
|
||||||
PostRestoreResponse,
|
PostRestoreResponse,
|
||||||
)
|
)
|
||||||
@@ -186,6 +188,16 @@ async def test_mint_quote(ledger: Ledger):
|
|||||||
result = response.json()
|
result = response.json()
|
||||||
assert result["quote"]
|
assert result["quote"]
|
||||||
assert result["request"]
|
assert result["request"]
|
||||||
|
|
||||||
|
# deserialize the response
|
||||||
|
resp_quote = PostMintQuoteResponse(**result)
|
||||||
|
assert resp_quote.quote == result["quote"]
|
||||||
|
assert resp_quote.state == MintQuoteState.unpaid.value
|
||||||
|
|
||||||
|
# check if DEPRECATED paid flag is also returned
|
||||||
|
assert result["paid"] is False
|
||||||
|
assert resp_quote.paid is False
|
||||||
|
|
||||||
invoice = bolt11.decode(result["request"])
|
invoice = bolt11.decode(result["request"])
|
||||||
assert invoice.amount_msat == 100 * 1000
|
assert invoice.amount_msat == 100 * 1000
|
||||||
|
|
||||||
@@ -195,6 +207,9 @@ async def test_mint_quote(ledger: Ledger):
|
|||||||
|
|
||||||
assert result["expiry"] == expiry
|
assert result["expiry"] == expiry
|
||||||
|
|
||||||
|
# pay the invoice
|
||||||
|
pay_if_regtest(result["request"])
|
||||||
|
|
||||||
# get mint quote again from api
|
# get mint quote again from api
|
||||||
response = httpx.get(
|
response = httpx.get(
|
||||||
f"{BASE_URL}/v1/mint/quote/bolt11/{result['quote']}",
|
f"{BASE_URL}/v1/mint/quote/bolt11/{result['quote']}",
|
||||||
@@ -202,6 +217,14 @@ async def test_mint_quote(ledger: Ledger):
|
|||||||
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||||
result2 = response.json()
|
result2 = response.json()
|
||||||
assert result2["quote"] == result["quote"]
|
assert result2["quote"] == result["quote"]
|
||||||
|
# deserialize the response
|
||||||
|
resp_quote = PostMintQuoteResponse(**result2)
|
||||||
|
assert resp_quote.quote == result["quote"]
|
||||||
|
assert resp_quote.state == MintQuoteState.paid.value
|
||||||
|
|
||||||
|
# check if DEPRECATED paid flag is also returned
|
||||||
|
assert result2["paid"] is True
|
||||||
|
assert resp_quote.paid is True
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@@ -255,6 +278,18 @@ async def test_melt_quote_internal(ledger: Ledger, wallet: Wallet):
|
|||||||
assert result["amount"] == 64
|
assert result["amount"] == 64
|
||||||
# TODO: internal invoice, fee should be 0
|
# TODO: internal invoice, fee should be 0
|
||||||
assert result["fee_reserve"] == 0
|
assert result["fee_reserve"] == 0
|
||||||
|
|
||||||
|
# deserialize the response
|
||||||
|
resp_quote = PostMeltQuoteResponse(**result)
|
||||||
|
assert resp_quote.quote == result["quote"]
|
||||||
|
assert resp_quote.payment_preimage is None
|
||||||
|
assert resp_quote.change is None
|
||||||
|
assert resp_quote.state == MeltQuoteState.unpaid.value
|
||||||
|
|
||||||
|
# check if DEPRECATED paid flag is also returned
|
||||||
|
assert result["paid"] is False
|
||||||
|
assert resp_quote.paid is False
|
||||||
|
|
||||||
invoice_obj = bolt11.decode(request)
|
invoice_obj = bolt11.decode(request)
|
||||||
|
|
||||||
expiry = None
|
expiry = None
|
||||||
@@ -263,13 +298,25 @@ async def test_melt_quote_internal(ledger: Ledger, wallet: Wallet):
|
|||||||
|
|
||||||
assert result["expiry"] == expiry
|
assert result["expiry"] == expiry
|
||||||
|
|
||||||
# get melt quote again from api
|
# # get melt quote again from api
|
||||||
response = httpx.get(
|
# response = httpx.get(
|
||||||
f"{BASE_URL}/v1/melt/quote/bolt11/{result['quote']}",
|
# f"{BASE_URL}/v1/melt/quote/bolt11/{result['quote']}",
|
||||||
)
|
# )
|
||||||
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
# assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||||
result2 = response.json()
|
# result2 = response.json()
|
||||||
assert result2["quote"] == result["quote"]
|
# assert result2["quote"] == result["quote"]
|
||||||
|
|
||||||
|
# # deserialize the response
|
||||||
|
# resp_quote = PostMeltQuoteResponse(**result2)
|
||||||
|
# assert resp_quote.quote == result["quote"]
|
||||||
|
# assert resp_quote.payment_preimage is not None
|
||||||
|
# assert len(resp_quote.payment_preimage) == 64
|
||||||
|
# assert resp_quote.change is not None
|
||||||
|
# assert resp_quote.state == MeltQuoteState.paid.value
|
||||||
|
|
||||||
|
# # check if DEPRECATED paid flag is also returned
|
||||||
|
# assert result2["paid"] is True
|
||||||
|
# assert resp_quote.paid is True
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@@ -338,6 +385,19 @@ async def test_melt_internal(ledger: Ledger, wallet: Wallet):
|
|||||||
assert result.get("payment_preimage") is not None
|
assert result.get("payment_preimage") is not None
|
||||||
assert result["paid"] is True
|
assert result["paid"] is True
|
||||||
|
|
||||||
|
# deserialize the response
|
||||||
|
resp_quote = PostMeltQuoteResponse(**result)
|
||||||
|
assert resp_quote.quote == quote.quote
|
||||||
|
|
||||||
|
# internal invoice, no preimage, no change
|
||||||
|
assert resp_quote.payment_preimage == ""
|
||||||
|
assert resp_quote.change == []
|
||||||
|
assert resp_quote.state == MeltQuoteState.paid.value
|
||||||
|
|
||||||
|
# check if DEPRECATED paid flag is also returned
|
||||||
|
assert result["paid"] is True
|
||||||
|
assert resp_quote.paid is True
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
@@ -387,6 +447,19 @@ async def test_melt_external(ledger: Ledger, wallet: Wallet):
|
|||||||
# we get back 2 sats because Lightning was free to pay on regtest
|
# we get back 2 sats because Lightning was free to pay on regtest
|
||||||
assert result["change"][0]["amount"] == 2
|
assert result["change"][0]["amount"] == 2
|
||||||
|
|
||||||
|
# deserialize the response
|
||||||
|
resp_quote = PostMeltQuoteResponse(**result)
|
||||||
|
assert resp_quote.quote == quote.quote
|
||||||
|
assert resp_quote.payment_preimage is not None
|
||||||
|
assert len(resp_quote.payment_preimage) == 64
|
||||||
|
assert resp_quote.change is not None
|
||||||
|
assert resp_quote.change[0].amount == 2
|
||||||
|
assert resp_quote.state == MeltQuoteState.paid.value
|
||||||
|
|
||||||
|
# check if DEPRECATED paid flag is also returned
|
||||||
|
assert result["paid"] is True
|
||||||
|
assert resp_quote.paid is True
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
|
|||||||
@@ -357,11 +357,12 @@ async def test_melt_with_more_inputs_than_invoice(wallet1: Wallet, ledger: Ledge
|
|||||||
|
|
||||||
# make sure we have more inputs than the melt quote needs
|
# make sure we have more inputs than the melt quote needs
|
||||||
assert sum_proofs(wallet1.proofs) >= melt_quote.amount + melt_quote.fee_reserve
|
assert sum_proofs(wallet1.proofs) >= melt_quote.amount + melt_quote.fee_reserve
|
||||||
payment_proof, return_outputs = await ledger.melt(
|
melt_resp = await ledger.melt(
|
||||||
proofs=wallet1.proofs, quote=melt_quote.quote, outputs=outputs
|
proofs=wallet1.proofs, quote=melt_quote.quote, outputs=outputs
|
||||||
)
|
)
|
||||||
# we get 2 sats back because we overpaid
|
# we get 2 sats back because we overpaid
|
||||||
assert sum([o.amount for o in return_outputs]) == 2
|
assert melt_resp.change
|
||||||
|
assert sum([o.amount for o in melt_resp.change]) == 2
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
|||||||
Reference in New Issue
Block a user