Wallet: store quotes (#657)

* wallet_quotes_wip

* fix quote in db

* fix subscription test

* clean up api

* fix api tests

* fix balance check
This commit is contained in:
callebtc
2024-11-01 13:27:27 +01:00
committed by GitHub
parent 21418a114a
commit 92627399a5
31 changed files with 981 additions and 865 deletions

View File

@@ -15,7 +15,15 @@ import click
from click import Context
from loguru import logger
from ...core.base import Invoice, Method, MintQuoteState, TokenV4, Unit
from ...core.base import (
MeltQuote,
MeltQuoteState,
Method,
MintQuote,
MintQuoteState,
TokenV4,
Unit,
)
from ...core.helpers import sum_proofs
from ...core.json_rpc.base import JSONRPCNotficationParams
from ...core.logging import configure_logger
@@ -24,8 +32,9 @@ from ...core.settings import settings
from ...nostr.client.client import NostrClient
from ...tor.tor import TorProxy
from ...wallet.crud import (
get_lightning_invoice,
get_lightning_invoices,
get_bolt11_melt_quotes,
get_bolt11_mint_quote,
get_bolt11_mint_quotes,
get_reserved_proofs,
get_seed_and_mnemonic,
)
@@ -302,7 +311,7 @@ async def invoice(
)
paid = False
invoice_nonlocal: Union[None, Invoice] = None
invoice_nonlocal: Union[None, MintQuote] = None
subscription_nonlocal: Union[None, SubscriptionManager] = None
def mint_invoice_callback(msg: JSONRPCNotficationParams):
@@ -318,20 +327,24 @@ async def invoice(
if paid:
return
try:
quote = PostMintQuoteResponse.parse_obj(msg.payload)
ws_quote_resp = PostMintQuoteResponse.parse_obj(msg.payload)
except Exception:
return
logger.debug(f"Received callback for quote: {quote}")
logger.debug(
f"Received callback for quote: {ws_quote_resp.quote}: state {ws_quote_resp.state}"
)
# we need to sleep to give the callback map some time to be populated
time.sleep(0.1)
if (
(quote.paid or quote.state == MintQuoteState.paid.value)
and quote.request == invoice.bolt11
(ws_quote_resp.state == MintQuoteState.paid.value)
and ws_quote_resp.request == mint_quote.request
and msg.subId in subscription.callback_map.keys()
):
try:
asyncio.run(
wallet.mint(int(amount), split=optional_split, id=invoice.id)
wallet.mint(
int(amount), split=optional_split, quote_id=mint_quote.quote
)
)
# set paid so we won't react to any more callbacks
paid = True
@@ -348,21 +361,21 @@ async def invoice(
Method["bolt11"], wallet.unit
)
if mint_supports_websockets and not no_check:
invoice, subscription = await wallet.request_mint_with_callback(
mint_quote, subscription = await wallet.request_mint_with_callback(
amount, callback=mint_invoice_callback, memo=memo
)
invoice_nonlocal, subscription_nonlocal = invoice, subscription
invoice_nonlocal, subscription_nonlocal = mint_quote, subscription
else:
invoice = await wallet.request_mint(amount, memo=memo)
if invoice.bolt11:
mint_quote = await wallet.request_mint(amount, memo=memo)
if mint_quote.request:
print("")
print(f"Pay invoice to mint {wallet.unit.str(amount)}:")
print("")
print(f"Invoice: {invoice.bolt11}")
print(f"Invoice: {mint_quote.request}")
print("")
print(
"You can use this command to check the invoice: cashu invoice"
f" {amount} --id {invoice.id}"
f" {amount} --id {mint_quote.quote}"
)
if no_check:
return
@@ -381,9 +394,11 @@ async def invoice(
while time.time() < check_until and not paid:
await asyncio.sleep(5)
try:
mint_quote_resp = await wallet.get_mint_quote(invoice.id)
mint_quote_resp = await wallet.get_mint_quote(mint_quote.quote)
if mint_quote_resp.state == MintQuoteState.paid.value:
await wallet.mint(amount, split=optional_split, id=invoice.id)
await wallet.mint(
amount, split=optional_split, quote_id=mint_quote.quote
)
paid = True
else:
print(".", end="", flush=True)
@@ -403,7 +418,7 @@ async def invoice(
# user paid invoice before and wants to check the quote id
elif amount and id:
await wallet.mint(amount, split=optional_split, id=id)
await wallet.mint(amount, split=optional_split, quote_id=id)
# close open subscriptions so we can exit
try:
@@ -422,10 +437,10 @@ async def invoice(
@coro
async def swap(ctx: Context):
print("Select the mint to swap from:")
outgoing_wallet = await get_mint_wallet(ctx, force_select=True)
outgoing_wallet: Wallet = await get_mint_wallet(ctx, force_select=True)
print("Select the mint to swap to:")
incoming_wallet = await get_mint_wallet(ctx, force_select=True)
incoming_wallet: Wallet = await get_mint_wallet(ctx, force_select=True)
await incoming_wallet.load_mint()
await outgoing_wallet.load_mint()
@@ -437,22 +452,25 @@ async def swap(ctx: Context):
assert amount > 0, "amount is not positive"
# request invoice from incoming mint
invoice = await incoming_wallet.request_mint(amount)
mint_quote = await incoming_wallet.request_mint(amount)
# pay invoice from outgoing mint
quote = await outgoing_wallet.melt_quote(invoice.bolt11)
total_amount = quote.amount + quote.fee_reserve
melt_quote_resp = await outgoing_wallet.melt_quote(mint_quote.request)
total_amount = melt_quote_resp.amount + melt_quote_resp.fee_reserve
if outgoing_wallet.available_balance < total_amount:
raise Exception("balance too low")
send_proofs, fees = await outgoing_wallet.select_to_send(
outgoing_wallet.proofs, total_amount, set_reserved=True
)
await outgoing_wallet.melt(
send_proofs, invoice.bolt11, quote.fee_reserve, quote.quote
send_proofs,
mint_quote.request,
melt_quote_resp.fee_reserve,
melt_quote_resp.quote,
)
# mint token in incoming mint
await incoming_wallet.mint(amount, id=invoice.id)
await incoming_wallet.mint(amount, quote_id=mint_quote.quote)
await incoming_wallet.load_proofs(reload=True)
await print_mint_balances(incoming_wallet, show_mints=True)
@@ -875,67 +893,85 @@ async def invoices(ctx, paid: bool, unpaid: bool, pending: bool, mint: bool):
if mint:
await wallet.load_mint()
paid_arg = None
melt_state: MeltQuoteState | None = None
mint_state: MintQuoteState | None = None
if unpaid:
paid_arg = False
melt_state = MeltQuoteState.unpaid
mint_state = MintQuoteState.unpaid
elif paid:
paid_arg = True
melt_state = MeltQuoteState.paid
mint_state = MintQuoteState.paid
invoices = await get_lightning_invoices(
melt_quotes = await get_bolt11_melt_quotes(
db=wallet.db,
paid=paid_arg,
pending=pending or None,
state=melt_state,
)
if len(invoices) == 0:
mint_quotes = await get_bolt11_mint_quotes(
db=wallet.db,
state=mint_state,
)
if len(melt_quotes) == 0 and len(mint_quotes) == 0:
print("No invoices found.")
return
async def _try_to_mint_pending_invoice(amount: int, id: str) -> Optional[Invoice]:
async def _try_to_mint_pending_invoice(amount: int, id: str) -> Optional[MintQuote]:
try:
await wallet.mint(amount, id)
return await get_lightning_invoice(db=wallet.db, id=id)
proofs = await wallet.mint(amount, id)
print(f"Received {wallet.unit.str(sum_proofs(proofs))}")
return await get_bolt11_mint_quote(db=wallet.db, quote=id)
except Exception as e:
logger.error(f"Could not mint pending invoice [{id}]: {e}")
logger.error(f"Could not mint pending invoice: {e}")
return None
def _print_invoice_info(invoice: Invoice):
def _print_quote_info(
quote: MintQuote | MeltQuote | None, counter: int | None = None
):
print("\n--------------------------\n")
print(f"Amount: {abs(invoice.amount)}")
print(f"ID: {invoice.id}")
print(f"Paid: {invoice.paid}")
print(f"Incoming: {invoice.amount > 0}")
if counter:
print(f"#{counter}", end=" ")
if isinstance(quote, MintQuote):
print("Mint quote (incoming invoice)")
elif isinstance(quote, MeltQuote):
print("Melt quote (outgoing invoice)")
else:
return
print(f"Amount: {abs(quote.amount)}")
print(f"Mint: {quote.mint}")
print(f"ID: {quote.quote}")
print(f"State: {quote.state}")
if invoice.preimage:
print(f"Preimage: {invoice.preimage}")
if invoice.time_created:
if isinstance(quote, MeltQuote):
if quote.payment_preimage:
print(f"Preimage: {quote.payment_preimage}")
if quote.created_time:
d = datetime.fromtimestamp(
int(float(invoice.time_created)), timezone.utc
int(float(quote.created_time)), timezone.utc
).strftime("%Y-%m-%d %H:%M:%S")
print(f"Created at: {d}")
if invoice.time_paid:
if quote.paid_time:
d = datetime.fromtimestamp(
(int(float(invoice.time_paid))), timezone.utc
(int(float(quote.paid_time))), timezone.utc
).strftime("%Y-%m-%d %H:%M:%S")
print(f"Paid at: {d}")
print(f"\nPayment request: {invoice.bolt11}")
print(f"\nPayment request: {quote.request}")
invoices_printed_count = 0
for invoice in invoices:
is_pending_invoice = invoice.out is False and invoice.paid is False
if is_pending_invoice and mint:
# Tries to mint pending invoice
updated_invoice = await _try_to_mint_pending_invoice(
invoice.amount, invoice.id
)
# If the mint ran successfully and we are querying for pending or unpaid invoices, do not print it
if pending or unpaid:
continue
# Otherwise, print the invoice with updated values
if updated_invoice:
invoice = updated_invoice
for melt_quote in melt_quotes:
_print_quote_info(melt_quote, invoices_printed_count + 1)
invoices_printed_count += 1
_print_invoice_info(invoice)
for mint_quote in mint_quotes:
if mint_quote.state == MintQuoteState.unpaid and mint:
# Tries to mint pending invoice
mint_quote_pay = await _try_to_mint_pending_invoice(
mint_quote.amount, mint_quote.quote
)
# If minting was successful, we don't need to print this invoice
if mint_quote_pay:
continue
_print_quote_info(mint_quote, invoices_printed_count + 1)
invoices_printed_count += 1
if invoices_printed_count == 0: