From ffecd03c4b3c561a51de56b7287a7a762679ecb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Fri, 25 Apr 2025 11:52:54 +0200 Subject: [PATCH] refactor: fundingsource Invoice-, PaymentResponses (#3089) --- lnbits/wallets/alby.py | 33 ++++++++++-------- lnbits/wallets/blink.py | 19 +++++++---- lnbits/wallets/breez.py | 26 +++++++-------- lnbits/wallets/cliche.py | 27 ++++++++------- lnbits/wallets/corelightning.py | 29 ++++++++-------- lnbits/wallets/corelightningrest.py | 52 +++++++++++++++-------------- lnbits/wallets/eclair.py | 44 +++++++++++++----------- lnbits/wallets/lnbits.py | 29 +++++++++------- lnbits/wallets/lndgrpc.py | 14 +++++--- lnbits/wallets/lndrest.py | 32 +++++++++++------- lnbits/wallets/lnpay.py | 33 +++++++++--------- lnbits/wallets/lntips.py | 16 +++++---- lnbits/wallets/nwc.py | 35 +++++++++++-------- lnbits/wallets/opennode.py | 17 +++++----- lnbits/wallets/phoenixd.py | 32 +++++++++++------- lnbits/wallets/spark.py | 34 +++++++++++-------- lnbits/wallets/zbd.py | 30 +++++++++-------- 17 files changed, 287 insertions(+), 215 deletions(-) diff --git a/lnbits/wallets/alby.py b/lnbits/wallets/alby.py index a36c550b..cda22b07 100644 --- a/lnbits/wallets/alby.py +++ b/lnbits/wallets/alby.py @@ -1,7 +1,7 @@ import asyncio import hashlib import json -from typing import AsyncGenerator, Dict, Optional +from typing import AsyncGenerator, Optional import httpx from loguru import logger @@ -72,10 +72,10 @@ class AlbyWallet(Wallet): memo: Optional[str] = None, description_hash: Optional[bytes] = None, unhashed_description: Optional[bytes] = None, - **kwargs, + **_, ) -> InvoiceResponse: # https://api.getalby.com/invoices - data: Dict = {"amount": f"{amount}"} + data: dict = {"amount": f"{amount}"} if description_hash: data["description_hash"] = description_hash.hex() elif unhashed_description: @@ -95,25 +95,29 @@ class AlbyWallet(Wallet): if r.is_error: error_message = data["message"] if "message" in data else r.text - return InvoiceResponse(False, None, None, error_message) + return InvoiceResponse(ok=False, error_message=error_message) checking_id = data["payment_hash"] payment_request = data["payment_request"] - return InvoiceResponse(True, checking_id, payment_request, None) + return InvoiceResponse( + ok=True, + checking_id=checking_id, + payment_request=payment_request, + ) except KeyError as exc: logger.warning(exc) return InvoiceResponse( - False, None, None, "Server error: 'missing required fields'" + ok=False, error_message="Server error: 'missing required fields'" ) except json.JSONDecodeError as exc: logger.warning(exc) return InvoiceResponse( - False, None, None, "Server error: 'invalid json response'" + ok=False, error_message="Server error: 'invalid json response'" ) except Exception as exc: logger.warning(exc) return InvoiceResponse( - False, None, None, f"Unable to connect to {self.endpoint}." + ok=False, error_message=f"Unable to connect to {self.endpoint}." ) async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse: @@ -129,30 +133,31 @@ class AlbyWallet(Wallet): if r.is_error: error_message = data["message"] if "message" in data else r.text - return PaymentResponse(None, None, None, None, error_message) + return PaymentResponse(error_message=error_message) checking_id = data["payment_hash"] # todo: confirm with bitkarrot that having the minus is fine # other funding sources return a positive fee value fee_msat = -data["fee"] preimage = data["payment_preimage"] - - return PaymentResponse(True, checking_id, fee_msat, preimage, None) + return PaymentResponse( + ok=True, checking_id=checking_id, fee_msat=fee_msat, preimage=preimage + ) except KeyError as exc: logger.warning(exc) return PaymentResponse( - None, None, None, None, "Server error: 'missing required fields'" + error_message="Server error: 'missing required fields'" ) except json.JSONDecodeError as exc: logger.warning(exc) return PaymentResponse( - None, None, None, None, "Server error: 'invalid json response'" + error_message="Server error: 'invalid json response'" ) except Exception as exc: logger.info(f"Failed to pay invoice {bolt11}") logger.warning(exc) return PaymentResponse( - None, None, None, None, f"Unable to connect to {self.endpoint}." + error_message=f"Unable to connect to {self.endpoint}." ) async def get_invoice_status(self, checking_id: str) -> PaymentStatus: diff --git a/lnbits/wallets/blink.py b/lnbits/wallets/blink.py index 0a79cad7..9fdf61ea 100644 --- a/lnbits/wallets/blink.py +++ b/lnbits/wallets/blink.py @@ -135,7 +135,7 @@ class BlinkWallet(Wallet): ) if len(errors) > 0: error_message = errors[0].get("message") - return InvoiceResponse(False, None, None, error_message) + return InvoiceResponse(ok=False, error_message=error_message) payment_request = ( response.get("data", {}) @@ -150,15 +150,18 @@ class BlinkWallet(Wallet): .get("paymentHash", None) ) - return InvoiceResponse(True, checking_id, payment_request, None) + # TODO: add preimage to response + return InvoiceResponse( + ok=True, checking_id=checking_id, payment_request=payment_request + ) except json.JSONDecodeError: return InvoiceResponse( - False, None, None, "Server error: 'invalid json response'" + ok=False, error_message="Server error: 'invalid json response'" ) except Exception as exc: logger.warning(exc) return InvoiceResponse( - False, None, None, f"Unable to connect to {self.endpoint}." + ok=False, error_message=f"Unable to connect to {self.endpoint}." ) async def pay_invoice( @@ -185,19 +188,21 @@ class BlinkWallet(Wallet): ) if len(errors) > 0: error_message = errors[0].get("message") - return PaymentResponse(False, None, None, None, error_message) + return PaymentResponse(ok=False, error_message=error_message) checking_id = bolt11.decode(bolt11_invoice).payment_hash payment_status = await self.get_payment_status(checking_id) fee_msat = payment_status.fee_msat preimage = payment_status.preimage - return PaymentResponse(True, checking_id, fee_msat, preimage, None) + return PaymentResponse( + ok=True, checking_id=checking_id, fee_msat=fee_msat, preimage=preimage + ) except Exception as exc: logger.info(f"Failed to pay invoice {bolt11_invoice}") logger.warning(exc) return PaymentResponse( - None, None, None, None, f"Unable to connect to {self.endpoint}." + error_message=f"Unable to connect to {self.endpoint}." ) async def get_invoice_status(self, checking_id: str) -> PaymentStatus: diff --git a/lnbits/wallets/breez.py b/lnbits/wallets/breez.py index 97562309..b6aaa9bd 100644 --- a/lnbits/wallets/breez.py +++ b/lnbits/wallets/breez.py @@ -182,15 +182,16 @@ else: ) ) + # TODO: add preimage return InvoiceResponse( - True, - breez_invoice.ln_invoice.payment_hash, - breez_invoice.ln_invoice.bolt11, - None, + ok=True, + checking_id=breez_invoice.ln_invoice.payment_hash, + payment_request=breez_invoice.ln_invoice.bolt11, + # preimage=breez_invoice.ln_invoice.payment_preimage, ) except Exception as e: logger.warning(e) - return InvoiceResponse(False, None, None, str(e)) + return InvoiceResponse(ok=False, error_message=str(e)) async def pay_invoice( self, bolt11: str, fee_limit_msat: int @@ -217,22 +218,19 @@ else: except Exception as ex: logger.info(ex) # assume that payment failed? - return PaymentResponse( - False, None, None, None, f"payment failed: {exc}" - ) + return PaymentResponse(ok=False, error_message=f"payment failed: {exc}") if payment.status != breez_sdk.PaymentStatus.COMPLETE: - return PaymentResponse(False, None, None, None, "payment is pending") + return PaymentResponse(ok=False, error_message="payment is pending") # let's use the payment_hash as the checking_id checking_id = invoice.payment_hash return PaymentResponse( - True, - checking_id, - payment.fee_msat, - payment.details.data.payment_preimage, - None, + ok=True, + checking_id=checking_id, + fee_msat=payment.fee_msat, + preimage=payment.details.data.payment_preimage, ) async def get_invoice_status(self, checking_id: str) -> PaymentStatus: diff --git a/lnbits/wallets/cliche.py b/lnbits/wallets/cliche.py index e54622c1..c8a391bc 100644 --- a/lnbits/wallets/cliche.py +++ b/lnbits/wallets/cliche.py @@ -54,7 +54,7 @@ class ClicheWallet(Wallet): memo: Optional[str] = None, description_hash: Optional[bytes] = None, unhashed_description: Optional[bytes] = None, - **kwargs, + **_, ) -> InvoiceResponse: if unhashed_description or description_hash: description_hash_str = ( @@ -79,12 +79,13 @@ class ClicheWallet(Wallet): data = json.loads(r) checking_id = None payment_request = None - error_message = None if data.get("error") is not None and data["error"].get("message"): logger.error(data["error"]["message"]) error_message = data["error"]["message"] - return InvoiceResponse(False, checking_id, payment_request, error_message) + return InvoiceResponse( + ok=False, checking_id=checking_id, error_message=error_message + ) if data.get("result") is not None: checking_id, payment_request = ( @@ -92,15 +93,18 @@ class ClicheWallet(Wallet): data["result"]["invoice"], ) else: - return InvoiceResponse(False, None, None, "Could not get payment hash") + return InvoiceResponse(ok=False, error_message="Could not get payment hash") - return InvoiceResponse(True, checking_id, payment_request, error_message) + return InvoiceResponse( + ok=True, + checking_id=checking_id, + payment_request=payment_request, + ) async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse: ws = create_connection(self.endpoint) ws.send(f"pay-invoice --invoice {bolt11}") - checking_id, fee_msat, preimage, error_message, payment_ok = ( - None, + checking_id, fee_msat, preimage, payment_ok = ( None, None, None, @@ -109,8 +113,7 @@ class ClicheWallet(Wallet): for _ in range(2): r = ws.recv() data = json.loads(r) - checking_id, fee_msat, preimage, error_message, payment_ok = ( - None, + checking_id, fee_msat, preimage, payment_ok = ( None, None, None, @@ -119,7 +122,7 @@ class ClicheWallet(Wallet): if data.get("error") is not None: error_message = data["error"].get("message") - return PaymentResponse(False, None, None, None, error_message) + return PaymentResponse(ok=False, error_message=error_message) if data.get("method") == "payment_succeeded": payment_ok = True @@ -129,10 +132,10 @@ class ClicheWallet(Wallet): continue if data.get("result") is None: - return PaymentResponse(None) + return PaymentResponse(error_message="result is None") return PaymentResponse( - payment_ok, checking_id, fee_msat, preimage, error_message + ok=payment_ok, checking_id=checking_id, fee_msat=fee_msat, preimage=preimage ) async def get_invoice_status(self, checking_id: str) -> PaymentStatus: diff --git a/lnbits/wallets/corelightning.py b/lnbits/wallets/corelightning.py index c759a158..f014791b 100644 --- a/lnbits/wallets/corelightning.py +++ b/lnbits/wallets/corelightning.py @@ -115,35 +115,38 @@ class CoreLightningWallet(Wallet): if r.get("code") and r.get("code") < 0: # type: ignore raise Exception(r.get("message")) - - return InvoiceResponse(True, r["payment_hash"], r["bolt11"], None) + return InvoiceResponse( + ok=True, + checking_id=r["payment_hash"], + payment_request=r["bolt11"], + ) except RpcError as exc: logger.warning(exc) error_message = f"RPC '{exc.method}' failed with '{exc.error}'." - return InvoiceResponse(False, None, None, error_message) + return InvoiceResponse(ok=False, error_message=error_message) except KeyError as exc: logger.warning(exc) return InvoiceResponse( - False, None, None, "Server error: 'missing required fields'" + ok=False, error_message="Server error: 'missing required fields'" ) except Exception as e: logger.warning(e) - return InvoiceResponse(False, None, None, str(e)) + return InvoiceResponse(ok=False, error_message=str(e)) async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse: try: invoice = bolt11_decode(bolt11) except Bolt11Exception as exc: - return PaymentResponse(False, None, None, None, str(exc)) + return PaymentResponse(ok=False, error_message=str(exc)) try: previous_payment = await self.get_payment_status(invoice.payment_hash) if previous_payment.paid: - return PaymentResponse(False, None, None, None, "invoice already paid") + return PaymentResponse(ok=False, error_message="invoice already paid") if not invoice.amount_msat or invoice.amount_msat <= 0: return PaymentResponse( - False, None, None, None, "CLN 0 amount invoice not supported" + ok=False, error_message="CLN 0 amount invoice not supported" ) # maxfee overrides both maxfeepercent and exemptfee defaults (and @@ -170,23 +173,23 @@ class CoreLightningWallet(Wallet): if error_code in self.pay_failure_error_codes: error_message = exc.error.get("message", error_code) # type: ignore return PaymentResponse( - False, None, None, None, f"Payment failed: {error_message}" + ok=False, error_message=f"Payment failed: {error_message}" ) else: error_message = f"Payment failed: {exc.error}" - return PaymentResponse(None, None, None, None, error_message) + return PaymentResponse(error_message=error_message) except Exception: error_message = f"RPC '{exc.method}' failed with '{exc.error}'." - return PaymentResponse(None, None, None, None, error_message) + return PaymentResponse(error_message=error_message) except KeyError as exc: logger.warning(exc) return PaymentResponse( - None, None, None, None, "Server error: 'missing required fields'" + error_message="Server error: 'missing required fields'" ) except Exception as exc: logger.info(f"Failed to pay invoice {bolt11}") logger.warning(exc) - return PaymentResponse(None, None, None, None, f"Payment failed: '{exc}'.") + return PaymentResponse(error_message=f"Payment failed: '{exc}'.") async def get_invoice_status(self, checking_id: str) -> PaymentStatus: try: diff --git a/lnbits/wallets/corelightningrest.py b/lnbits/wallets/corelightningrest.py index 50a4ac52..d42ea906 100644 --- a/lnbits/wallets/corelightningrest.py +++ b/lnbits/wallets/corelightningrest.py @@ -1,7 +1,7 @@ import asyncio import json import random -from typing import AsyncGenerator, Dict, Optional +from typing import AsyncGenerator, Optional import httpx from bolt11 import Bolt11Exception @@ -109,7 +109,7 @@ class CoreLightningRestWallet(Wallet): **kwargs, ) -> InvoiceResponse: label = kwargs.get("label", f"lbl{random.random()}") - data: Dict = { + data: dict = { "amount": amount * 1000, "description": memo, "label": label, @@ -139,41 +139,47 @@ class CoreLightningRestWallet(Wallet): data = r.json() if len(data) == 0: - return InvoiceResponse(False, None, None, "no data") + return InvoiceResponse(ok=False, error_message="no data") if "error" in data: return InvoiceResponse( - False, None, None, f"""Server error: '{data["error"]}'""" + ok=False, error_message=f"""Server error: '{data["error"]}'""" ) if r.is_error: - return InvoiceResponse(False, None, None, f"Server error: '{r.text}'") + return InvoiceResponse( + ok=False, error_message=f"Server error: '{r.text}'" + ) if "payment_hash" not in data or "bolt11" not in data: return InvoiceResponse( - False, None, None, "Server error: 'missing required fields'" + ok=False, error_message="Server error: 'missing required fields'" ) - return InvoiceResponse(True, data["payment_hash"], data["bolt11"], None) + return InvoiceResponse( + ok=True, + checking_id=data["payment_hash"], + payment_request=data["bolt11"], + ) except json.JSONDecodeError: return InvoiceResponse( - False, None, None, "Server error: 'invalid json response'" + ok=False, error_message="Server error: 'invalid json response'" ) except Exception as exc: logger.warning(exc) return InvoiceResponse( - False, None, None, f"Unable to connect to {self.url}." + ok=False, error_message=f"Unable to connect to {self.url}." ) async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse: try: invoice = decode(bolt11) except Bolt11Exception as exc: - return PaymentResponse(False, None, None, None, str(exc)) + return PaymentResponse(ok=False, error_message=str(exc)) if not invoice.amount_msat or invoice.amount_msat <= 0: error_message = "0 amount invoices are not allowed" - return PaymentResponse(False, None, None, None, error_message) + return PaymentResponse(ok=False, error_message=error_message) try: r = await self.client.post( f"{self.url}/v1/pay", @@ -190,18 +196,16 @@ class CoreLightningRestWallet(Wallet): status = self.statuses.get(data["status"]) if "payment_preimage" not in data: return PaymentResponse( - status, - None, - None, - None, - data.get("error"), + ok=status, error_message=data.get("error") or "unknown error" ) checking_id = data["payment_hash"] preimage = data["payment_preimage"] fee_msat = data["msatoshi_sent"] - data["msatoshi"] - return PaymentResponse(status, checking_id, fee_msat, preimage, None) + return PaymentResponse( + ok=status, checking_id=checking_id, fee_msat=fee_msat, preimage=preimage + ) except httpx.HTTPStatusError as exc: try: logger.debug(exc) @@ -209,28 +213,26 @@ class CoreLightningRestWallet(Wallet): error_code = int(data["error"]["code"]) if error_code in self.pay_failure_error_codes: error_message = f"Payment failed: {data['error']['message']}" - return PaymentResponse(False, None, None, None, error_message) + return PaymentResponse(ok=False, error_message=error_message) error_message = f"REST failed with {data['error']['message']}." - return PaymentResponse(None, None, None, None, error_message) + return PaymentResponse(error_message=error_message) except Exception as exc: error_message = f"Unable to connect to {self.url}." - return PaymentResponse(None, None, None, None, error_message) + return PaymentResponse(error_message=error_message) except json.JSONDecodeError: return PaymentResponse( - None, None, None, None, "Server error: 'invalid json response'" + error_message="Server error: 'invalid json response'" ) except KeyError as exc: logger.warning(exc) return PaymentResponse( - None, None, None, None, "Server error: 'missing required fields'" + error_message="Server error: 'missing required fields'" ) except Exception as exc: logger.info(f"Failed to pay invoice {bolt11}") logger.warning(exc) - return PaymentResponse( - None, None, None, None, f"Unable to connect to {self.url}." - ) + return PaymentResponse(error_message=f"Unable to connect to {self.url}.") async def get_invoice_status(self, checking_id: str) -> PaymentStatus: r = await self.client.get( diff --git a/lnbits/wallets/eclair.py b/lnbits/wallets/eclair.py index d03bd238..b8b8ea82 100644 --- a/lnbits/wallets/eclair.py +++ b/lnbits/wallets/eclair.py @@ -3,7 +3,7 @@ import base64 import hashlib import json import urllib.parse -from typing import Any, AsyncGenerator, Dict, Optional +from typing import Any, AsyncGenerator, Optional import httpx from loguru import logger @@ -85,7 +85,7 @@ class EclairWallet(Wallet): unhashed_description: Optional[bytes] = None, **kwargs, ) -> InvoiceResponse: - data: Dict[str, Any] = { + data: dict[str, Any] = { "amountMsat": amount * 1000, } if kwargs.get("expiry"): @@ -105,30 +105,35 @@ class EclairWallet(Wallet): data = r.json() if len(data) == 0: - return InvoiceResponse(False, None, None, "no data") + return InvoiceResponse(ok=False, error_message="no data") if "error" in data: return InvoiceResponse( - False, None, None, f"""Server error: '{data["error"]}'""" + ok=False, error_message=f"""Server error: '{data["error"]}'""" ) if r.is_error: - return InvoiceResponse(False, None, None, f"Server error: '{r.text}'") - - return InvoiceResponse(True, data["paymentHash"], data["serialized"], None) + return InvoiceResponse( + ok=False, error_message=f"Server error: '{r.text}'" + ) + return InvoiceResponse( + ok=True, + checking_id=data["paymentHash"], + payment_request=data["serialized"], + ) except json.JSONDecodeError: return InvoiceResponse( - False, None, None, "Server error: 'invalid json response'" + ok=False, error_message="Server error: 'invalid json response'" ) except KeyError as exc: logger.warning(exc) return InvoiceResponse( - False, None, None, "Server error: 'missing required fields'" + ok=False, error_message="Server error: 'missing required fields'" ) except Exception as exc: logger.warning(exc) return InvoiceResponse( - False, None, None, f"Unable to connect to {self.url}." + ok=False, error_message=f"Unable to connect to {self.url}." ) async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse: @@ -142,35 +147,36 @@ class EclairWallet(Wallet): data = r.json() if "error" in data: - return PaymentResponse(None, None, None, None, data["error"]) + return PaymentResponse(error_message=data["error"]) if r.is_error: - return PaymentResponse(None, None, None, None, r.text) + return PaymentResponse(error_message=r.text) if data["type"] == "payment-failed": - return PaymentResponse(False, None, None, None, "payment failed") + return PaymentResponse(ok=False, error_message="payment failed") checking_id = data["paymentHash"] preimage = data["paymentPreimage"] except json.JSONDecodeError: return PaymentResponse( - None, None, None, None, "Server error: 'invalid json response'" + error_message="Server error: 'invalid json response'" ) except KeyError: return PaymentResponse( - None, None, None, None, "Server error: 'missing required fields'" + error_message="Server error: 'missing required fields'" ) except Exception as exc: logger.info(f"Failed to pay invoice {bolt11}") logger.warning(exc) - return PaymentResponse( - None, None, None, None, f"Unable to connect to {self.url}." - ) + return PaymentResponse(error_message=f"Unable to connect to {self.url}.") payment_status: PaymentStatus = await self.get_payment_status(checking_id) success = True if payment_status.success else None return PaymentResponse( - success, checking_id, payment_status.fee_msat, preimage, None + ok=success, + checking_id=checking_id, + fee_msat=payment_status.fee_msat, + preimage=preimage, ) async def get_invoice_status(self, checking_id: str) -> PaymentStatus: diff --git a/lnbits/wallets/lnbits.py b/lnbits/wallets/lnbits.py index c09e4108..d6cc5856 100644 --- a/lnbits/wallets/lnbits.py +++ b/lnbits/wallets/lnbits.py @@ -92,23 +92,25 @@ class LNbitsWallet(Wallet): if r.is_error or not payment_str: error_message = data["detail"] if "detail" in data else r.text return InvoiceResponse( - False, None, None, f"Server error: '{error_message}'" + ok=False, error_message=f"Server error: '{error_message}'" ) - return InvoiceResponse(True, data["checking_id"], payment_str, None) + return InvoiceResponse( + ok=True, checking_id=data["checking_id"], payment_request=payment_str + ) except json.JSONDecodeError: return InvoiceResponse( - False, None, None, "Server error: 'invalid json response'" + ok=False, error_message="Server error: 'invalid json response'" ) except KeyError as exc: logger.warning(exc) return InvoiceResponse( - False, None, None, "Server error: 'missing required fields'" + ok=False, error_message="Server error: 'missing required fields'" ) except Exception as exc: logger.warning(exc) return InvoiceResponse( - False, None, None, f"Unable to connect to {self.endpoint}." + ok=False, error_message=f"Unable to connect to {self.endpoint}." ) async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse: @@ -129,7 +131,10 @@ class LNbitsWallet(Wallet): success = True if payment.success else None return PaymentResponse( - success, checking_id, payment.fee_msat, payment.preimage + ok=success, + checking_id=checking_id, + fee_msat=payment.fee_msat, + preimage=payment.preimage, ) except httpx.HTTPStatusError as exc: @@ -138,25 +143,25 @@ class LNbitsWallet(Wallet): data = exc.response.json() error_message = f"Payment {data['status']}: {data['detail']}." if data["status"] == "failed": - return PaymentResponse(False, None, None, None, error_message) - return PaymentResponse(None, None, None, None, error_message) + return PaymentResponse(ok=False, error_message=error_message) + return PaymentResponse(error_message=error_message) except Exception as exc: error_message = f"Unable to connect to {self.endpoint}." - return PaymentResponse(None, None, None, None, error_message) + return PaymentResponse(error_message=error_message) except json.JSONDecodeError: return PaymentResponse( - None, None, None, None, "Server error: 'invalid json response'" + error_message="Server error: 'invalid json response'" ) except KeyError: return PaymentResponse( - None, None, None, None, "Server error: 'missing required fields'" + error_message="Server error: 'missing required fields'" ) except Exception as exc: logger.info(f"Failed to pay invoice {bolt11}") logger.warning(exc) return PaymentResponse( - None, None, None, None, f"Unable to connect to {self.endpoint}." + error_message=f"Unable to connect to {self.endpoint}." ) async def get_invoice_status(self, checking_id: str) -> PaymentStatus: diff --git a/lnbits/wallets/lndgrpc.py b/lnbits/wallets/lndgrpc.py index 00c04cd6..e338f223 100644 --- a/lnbits/wallets/lndgrpc.py +++ b/lnbits/wallets/lndgrpc.py @@ -149,11 +149,13 @@ class LndWallet(Wallet): except Exception as exc: logger.warning(exc) error_message = str(exc) - return InvoiceResponse(False, None, None, error_message) + return InvoiceResponse(ok=False, error_message=error_message) checking_id = bytes_to_hex(resp.r_hash) payment_request = str(resp.payment_request) - return InvoiceResponse(True, checking_id, payment_request, None) + return InvoiceResponse( + ok=True, checking_id=checking_id, payment_request=payment_request + ) async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse: # fee_limit_fixed = ln.FeeLimit(fixed=fee_limit_msat // 1000) @@ -167,7 +169,7 @@ class LndWallet(Wallet): resp = await self.routerpc.SendPaymentV2(req).read() except Exception as exc: logger.warning(exc) - return PaymentResponse(None, None, None, None, str(exc)) + return PaymentResponse(error_message=str(exc)) # PaymentStatus from https://github.com/lightningnetwork/lnd/blob/master/channeldb/payments.go#L178 statuses = { @@ -199,7 +201,11 @@ class LndWallet(Wallet): error_message = failure_reasons[resp.failure_reason] return PaymentResponse( - statuses[resp.status], checking_id, fee_msat, preimage, error_message + ok=statuses[resp.status], + checking_id=checking_id, + fee_msat=fee_msat, + preimage=preimage, + error_message=error_message, ) async def get_invoice_status(self, checking_id: str) -> PaymentStatus: diff --git a/lnbits/wallets/lndrest.py b/lnbits/wallets/lndrest.py index f327cdfd..4d6662df 100644 --- a/lnbits/wallets/lndrest.py +++ b/lnbits/wallets/lndrest.py @@ -132,34 +132,40 @@ class LndRestWallet(Wallet): data = r.json() if len(data) == 0: - return InvoiceResponse(False, None, None, "no data") + return InvoiceResponse(ok=False, error_message="no data") if "error" in data: return InvoiceResponse( - False, None, None, f"""Server error: '{data["error"]}'""" + ok=False, error_message=f"""Server error: '{data["error"]}'""" ) if r.is_error: - return InvoiceResponse(False, None, None, f"Server error: '{r.text}'") + return InvoiceResponse( + ok=False, error_message=f"Server error: '{r.text}'" + ) if "payment_request" not in data or "r_hash" not in data: return InvoiceResponse( - False, None, None, "Server error: 'missing required fields'" + ok=False, error_message="Server error: 'missing required fields'" ) payment_request = data["payment_request"] payment_hash = base64.b64decode(data["r_hash"]).hex() checking_id = payment_hash + return InvoiceResponse( + ok=True, + checking_id=checking_id, + payment_request=payment_request, + ) - return InvoiceResponse(True, checking_id, payment_request, None) except json.JSONDecodeError: return InvoiceResponse( - False, None, None, "Server error: 'invalid json response'" + ok=False, error_message="Server error: 'invalid json response'" ) except Exception as exc: logger.warning(exc) return InvoiceResponse( - False, None, None, f"Unable to connect to {self.endpoint}." + ok=False, error_message=f"Unable to connect to {self.endpoint}." ) async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse: @@ -185,25 +191,27 @@ class LndRestWallet(Wallet): payment_error = data.get("payment_error") if payment_error: logger.warning(f"LndRestWallet payment_error: {payment_error}.") - return PaymentResponse(False, None, None, None, payment_error) + return PaymentResponse(ok=False, error_message=payment_error) checking_id = base64.b64decode(data["payment_hash"]).hex() fee_msat = int(data["payment_route"]["total_fees_msat"]) preimage = base64.b64decode(data["payment_preimage"]).hex() - return PaymentResponse(True, checking_id, fee_msat, preimage, None) + return PaymentResponse( + ok=True, checking_id=checking_id, fee_msat=fee_msat, preimage=preimage + ) except KeyError as exc: logger.warning(exc) return PaymentResponse( - None, None, None, None, "Server error: 'missing required fields'" + error_message="Server error: 'missing required fields'" ) except json.JSONDecodeError: return PaymentResponse( - None, None, None, None, "Server error: 'invalid json response'" + error_message="Server error: 'invalid json response'" ) except Exception as exc: logger.warning(f"LndRestWallet pay_invoice POST error: {exc}.") return PaymentResponse( - None, None, None, None, f"Unable to connect to {self.endpoint}." + error_message=f"Unable to connect to {self.endpoint}." ) async def get_invoice_status(self, checking_id: str) -> PaymentStatus: diff --git a/lnbits/wallets/lnpay.py b/lnbits/wallets/lnpay.py index 7d2cd559..f5340607 100644 --- a/lnbits/wallets/lnpay.py +++ b/lnbits/wallets/lnpay.py @@ -91,20 +91,17 @@ class LNPayWallet(Wallet): json=data, timeout=60, ) - ok, checking_id, payment_request, error_message = ( - r.status_code == 201, - None, - None, - r.text, - ) - - if ok: + if r.status_code == 201: data = r.json() - checking_id, payment_request = data["id"], data["payment_request"] - - self.pending_invoices.append(checking_id) - - return InvoiceResponse(ok, checking_id, payment_request, error_message) + self.pending_invoices.append(data["id"]) + return InvoiceResponse( + ok=True, + payment_request=data["payment_request"], + ) + return InvoiceResponse( + ok=False, + error_message=r.text, + ) async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse: r = await self.client.post( @@ -116,17 +113,17 @@ class LNPayWallet(Wallet): try: data = r.json() except Exception: - return PaymentResponse( - False, None, 0, None, f"Got invalid JSON: {r.text[:200]}" - ) + return PaymentResponse(ok=False, error_message="Got invalid JSON.") if r.is_error: - return PaymentResponse(False, None, None, None, data["message"]) + return PaymentResponse(ok=False, error_message=data["message"]) checking_id = data["lnTx"]["id"] fee_msat = 0 preimage = data["lnTx"]["payment_preimage"] - return PaymentResponse(True, checking_id, fee_msat, preimage, None) + return PaymentResponse( + ok=True, checking_id=checking_id, fee_msat=fee_msat, preimage=preimage + ) async def get_invoice_status(self, checking_id: str) -> PaymentStatus: return await self.get_payment_status(checking_id) diff --git a/lnbits/wallets/lntips.py b/lnbits/wallets/lntips.py index b8046bdd..3e3e348a 100644 --- a/lnbits/wallets/lntips.py +++ b/lnbits/wallets/lntips.py @@ -70,7 +70,7 @@ class LnTipsWallet(Wallet): memo: Optional[str] = None, description_hash: Optional[bytes] = None, unhashed_description: Optional[bytes] = None, - **kwargs, + **_, ) -> InvoiceResponse: data: Dict = {"amount": amount, "description_hash": "", "memo": memo or ""} if description_hash: @@ -91,11 +91,13 @@ class LnTipsWallet(Wallet): except Exception: error_message = r.text - return InvoiceResponse(False, None, None, error_message) + return InvoiceResponse(ok=False, error_message=error_message) data = r.json() return InvoiceResponse( - True, data["payment_hash"], data["payment_request"], None + ok=True, + checking_id=data["payment_hash"], + payment_request=data["payment_request"], ) async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse: @@ -105,7 +107,7 @@ class LnTipsWallet(Wallet): timeout=None, ) if r.is_error: - return PaymentResponse(False, None, 0, None, r.text) + return PaymentResponse(ok=False, error_message=r.text) if "error" in r.json(): try: @@ -113,13 +115,15 @@ class LnTipsWallet(Wallet): error_message = data["error"] except Exception: error_message = r.text - return PaymentResponse(False, None, 0, None, error_message) + return PaymentResponse(ok=False, error_message=error_message) data = r.json()["details"] checking_id = data["payment_hash"] fee_msat = -data["fee"] preimage = data["preimage"] - return PaymentResponse(True, checking_id, fee_msat, preimage, None) + return PaymentResponse( + ok=True, checking_id=checking_id, fee_msat=fee_msat, preimage=preimage + ) async def get_invoice_status(self, checking_id: str) -> PaymentStatus: try: diff --git a/lnbits/wallets/nwc.py b/lnbits/wallets/nwc.py index bbfad1ec..bca6cbf3 100644 --- a/lnbits/wallets/nwc.py +++ b/lnbits/wallets/nwc.py @@ -132,7 +132,7 @@ class NWCWallet(Wallet): memo: Optional[str] = None, description_hash: Optional[bytes] = None, unhashed_description: Optional[bytes] = None, - **kwargs, + **_, ) -> InvoiceResponse: desc = "" desc_hash = None @@ -148,10 +148,8 @@ class NWCWallet(Wallet): info = await self.conn.get_info() if "make_invoice" not in info["supported_methods"]: return InvoiceResponse( - False, - None, - None, - "make_invoice is not supported by this NWC service.", + ok=False, + error_message="make_invoice is not supported by this NWC service.", ) resp = await self.conn.call( "make_invoice", @@ -175,7 +173,9 @@ class NWCWallet(Wallet): "expired": False, } ) - return InvoiceResponse(True, checking_id, payment_request, None) + return InvoiceResponse( + ok=True, checking_id=checking_id, payment_request=payment_request + ) except Exception as e: return InvoiceResponse(ok=False, error_message=str(e)) @@ -203,7 +203,9 @@ class NWCWallet(Wallet): if "lookup_invoice" not in info["supported_methods"]: # if not supported, we assume it succeeded - return PaymentResponse(True, payment_hash, None, preimage, None) + return PaymentResponse( + ok=True, checking_id=payment_hash, preimage=preimage, fee_msat=0 + ) try: payment_data = await self.conn.call( @@ -213,15 +215,20 @@ class NWCWallet(Wallet): "preimage", None ) if not settled: - return PaymentResponse(None, payment_hash, None, None, None) + return PaymentResponse(checking_id=payment_hash) else: fee_msat = payment_data.get("fees_paid", None) - return PaymentResponse(True, payment_hash, fee_msat, preimage, None) + return PaymentResponse( + ok=True, + checking_id=payment_hash, + fee_msat=fee_msat, + preimage=preimage, + ) except Exception: # Workaround: some nwc service providers might not store the invoice # right away, so this call may raise an exception. # We will assume the payment is pending anyway - return PaymentResponse(None, payment_hash, None, None, None) + return PaymentResponse(checking_id=payment_hash) except NWCError as e: logger.error("Error paying invoice: " + str(e)) failure_codes = [ @@ -237,13 +244,14 @@ class NWCWallet(Wallet): ] failed = e.code in failure_codes return PaymentResponse( - None if not failed else False, + ok=None if not failed else False, error_message=e.message if failed else None, ) except Exception as e: - logger.error("Error paying invoice: " + str(e)) + msg = "Error paying invoice: " + str(e) + logger.error(msg) # assume pending - return PaymentResponse(None) + return PaymentResponse(error_message=msg) async def get_invoice_status(self, checking_id: str) -> PaymentStatus: return await self.get_payment_status(checking_id) @@ -300,6 +308,7 @@ class NWCConnection: self.account_private_key = secp256k1.PrivateKey(bytes.fromhex(secret)) self.account_private_key_hex = secret self.account_public_key = self.account_private_key.pubkey + assert self.account_public_key self.account_public_key_hex = self.account_public_key.serialize().hex()[2:] # Extract service key (used for encryption to identify the nwc service provider) diff --git a/lnbits/wallets/opennode.py b/lnbits/wallets/opennode.py index e4ac8936..d6e11841 100644 --- a/lnbits/wallets/opennode.py +++ b/lnbits/wallets/opennode.py @@ -72,7 +72,7 @@ class OpenNodeWallet(Wallet): memo: Optional[str] = None, description_hash: Optional[bytes] = None, unhashed_description: Optional[bytes] = None, - **kwargs, + **_, ) -> InvoiceResponse: if description_hash or unhashed_description: raise UnsupportedError("description_hash") @@ -88,13 +88,15 @@ class OpenNodeWallet(Wallet): if r.is_error: error_message = r.json()["message"] - return InvoiceResponse(False, None, None, error_message) + return InvoiceResponse(ok=False, error_message=error_message) data = r.json()["data"] checking_id = data["id"] payment_request = data["lightning_invoice"]["payreq"] self.pending_invoices.append(checking_id) - return InvoiceResponse(True, checking_id, payment_request, None) + return InvoiceResponse( + ok=True, checking_id=checking_id, payment_request=payment_request + ) async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse: r = await self.client.post( @@ -105,16 +107,15 @@ class OpenNodeWallet(Wallet): if r.is_error: error_message = r.json()["message"] - return PaymentResponse(False, None, None, None, error_message) + return PaymentResponse(ok=False, error_message=error_message) data = r.json()["data"] checking_id = data["id"] fee_msat = -data["fee"] * 1000 - + # pending if data["status"] != "paid": - return PaymentResponse(None, checking_id, fee_msat, None, "payment failed") - - return PaymentResponse(True, checking_id, fee_msat, None, None) + return PaymentResponse(ok=None, checking_id=checking_id, fee_msat=fee_msat) + return PaymentResponse(ok=True, checking_id=checking_id, fee_msat=fee_msat) async def get_invoice_status(self, checking_id: str) -> PaymentStatus: r = await self.client.get(f"/v1/charge/{checking_id}") diff --git a/lnbits/wallets/phoenixd.py b/lnbits/wallets/phoenixd.py index 3365dfde..64808fbe 100644 --- a/lnbits/wallets/phoenixd.py +++ b/lnbits/wallets/phoenixd.py @@ -133,25 +133,29 @@ class PhoenixdWallet(Wallet): if r.is_error or "paymentHash" not in data: error_message = data["message"] return InvoiceResponse( - False, None, None, f"Server error: '{error_message}'" + ok=False, error_message=f"Server error: '{error_message}'" ) checking_id = data["paymentHash"] payment_request = data["serialized"] - return InvoiceResponse(True, checking_id, payment_request, None) + return InvoiceResponse( + ok=True, + checking_id=checking_id, + payment_request=payment_request, + ) except json.JSONDecodeError: return InvoiceResponse( - False, None, None, "Server error: 'invalid json response'" + ok=False, error_message="Server error: 'invalid json response'" ) except KeyError as exc: logger.warning(exc) return InvoiceResponse( - False, None, None, "Server error: 'missing required fields'" + ok=False, error_message="Server error: 'missing required fields'" ) except Exception as exc: logger.warning(exc) return InvoiceResponse( - False, None, None, f"Unable to connect to {self.endpoint}." + ok=False, error_message=f"Unable to connect to {self.endpoint}." ) async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse: @@ -168,31 +172,35 @@ class PhoenixdWallet(Wallet): data = r.json() if "routingFeeSat" not in data and "reason" in data: - return PaymentResponse(None, None, None, None, data["reason"]) + return PaymentResponse(error_message=data["reason"]) if r.is_error or "paymentHash" not in data: error_message = data["message"] if "message" in data else r.text - return PaymentResponse(None, None, None, None, error_message) + return PaymentResponse(error_message=error_message) checking_id = data["paymentHash"] fee_msat = -int(data["routingFeeSat"]) * 1000 preimage = data["paymentPreimage"] - - return PaymentResponse(True, checking_id, fee_msat, preimage, None) + return PaymentResponse( + ok=True, + checking_id=checking_id, + fee_msat=fee_msat, + preimage=preimage, + ) except json.JSONDecodeError: return PaymentResponse( - None, None, None, None, "Server error: 'invalid json response'" + error_message="Server error: 'invalid json response'" ) except KeyError: return PaymentResponse( - None, None, None, None, "Server error: 'missing required fields'" + error_message="Server error: 'missing required fields'" ) except Exception as exc: logger.info(f"Failed to pay invoice {bolt11}") logger.warning(exc) return PaymentResponse( - None, None, None, None, f"Unable to connect to {self.endpoint}." + error_message=f"Unable to connect to {self.endpoint}." ) async def get_invoice_status(self, checking_id: str) -> PaymentStatus: diff --git a/lnbits/wallets/spark.py b/lnbits/wallets/spark.py index 89d0c432..39dde549 100644 --- a/lnbits/wallets/spark.py +++ b/lnbits/wallets/spark.py @@ -115,8 +115,6 @@ class SparkWallet(Wallet): **kwargs, ) -> InvoiceResponse: label = f"lbs{random.random()}" - checking_id = label - try: if description_hash: r = await self.invoicewithdescriptionhash( @@ -138,11 +136,13 @@ class SparkWallet(Wallet): exposeprivatechannels=True, expiry=kwargs.get("expiry"), ) - ok, payment_request, error_message = True, r["bolt11"], "" + return InvoiceResponse( + ok=True, + payment_request=r["bolt11"], + checking_id=label, + ) except (SparkError, UnknownError) as e: - ok, payment_request, error_message = False, None, str(e) - - return InvoiceResponse(ok, checking_id, payment_request, error_message) + return InvoiceResponse(ok=False, error_message=str(e)) async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse: try: @@ -152,17 +152,22 @@ class SparkWallet(Wallet): ) fee_msat = -int(r["msatoshi_sent"] - r["msatoshi"]) preimage = r["payment_preimage"] - return PaymentResponse(True, r["payment_hash"], fee_msat, preimage, None) + return PaymentResponse( + ok=True, + checking_id=r["payment_hash"], + fee_msat=fee_msat, + preimage=preimage, + ) except (SparkError, UnknownError) as exc: listpays = await self.listpays(bolt11) if not listpays: - return PaymentResponse(False, None, None, None, str(exc)) + return PaymentResponse(ok=False, error_message=str(exc)) pays = listpays["pays"] if len(pays) == 0: - return PaymentResponse(False, None, None, None, str(exc)) + return PaymentResponse(ok=False, error_message=str(exc)) pay = pays[0] payment_hash = pay["payment_hash"] @@ -174,10 +179,10 @@ class SparkWallet(Wallet): ) from exc if pay["status"] == "failed": - return PaymentResponse(False, None, None, None, str(exc)) + return PaymentResponse(ok=False, error_message=str(exc)) if pay["status"] == "pending": - return PaymentResponse(None, payment_hash, None, None, None) + return PaymentResponse(ok=None, checking_id=payment_hash) if pay["status"] == "complete": r = pay @@ -190,10 +195,13 @@ class SparkWallet(Wallet): fee_msat = -int(r["msatoshi_sent"] - r["msatoshi"]) preimage = r["payment_preimage"] return PaymentResponse( - True, r["payment_hash"], fee_msat, preimage, None + ok=True, + checking_id=r["payment_hash"], + fee_msat=fee_msat, + preimage=preimage, ) else: - return PaymentResponse(False, None, None, None, str(exc)) + return PaymentResponse(ok=False, error_message=str(exc)) async def get_invoice_status(self, checking_id: str) -> PaymentStatus: try: diff --git a/lnbits/wallets/zbd.py b/lnbits/wallets/zbd.py index 6f2f892f..ca7c482b 100644 --- a/lnbits/wallets/zbd.py +++ b/lnbits/wallets/zbd.py @@ -3,9 +3,9 @@ import hashlib from typing import AsyncGenerator, Dict, Optional import httpx +from bolt11 import decode as bolt11_decode from loguru import logger -from lnbits import bolt11 from lnbits.settings import settings from .base import ( @@ -61,7 +61,7 @@ class ZBDWallet(Wallet): memo: Optional[str] = None, description_hash: Optional[bytes] = None, unhashed_description: Optional[bytes] = None, - **kwargs, + **_, ) -> InvoiceResponse: # https://api.zebedee.io/v0/charges @@ -89,21 +89,23 @@ class ZBDWallet(Wallet): if r.is_error: error_message = r.json()["message"] - return InvoiceResponse(False, None, None, error_message) + return InvoiceResponse(ok=False, error_message=error_message) data = r.json()["data"] checking_id = data["id"] # this is a zbd id payment_request = data["invoice"]["request"] - return InvoiceResponse(True, checking_id, payment_request, None) + return InvoiceResponse( + ok=True, + checking_id=checking_id, + payment_request=payment_request, + ) - async def pay_invoice( - self, bolt11_invoice: str, fee_limit_msat: int - ) -> PaymentResponse: + async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse: # https://api.zebedee.io/v0/payments r = await self.client.post( "payments", json={ - "invoice": bolt11_invoice, + "invoice": bolt11, "description": "", "amount": "", "internalId": "", @@ -114,15 +116,17 @@ class ZBDWallet(Wallet): if r.is_error: error_message = r.json()["message"] - return PaymentResponse(False, None, None, None, error_message) + return PaymentResponse(ok=False, error_message=error_message) data = r.json() - checking_id = bolt11.decode(bolt11_invoice).payment_hash + checking_id = bolt11_decode(bolt11).payment_hash fee_msat = -int(data["data"]["fee"]) preimage = data["data"]["preimage"] - return PaymentResponse(True, checking_id, fee_msat, preimage, None) + return PaymentResponse( + ok=True, checking_id=checking_id, fee_msat=fee_msat, preimage=preimage + ) async def get_invoice_status(self, checking_id: str) -> PaymentStatus: r = await self.client.get(f"charges/{checking_id}") @@ -137,7 +141,7 @@ class ZBDWallet(Wallet): "expired": False, "completed": True, } - return PaymentStatus(statuses[data.get("status")]) + return PaymentStatus(paid=statuses[data.get("status")]) async def get_payment_status(self, checking_id: str) -> PaymentStatus: r = await self.client.get(f"payments/{checking_id}") @@ -155,7 +159,7 @@ class ZBDWallet(Wallet): "failed": False, } - return PaymentStatus(statuses[data.get("status")], fee_msat=None, preimage=None) + return PaymentStatus(paid=statuses[data.get("status")]) async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: self.queue: asyncio.Queue = asyncio.Queue(0)