mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-24 03:54:21 +01:00
Wallet: Lightning interface (#318)
* mint does not start yet * fix import * revert mint db migrations * handle zero fee case * cli: adjust fee message * wallet: replace requests with httpx * clean up * rename http client decorator * fix pending check in main, todo: TEST PROXIES WITH HTTPX * fix up * use httpx for nostr as well * update packages to same versions as https://github.com/lnbits/lnbits/pull/1609/files * fix proof deserialization * check for string * tests passing * adjust wallet api tests * lockfile * add correct responses to Lightning interface and delete melt_id for proofs for which the payent has failed * fix create_invoice checking_id response * migrations atomic * proofs are stored automatically when created * make format * use bolt11 lib * stricter type checking * add fee response to payments * assert fees in test_melt * test that mint_id and melt_id is stored correctly in proofs and proofs_used * remove traces * refactor: Lightning interface into own file and LedgerCrud with typing * fix tests * fix payment response * rename variable
This commit is contained in:
137
cashu/mint/lightning.py
Normal file
137
cashu/mint/lightning.py
Normal file
@@ -0,0 +1,137 @@
|
||||
from typing import Optional, Union
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from ..core.base import (
|
||||
Invoice,
|
||||
)
|
||||
from ..core.db import Connection, Database
|
||||
from ..core.errors import (
|
||||
InvoiceNotPaidError,
|
||||
LightningError,
|
||||
)
|
||||
from ..lightning.base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet
|
||||
from ..mint.crud import LedgerCrud
|
||||
from .protocols import SupportLightning, SupportsDb
|
||||
|
||||
|
||||
class LedgerLightning(SupportLightning, SupportsDb):
|
||||
"""Lightning functions for the ledger."""
|
||||
|
||||
lightning: Wallet
|
||||
crud: LedgerCrud
|
||||
db: Database
|
||||
|
||||
async def _request_lightning_invoice(self, amount: int) -> InvoiceResponse:
|
||||
"""Generate a Lightning invoice using the funding source backend.
|
||||
|
||||
Args:
|
||||
amount (int): Amount of invoice (in Satoshis)
|
||||
|
||||
Raises:
|
||||
Exception: Error with funding source.
|
||||
|
||||
Returns:
|
||||
Tuple[str, str]: Bolt11 invoice and payment id (for lookup)
|
||||
"""
|
||||
logger.trace(
|
||||
"_request_lightning_invoice: Requesting Lightning invoice for"
|
||||
f" {amount} satoshis."
|
||||
)
|
||||
status = await self.lightning.status()
|
||||
logger.trace(
|
||||
"_request_lightning_invoice: Lightning wallet balance:"
|
||||
f" {status.balance_msat}"
|
||||
)
|
||||
if status.error_message:
|
||||
raise LightningError(
|
||||
f"Lightning wallet not responding: {status.error_message}"
|
||||
)
|
||||
payment = await self.lightning.create_invoice(amount, "Cashu deposit")
|
||||
logger.trace(
|
||||
f"_request_lightning_invoice: Lightning invoice: {payment.payment_request}"
|
||||
)
|
||||
|
||||
if not payment.ok:
|
||||
raise LightningError(f"Lightning wallet error: {payment.error_message}")
|
||||
assert payment.payment_request and payment.checking_id, LightningError(
|
||||
"could not fetch invoice from Lightning backend"
|
||||
)
|
||||
return payment
|
||||
|
||||
async def _check_lightning_invoice(
|
||||
self, *, amount: int, id: str, conn: Optional[Connection] = None
|
||||
) -> PaymentStatus:
|
||||
"""Checks with the Lightning backend whether an invoice with `id` was paid.
|
||||
|
||||
Args:
|
||||
amount (int): Amount of the outputs the wallet wants in return (in Satoshis).
|
||||
id (str): Id to look up Lightning invoice by.
|
||||
|
||||
Raises:
|
||||
Exception: Invoice not found.
|
||||
Exception: Tokens for invoice already issued.
|
||||
Exception: Amount larger than invoice amount.
|
||||
Exception: Invoice not paid yet
|
||||
e: Update database and pass through error.
|
||||
|
||||
Returns:
|
||||
bool: True if invoice has been paid, else False
|
||||
"""
|
||||
invoice: Union[Invoice, None] = await self.crud.get_lightning_invoice(
|
||||
id=id, db=self.db, conn=conn
|
||||
)
|
||||
if invoice is None:
|
||||
raise LightningError("invoice not found.")
|
||||
if invoice.issued:
|
||||
raise LightningError("tokens already issued for this invoice.")
|
||||
if amount > invoice.amount:
|
||||
raise LightningError(
|
||||
f"requested amount too high: {amount}. Invoice amount: {invoice.amount}"
|
||||
)
|
||||
assert invoice.payment_hash, "invoice has no payment hash."
|
||||
# set this invoice as issued
|
||||
await self.crud.update_lightning_invoice(
|
||||
id=id, issued=True, db=self.db, conn=conn
|
||||
)
|
||||
|
||||
try:
|
||||
status = await self.lightning.get_invoice_status(invoice.payment_hash)
|
||||
if status.paid:
|
||||
return status
|
||||
else:
|
||||
raise InvoiceNotPaidError()
|
||||
except Exception as e:
|
||||
# unset issued
|
||||
await self.crud.update_lightning_invoice(
|
||||
id=id, issued=False, db=self.db, conn=conn
|
||||
)
|
||||
raise e
|
||||
|
||||
async def _pay_lightning_invoice(
|
||||
self, invoice: str, fee_limit_msat: int
|
||||
) -> PaymentResponse:
|
||||
"""Pays a Lightning invoice via the funding source backend.
|
||||
|
||||
Args:
|
||||
invoice (str): Bolt11 Lightning invoice
|
||||
fee_limit_msat (int): Maximum fee reserve for payment (in Millisatoshi)
|
||||
|
||||
Raises:
|
||||
Exception: Funding source error.
|
||||
|
||||
Returns:
|
||||
Tuple[bool, string, int]: Returns payment status, preimage of invoice, paid fees (in Millisatoshi)
|
||||
"""
|
||||
status = await self.lightning.status()
|
||||
if status.error_message:
|
||||
raise LightningError(
|
||||
f"Lightning wallet not responding: {status.error_message}"
|
||||
)
|
||||
payment = await self.lightning.pay_invoice(
|
||||
invoice, fee_limit_msat=fee_limit_msat
|
||||
)
|
||||
logger.trace(f"_pay_lightning_invoice: Lightning payment status: {payment.ok}")
|
||||
# make sure that fee is positive and not None
|
||||
payment.fee_msat = abs(payment.fee_msat) if payment.fee_msat else 0
|
||||
return payment
|
||||
Reference in New Issue
Block a user