feat: experiment node from python

This commit is contained in:
Vlad Stan
2026-02-13 16:59:01 +02:00
parent 1f948bbe13
commit 7d9d975312
2 changed files with 61 additions and 4 deletions
+1 -3
View File
@@ -57,9 +57,7 @@ Below is a side-by-side comparison of Lightning funding sources you can use with
| **LN.tips** | Custodial/Self-Custodial | Depends on provider | Medium | ❌ | Low | Provider-managed | Moderate | Low | Transaction fees may apply | Medium | Simple hosted service; use LN.tips API as your backend. | | **LN.tips** | Custodial/Self-Custodial | Depends on provider | Medium | ❌ | Low | Provider-managed | Moderate | Low | Transaction fees may apply | Medium | Simple hosted service; use LN.tips API as your backend. |
| **Fake Wallet** | Testing (simulated) | ❌ | Low | ❌ | N/A | N/A | Easy | Low | None (test only) | N/A | For testing only; mints accounting units in LNbits (no real sats, unit name configurable). | | **Fake Wallet** | Testing (simulated) | ❌ | Low | ❌ | N/A | N/A | Easy | Low | None (test only) | N/A | For testing only; mints accounting units in LNbits (no real sats, unit name configurable). |
## TODO: add Spark
TODO: add Spark
---
### Notes for readers ### Notes for readers
+60 -1
View File
@@ -1,14 +1,17 @@
import asyncio import asyncio
import hashlib import hashlib
import json import json
import shutil
import subprocess
from collections.abc import AsyncGenerator from collections.abc import AsyncGenerator
from pathlib import Path
from typing import Any, cast from typing import Any, cast
import httpx import httpx
from bolt11 import decode as bolt11_decode from bolt11 import decode as bolt11_decode
from loguru import logger from loguru import logger
from lnbits.helpers import normalize_endpoint from lnbits.helpers import download_url, normalize_endpoint
from lnbits.settings import settings from lnbits.settings import settings
from .base import ( from .base import (
@@ -38,21 +41,77 @@ class LightsparkSparkWallet(Wallet):
""" """
def __init__(self): def __init__(self):
self._status = "Initializing"
self._sidecar_path = Path(settings.lnbits_data_folder, "light_spark")
self.pending_invoices: list[str] = [] self.pending_invoices: list[str] = []
self.endpoint = normalize_endpoint(cast(str, settings.spark_l2_endpoint)) self.endpoint = normalize_endpoint(cast(str, settings.spark_l2_endpoint))
api_key = settings.spark_l2_api_key api_key = settings.spark_l2_api_key
headers = {"User-Agent": settings.user_agent} headers = {"User-Agent": settings.user_agent}
if api_key: if api_key:
headers["X-Api-Key"] = api_key headers["X-Api-Key"] = api_key
self.sidecar_task = asyncio.create_task(self._start_sidecar())
self.client = httpx.AsyncClient( self.client = httpx.AsyncClient(
base_url=self.endpoint, base_url=self.endpoint,
headers=headers, headers=headers,
timeout=60, timeout=60,
) )
async def _start_sidecar(self):
logger.info("Starting Spark sidecar")
npm_path = shutil.which("npm")
if not npm_path:
logger.error("npm not found in PATH, cannot start Spark sidecar")
return
logger.info(f"npm found: {npm_path}")
if not Path(self._sidecar_path, "package.json").is_file():
logger.info("⏳ Downloading Spark sidecar.")
await asyncio.to_thread(
download_url,
"https://github.com/lnbits/spark_sidecar/archive/refs/heads/main.zip",
Path(self._sidecar_path, "spark_sidecar.zip"),
)
logger.info("✅ Downloaded Spark sidecar.")
logger.info("⏳ Extracting Spark sidecar.")
shutil.unpack_archive(
Path(self._sidecar_path, "spark_sidecar.zip"),
self._sidecar_path,
)
logger.info("✅ Extracted Spark sidecar.")
# todo: remove zip
node_modules_path = Path(self._sidecar_path, "spark_sidecar-main")
if not Path(self._sidecar_path, node_modules_path, "node_modules").is_dir():
result = subprocess.run(
["npm", "install"],
cwd=str(node_modules_path),
capture_output=True,
text=True,
check=True, # raises an exception if npm fails
)
print("### npm install output:")
print(result.stdout)
print(result.stderr)
print("### Starting Spark sidecar node server")
result = subprocess.run(
["node", "server.mjs"],
cwd=str(node_modules_path),
capture_output=True,
text=True,
check=True, # raises an exception if node fails
)
print("### Spark sidecar output:")
print(result.stdout)
print(result.stderr)
async def cleanup(self): async def cleanup(self):
try: try:
await self.client.aclose() await self.client.aclose()
self.sidecar_task.cancel()
except RuntimeError as e: except RuntimeError as e:
logger.warning(f"Error closing wallet connection: {e}") logger.warning(f"Error closing wallet connection: {e}")