Files
nutshell/cashu/mint/lightning.py
callebtc 0490f20932 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
2023-10-21 14:38:16 +02:00

138 lines
4.8 KiB
Python

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