From e9909015486d61531450a28d679c45a2fc6d596d Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 9 Feb 2026 13:18:52 +0200 Subject: [PATCH] refactor: - private methods to the bottom - do not raise but return status --- lnbits/wallets/lightspark.py | 205 ++++++++++++++++++----------------- 1 file changed, 106 insertions(+), 99 deletions(-) diff --git a/lnbits/wallets/lightspark.py b/lnbits/wallets/lightspark.py index b31a0a9ab..be98ad90b 100644 --- a/lnbits/wallets/lightspark.py +++ b/lnbits/wallets/lightspark.py @@ -1,6 +1,7 @@ import asyncio import hashlib import json +import os import shutil import subprocess import uuid @@ -80,34 +81,6 @@ class LightsparkSparkWallet(Wallet): except RuntimeError as e: logger.warning(f"Error closing wallet connection: {e}") - async def _request( - self, method: str, path: str, json_data: dict[str, Any] | None = None - ) -> dict[str, Any]: - error_message = None - try: - r = await self.client.request(method, path, json=json_data) - r.raise_for_status() - j = r.json() - except (httpx.RequestError, httpx.HTTPStatusError, json.JSONDecodeError) as exc: - if isinstance(exc, httpx.HTTPStatusError) and exc.response is not None: - try: - error_json = exc.response.json() - if "error" in error_json: - error_message = error_json["error"] - except Exception as json_exc: - logger.error( - f"Failed to parse Spark error response as JSON: {json_exc}" - ) - raise SparkSidecarError( - error_message or f"Spark sidecar request error: '{exc}'" - ) from exc - - if error_message or j.get("error"): - raise SparkSidecarError( - error_message or f"Spark sidecar error: {j['error']}" - ) - return j - async def status(self) -> StatusResponse: try: res = await self._request("POST", "/v1/balance") @@ -119,6 +92,7 @@ class LightsparkSparkWallet(Wallet): return StatusResponse("Spark sidecar: missing balance.", 0) return StatusResponse(None, int(balance_sats) * 1000) except Exception as e: + logger.warning(e) return StatusResponse(f"Spark sidecar status error: {e}", 0) async def create_invoice( @@ -149,8 +123,9 @@ class LightsparkSparkWallet(Wallet): bolt11 = res.get("payment_request") checking_id = res.get("checking_id") if not bolt11 or not checking_id: - raise SparkSidecarError( - "Spark sidecar invoice response missing fields." + return InvoiceResponse( + ok=False, + error_message="Spark sidecar invoice response missing fields.", ) self.pending_invoices.append(checking_id) @@ -158,7 +133,7 @@ class LightsparkSparkWallet(Wallet): ok=True, payment_request=bolt11, checking_id=checking_id, - preimage=res.get("preimage"), + preimage=res.get("preimage", None), ) except Exception as e: return InvoiceResponse(ok=False, error_message=str(e)) @@ -170,7 +145,8 @@ class LightsparkSparkWallet(Wallet): payment_hash = None try: payment_hash = bolt11_decode(bolt11).payment_hash - except Exception: + except Exception as exc: + logger.warning(exc) payment_hash = None payload = { "bolt11": bolt11, @@ -178,10 +154,11 @@ class LightsparkSparkWallet(Wallet): "payment_hash": payment_hash, } res = await self._request("POST", "/v1/payments", payload) - checking_id = payment_hash or res.get("checking_id") + checking_id = payment_hash or res.get("checking_id") # todo: preimage if not checking_id: - raise SparkSidecarError( - "Spark sidecar payment response missing checking_id." + return PaymentResponse( + ok=False, + error_message="Spark sidecar payment response missing checking_id.", ) status = res.get("status") fee_msat = res.get("fee_msat") @@ -205,7 +182,8 @@ class LightsparkSparkWallet(Wallet): if not status: return PaymentPendingStatus() return self._map_invoice_status(status) - except Exception: + except Exception as exc: + logger.warning(exc) return PaymentPendingStatus() async def get_payment_status(self, checking_id: str) -> PaymentStatus: @@ -225,69 +203,10 @@ class LightsparkSparkWallet(Wallet): if mapped.failed: return PaymentFailedStatus() return PaymentPendingStatus() - except Exception: + except Exception as exc: + logger.warning(exc) return PaymentPendingStatus() - def _map_invoice_status(self, status: str) -> PaymentStatus: - success = { - "LIGHTNING_PAYMENT_RECEIVED", - "TRANSFER_COMPLETED", - "PAYMENT_PREIMAGE_RECOVERED", - } - failed = { - "TRANSFER_FAILED", - "PAYMENT_PREIMAGE_RECOVERING_FAILED", - "REFUND_SIGNING_FAILED", - "REFUND_SIGNING_COMMITMENTS_QUERYING_FAILED", - "TRANSFER_CREATION_FAILED", - } - if status in success: - return PaymentSuccessStatus() - if status in failed: - return PaymentFailedStatus() - return PaymentPendingStatus() - - def _map_payment_status(self, status: str) -> PaymentStatus: - success = { - "LIGHTNING_PAYMENT_SUCCEEDED", - "TRANSFER_COMPLETED", - "PREIMAGE_PROVIDED", - } - failed = { - "LIGHTNING_PAYMENT_FAILED", - "TRANSFER_FAILED", - "PREIMAGE_PROVIDING_FAILED", - "USER_TRANSFER_VALIDATION_FAILED", - "USER_SWAP_RETURN_FAILED", - } - if status in success: - return PaymentSuccessStatus() - if status in failed: - return PaymentFailedStatus() - return PaymentPendingStatus() - - def _map_payment_ok(self, status: str) -> bool | None: - mapped = self._map_payment_status(status) - if mapped.success: - return True - if mapped.failed: - return False - return None - - async def _poll_pending_invoices(self) -> AsyncGenerator[str, None]: - while settings.lnbits_running: - for invoice in list(self.pending_invoices): - try: - status = await self.get_invoice_status(invoice) - if status.paid: - yield invoice - self.pending_invoices.remove(invoice) - elif status.failed: - self.pending_invoices.remove(invoice) - except Exception as exc: - logger.error(f"could not get status of invoice {invoice}: '{exc}' ") - await asyncio.sleep(5) - async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: stream_path = "/v1/invoices/stream" while settings.lnbits_running: @@ -318,6 +237,48 @@ class LightsparkSparkWallet(Wallet): ) await asyncio.sleep(5) + async def _request( + self, method: str, path: str, json_data: dict[str, Any] | None = None + ) -> dict[str, Any]: + error_message = None + try: + r = await self.client.request(method, path, json=json_data) + r.raise_for_status() + j = r.json() + except (httpx.RequestError, httpx.HTTPStatusError, json.JSONDecodeError) as exc: + if isinstance(exc, httpx.HTTPStatusError) and exc.response is not None: + try: + error_json = exc.response.json() + if "error" in error_json: + error_message = error_json["error"] + except Exception as json_exc: + logger.error( + f"Failed to parse Spark error response as JSON: {json_exc}" + ) + raise SparkSidecarError( + error_message or f"Spark sidecar request error: '{exc}'" + ) from exc + + if error_message or j.get("error"): + raise SparkSidecarError( + error_message or f"Spark sidecar error: {j['error']}" + ) + return j + + async def _poll_pending_invoices(self) -> AsyncGenerator[str, None]: + while settings.lnbits_running: + for invoice in list(self.pending_invoices): + try: + status = await self.get_invoice_status(invoice) + if status.paid: + yield invoice + self.pending_invoices.remove(invoice) + elif status.failed: + self.pending_invoices.remove(invoice) + except Exception as exc: + logger.error(f"could not get status of invoice {invoice}: '{exc}' ") + await asyncio.sleep(5) + async def _start_sidecar(self): logger.info("Starting Spark sidecar") node_path = shutil.which("node") @@ -391,6 +352,8 @@ class LightsparkSparkWallet(Wallet): zip_path = Path(self._sidecar_path, f"{repo}.zip") logger.info(f"⏳ Downloading Spark sidecar to {zip_path}") Path(zip_path).parent.mkdir(parents=True, exist_ok=True) + if zip_path.is_file(): + os.remove(zip_path) await asyncio.to_thread( download_url, @@ -406,8 +369,6 @@ class LightsparkSparkWallet(Wallet): self._sidecar_path, ) logger.info("✅ Extracted Spark sidecar.") - shutil.rmtree(zip_path, ignore_errors=True) - # todo: remove zip def _log_process_output(self, process: subprocess.Popen): if process.stdout: @@ -415,3 +376,49 @@ class LightsparkSparkWallet(Wallet): logger.warning(f"[Lightspark]: {line}", end="") else: logger.error(" No output captured for Spark sidecar.") + + def _map_invoice_status(self, status: str) -> PaymentStatus: + success = { + "LIGHTNING_PAYMENT_RECEIVED", + "TRANSFER_COMPLETED", + "PAYMENT_PREIMAGE_RECOVERED", + } + failed = { + "TRANSFER_FAILED", + "PAYMENT_PREIMAGE_RECOVERING_FAILED", + "REFUND_SIGNING_FAILED", + "REFUND_SIGNING_COMMITMENTS_QUERYING_FAILED", + "TRANSFER_CREATION_FAILED", + } + if status in success: + return PaymentSuccessStatus() + if status in failed: + return PaymentFailedStatus() + return PaymentPendingStatus() + + def _map_payment_status(self, status: str) -> PaymentStatus: + success = { + "LIGHTNING_PAYMENT_SUCCEEDED", + "TRANSFER_COMPLETED", + "PREIMAGE_PROVIDED", + } + failed = { + "LIGHTNING_PAYMENT_FAILED", + "TRANSFER_FAILED", + "PREIMAGE_PROVIDING_FAILED", + "USER_TRANSFER_VALIDATION_FAILED", + "USER_SWAP_RETURN_FAILED", + } + if status in success: + return PaymentSuccessStatus() + if status in failed: + return PaymentFailedStatus() + return PaymentPendingStatus() + + def _map_payment_ok(self, status: str) -> bool | None: + mapped = self._map_payment_status(status) + if mapped.success: + return True + if mapped.failed: + return False + return None