Files
nutshell/cashu/lightning/base.py
callebtc d8d3037cc5 WIP: New melt flow (#622)
* `PaymentResult`

* ledger: rely on PaymentResult instead of paid flag. Double check for payments marked pending.

* `None` is `PENDING`

* make format

* reflected changes API tests where `PaymentStatus` is used + reflected changes in lnbits

* reflect changes in blink backend and tests

* fix lnbits get_payment_status

* remove paid flag

* fix mypy

* remove more paid flags

* fix strike mypy

* green

* shorten all state checks

* fix

* fix some tests

* gimme 

* fix............

* fix lnbits

* fix error

* lightning refactor

* add more regtest tests

* add tests for pending state and failure

* shorten checks

* use match case for startup check - and remember modified checking_id from pay_invoice

* fix strike pending return

* new tests?

* refactor startup routine into get_melt_quote

* test with purge

* refactor blink

* cleanup responses

* blink: return checking_id on failure

* fix lndgrpc try except

* add more testing for melt branches

* speed things up a bit

* remove comments

* remove comments

* block pending melt quotes

* remove comments

---------

Co-authored-by: lollerfirst <lollerfirst@gmail.com>
2024-09-24 14:55:35 +02:00

178 lines
4.2 KiB
Python

from abc import ABC, abstractmethod
from enum import Enum, auto
from typing import AsyncGenerator, Coroutine, Optional, Union
from pydantic import BaseModel
from ..core.base import (
Amount,
MeltQuote,
Unit,
)
from ..core.models import PostMeltQuoteRequest
class StatusResponse(BaseModel):
balance: Union[int, float]
error_message: Optional[str] = None
class InvoiceQuoteResponse(BaseModel):
checking_id: str
amount: int
class PaymentQuoteResponse(BaseModel):
checking_id: str
amount: Amount
fee: Amount
class InvoiceResponse(BaseModel):
ok: bool # True: invoice created, False: failed
checking_id: Optional[str] = None
payment_request: Optional[str] = None
error_message: Optional[str] = None
class PaymentResult(Enum):
SETTLED = auto()
FAILED = auto()
PENDING = auto()
UNKNOWN = auto()
def __str__(self):
return self.name
class PaymentResponse(BaseModel):
result: PaymentResult
checking_id: Optional[str] = None
fee: Optional[Amount] = None
preimage: Optional[str] = None
error_message: Optional[str] = None
@property
def pending(self) -> bool:
return self.result == PaymentResult.PENDING
@property
def settled(self) -> bool:
return self.result == PaymentResult.SETTLED
@property
def failed(self) -> bool:
return self.result == PaymentResult.FAILED
@property
def unknown(self) -> bool:
return self.result == PaymentResult.UNKNOWN
class PaymentStatus(BaseModel):
result: PaymentResult
fee: Optional[Amount] = None
preimage: Optional[str] = None
error_message: Optional[str] = None
@property
def pending(self) -> bool:
return self.result == PaymentResult.PENDING
@property
def settled(self) -> bool:
return self.result == PaymentResult.SETTLED
@property
def failed(self) -> bool:
return self.result == PaymentResult.FAILED
@property
def unknown(self) -> bool:
return self.result == PaymentResult.UNKNOWN
def __str__(self) -> str:
if self.result == PaymentResult.SETTLED:
return (
"settled"
+ (f" (preimage: {self.preimage})" if self.preimage else "")
+ (f" (fee: {self.fee})" if self.fee else "")
)
elif self.result == PaymentResult.FAILED:
return "failed"
elif self.result == PaymentResult.PENDING:
return "still pending"
else: # self.result == PaymentResult.UNKNOWN:
return "unknown" + (
f" (Error: {self.error_message})" if self.error_message else ""
)
class LightningBackend(ABC):
supports_mpp: bool = False
supports_incoming_payment_stream: bool = False
supported_units: set[Unit]
supports_description: bool = False
unit: Unit
def assert_unit_supported(self, unit: Unit):
if unit not in self.supported_units:
raise Unsupported(f"Unit {unit} is not supported")
@abstractmethod
def __init__(self, unit: Unit, **kwargs):
pass
@abstractmethod
def status(self) -> Coroutine[None, None, StatusResponse]:
pass
@abstractmethod
def create_invoice(
self,
amount: Amount,
memo: Optional[str] = None,
description_hash: Optional[bytes] = None,
) -> Coroutine[None, None, InvoiceResponse]:
pass
@abstractmethod
def pay_invoice(
self, quote: MeltQuote, fee_limit_msat: int
) -> Coroutine[None, None, PaymentResponse]:
pass
@abstractmethod
def get_invoice_status(
self, checking_id: str
) -> Coroutine[None, None, PaymentStatus]:
pass
@abstractmethod
def get_payment_status(
self, checking_id: str
) -> Coroutine[None, None, PaymentStatus]:
pass
@abstractmethod
async def get_payment_quote(
self,
melt_quote: PostMeltQuoteRequest,
) -> PaymentQuoteResponse:
pass
# @abstractmethod
# async def get_invoice_quote(
# self,
# bolt11: str,
# ) -> InvoiceQuoteResponse:
# pass
@abstractmethod
def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
pass
class Unsupported(Exception):
pass