Skip to content

Repository files navigation

LNbits

License: MIT Built for LNbits

Spark L2 sidecar

This sidecar exposes a small HTTP API for LNbits to talk to the Spark L2 SDK. https://www.spark.money/

Install

git clone https://github.com/lnbits/spark_sidecar.git
cd spark_sidecar
npm install

Run

chmod +x server.mjs

SPARK_MNEMONIC="bottom bottom bottom bottom bottom bottom bottom bottom bottom bottom bottom bottom" \
SPARK_NETWORK=MAINNET \
SPARK_SIDECAR_PORT=8765 \
SPARK_PAY_WAIT_MS=20000 \
node server.mjs

Spark Multiplicity Setting

Optional multiplicity tuning for Spark leaf optimization

Default multiplicity is 3

SPARK_MULTIPLICITY=3

Optional API Key

SPARK_SIDECAR_API_KEY="mykey"

Set the same key in LNbits as SPARK_L2_EXTERNAL_API_KEY, and point SPARK_L2_EXTERNAL_ENDPOINT at this sidecar.

If you prefer to provide the mnemonic after startup, omit SPARK_MNEMONIC and POST it to the sidecar:

curl -X POST http://127.0.0.1:8765/v1/mnemonic \
  -H "Content-Type: application/json" \
  -d '{"mnemonic":"bottom bottom bottom bottom bottom bottom bottom bottom bottom bottom bottom bottom"}'

Nix (flake)

Build:

nix build

Run:

SPARK_MNEMONIC="bottom bottom bottom bottom bottom bottom bottom bottom bottom bottom bottom bottom" \
SPARK_NETWORK=MAINNET \
SPARK_SIDECAR_PORT=8765 \
SPARK_PAY_WAIT_MS=20000 \
nix run

Notes:

  • The flake includes flake.nix and flake.lock. Commit both.
  • The result symlink from nix build should not be committed.

Endpoints

  • POST /v1/mnemonic
  • POST /v1/balance
  • POST /v1/invoices
  • POST /v1/payments
  • GET /v1/invoices/stream (SSE stream of paid Lightning receive requests)
  • GET /v1/invoices/{id}
  • GET /v1/payments/{id}

Invoice Stream

The stream endpoint emits Server-Sent Events after the received Spark funds have cleared. Both invoice status lookups and stream notifications check the invoice's own incoming transfer and its leaves. A Lightning success status alone is not enough: uncleared receipts return WAITING_FOR_FUNDS, which SparkL2 maps to pending. The check requires a completed transfer and available leaves registered in the SDK's local cache. Later spending, completed splits/aggregations, or a subsequent ownership change on a single-receiver transfer also prove prior availability. Each lookup reconstructs this evidence from Spark; there is no saved receipt ledger. A provider outage returns pending/unavailable, never an invented success. The availability check reads the pinned SDK's internal leaf registry; review incoming.mjs when upgrading the SDK.

Transfer events trigger checks immediately. While a stream client is connected, polling discovers new updates and retries uncleared receipts using bounded, in-memory bookkeeping. The discovery cursor starts at process startup. It does not replay all historic invoices after every restart. LNbits' invoice-ID checks recover payments made while the sidecar was offline directly from Spark.

SSE delivery is best effort, not an acknowledgement protocol. Disconnects can lose events, and reconnects can repeat them. Invoice-ID checks are the recovery path; a GET returns status without emitting an additional SSE notification.

Example:

curl -N http://127.0.0.1:8765/v1/invoices/stream

Each event payload is a JSON object:

{
  "checking_id": "<receive_request_id>",
  "payment_hash": "<hash>",
  "status": "LIGHTNING_PAYMENT_RECEIVED"
}

Optional tuning:

  • SPARK_STREAM_KEEPALIVE_MS (default 15000)
  • SPARK_STREAM_HEARTBEAT_MS (default 30000)
  • SPARK_TRANSFER_LOOKUP_CONCURRENCY (default 20)
  • SPARK_TRANSFER_QUEUE_MAX (default 5000)
  • SPARK_INVOICE_POLL_MS (default 2000)
  • SPARK_INVOICE_POLL_LIMIT (default 100)

Payment concurrency and waiting for funds

SPARK_OPERATION_CONCURRENCY (default 8, positive integer) limits concurrent outgoing operation workflows. Incoming receipt checks have a separate pool of the same size. Different operation IDs run concurrently; duplicate requests for one ID serialize and cannot dispatch it twice. Concurrent balance refreshes share one SDK call. Long outgoing history scans continue across polls, at most two pages per lookup. Incoming discovery reads at most four pages per polling pass. Slow stream clients are disconnected when their write buffers fill.

Outgoing payments check spendable funds including quoted fees before dispatch. SPARK_FUNDS_WAIT_MS (default 20000) bounds waiting within the original POST. If funds remain unavailable, the request fails before dispatch. Nothing is queued for later sending: status GETs only query Spark and never initiate a payment. This does not limit how long incoming invoices wait to be marked available.

If a send is rejected with FEE_LIMIT_EXCEEDED, the Spark quote exceeds the max_fee_sats supplied by LNbits. The rejection message shows both amounts. In LNbits Admin Settings → Funding, adjust the minimum fee reserve (millisats) to cover the quote: 5000 millisats permits a 5-sat fee. LNbits defaults to a 2000-millisat minimum, whereas Spark's Lightning withdrawal guidance recommends a minimum 5-sat budget. Larger payments or routes may require more. The sidecar never raises the supplied limit itself. Older SparkL2 connectors ignore error_message on failed responses; the sidecar log still shows the reason.

SPARK_PAY_WAIT_MS (default 4000) controls how long a payment POST polls before returning its current status; SparkL2 can continue polling pending payments. SPARK_PAY_POLL_MS defaults to 500. Keep the POST wait below LNbits' request timeout, allowing time for SDK calls.

Mock burst tests cover hundreds of concurrent requests, bounded overlap and provider idempotency keys. They do not establish live Spark capacity: signing, operator latency, leaf distribution, and wallet liquidity still affect throughput. Use one active sidecar per Spark wallet; removing the journal does not establish support for multiple independently running SDK wallets spending the same leaves.

Powered by LNbits

LNbits is a free and open-source lightning accounts system.

Visit LNbits Shop Try myLNbits SaaS

Verification

Run make check for formatting, static checks, payment tests and localhost HTTP/SSE tests. CI runs the Node tests on Linux, Windows and macOS. Replacement tests use fresh working directories, recover status from a mock Spark service, and assert that no sidecar files were created. Native Windows EXE packaging and live Spark payments still require their own deployment tests.

About

Simple sidecar for Spark L2

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages