Skip to main content

zeus-lightning-reference

Bitcoin/Lightning/ecash domain theory AS IMPLEMENTED IN ZEUS, for readers who may know React Native but zero Lightning (or vice versa). Load when working on or reading code that touches - BOLT11 invoice decoding (utils/Bolt11Utils.ts), payments (MPP/AMP/keysend, TLV records, routing fee limits), LNURL pay/withdraw/auth/channel or lightning addresses, ZEUS Pay / Zaplocker / nostr attestations, LSP flows (Olympus Flow 2.0, jit_bolt11 wrapping, LSPS0/1/7, 0-conf channel acceptance), Cashu/ecash (CDK FFI, multimint melts, token extraction), Nostr Wallet Connect (NIP-47, service and client roles), Boltz-protocol swaps, on-chain (address types, taproot, PSBT, coin control, CPFP bump retry, mempool.space fees), the universal input router utils/handleAnything.ts and its detector ordering, CLINK/noffer, or watchtowers. Also load when confused by terms like HTLC, preimage, hodl invoice, JIT channel, melt quote, submarine swap, or keysend while reading Zeus code.

Quellinformationen

Repository
ZeusLN/zeus
Letzte Quellaktivität
8. Oktober 2026 um 05:42
Erkannte Sprache von SKILL.md
Englisch
Sterne
1.399
Forks
252

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
zeus-lightning-reference
description
Bitcoin/Lightning/ecash domain theory AS IMPLEMENTED IN ZEUS, for readers who may know React Native but zero Lightning (or vice versa). Load when working on or reading code that touches - BOLT11 invoice decoding (utils/Bolt11Utils.ts), payments (MPP/AMP/keysend, TLV records, routing fee limits), LNURL pay/withdraw/auth/channel or lightning addresses, ZEUS Pay / Zaplocker / nostr attestations, LSP flows (Olympus Flow 2.0, jit_bolt11 wrapping, LSPS0/1/7, 0-conf channel acceptance), Cashu/ecash (CDK FFI, multimint melts, token extraction), Nostr Wallet Connect (NIP-47, service and client roles), Boltz-protocol swaps, on-chain (address types, taproot, PSBT, coin control, CPFP bump retry, mempool.space fees), the universal input router utils/handleAnything.ts and its detector ordering, CLINK/noffer, or watchtowers. Also load when confused by terms like HTLC, preimage, hodl invoice, JIT channel, melt quote, submarine swap, or keysend while reading Zeus code.
# Zeus Lightning & Ecash Domain Reference Protocol knowledge tied to the exact Zeus file that implements it, plus the Zeus-specific twist on each protocol. Every claim below was verified against the code at the commit in "Provenance and maintenance". Paths are repo-relative. ## When to use / When NOT to use **Use this skill when** you need to understand WHAT a protocol does and HOW Zeus implements it: decoding invoices, payment flows, LNURL, ZEUS Pay, LSPs, Cashu, NWC, swaps, on-chain, or the input router. **Do NOT use this skill for:** | Need | Use instead | | --- | --- | | Which of the 7 backends supports feature X; per-backend RPC quirks; adding an RPC | `zeus-backends-and-capabilities` | | Settings defaults, config axes, add-a-setting checklist | `zeus-config-and-flags` | | Keychain keys, storage blob, migrations (e.g. Cashu per-node namespacing details) | `zeus-storage-and-migrations` | | Boot sequence, stores DI, dispatch design rationale | `zeus-architecture-contract` | | Symptom-driven debugging, past incident history | `zeus-debugging-playbook` / `zeus-failure-archaeology` | | PR/commit/review rules, what changes are gated | `zeus-change-control` | One rule from change control that this skill must echo because everything here is funds-touching: **send/receive/payment-handling code takes minimal diffs only — no drive-by refactoring.** See `zeus-change-control` for the full policy. ## 1. Lightning in three paragraphs (just enough to read this codebase) **Channels and HTLCs.** The Lightning Network is a payment layer on top of Bitcoin. Two nodes lock bitcoin into a shared 2-of-2 on-chain output called a **channel**; they then pay each other instantly by exchanging signed balance updates off-chain. Multi-hop payments are forwarded through chains of channels using **HTLCs** (Hashed Time-Locked Contracts): each hop's payment is locked to the SHA-256 hash of a secret (the **preimage**); revealing the preimage settles every hop atomically. A **hodl invoice** is one where the receiver deliberately delays revealing the preimage — the payment hangs "in-flight" until released (ZEUS Pay's Zaplocker scheme, section 5, is built on this). **0-conf** channels are usable before their funding transaction confirms — safe only when you trust the channel opener, which is why Zeus only auto-accepts 0-conf from its LSP (section 6). **Invoices and routing.** A **BOLT11 invoice** (a `lnbc...` bech32 string) encodes amount, payment hash, expiry, destination and a signature (section 2). **BOLT12 "offers"** (`lno1...`) are reusable payment codes; Zeus supports them on the cln-rest and ldk-node backends. Payment senders find a route themselves and pay **routing fees** to intermediate hops; wallets set a fee cap per payment (section 3). **MPP** (multi-path payments) split one payment across several routes; **AMP** (atomic multi-path) is LND's variant that also works without an invoice. **Keysend** is a spontaneous payment (no invoice) where the sender generates the preimage and ships it inside the payment's **TLV** (type-length-value) custom records. **LSPs and the rest.** An **LSP** (Lightning Service Provider) sells inbound channel capacity, including **JIT** (just-in-time) channels opened at the moment a payment arrives (fee deducted from the received amount). **LNURL** is a family of HTTP-based UX protocols (pay/withdraw/auth/channel) layered on Lightning; a **lightning address** (`user@domain.com`) is LNURL-pay behind an email-like name. **Ecash (Cashu)** is a custodial-but-blinded token system: a **mint** holds sats and issues bearer tokens (**proofs**) it cannot link to you; **melting** a token converts it back into a Lightning payment. **NWC** (Nostr Wallet Connect, NIP-47) lets apps drive a wallet remotely over the Nostr relay network. **Submarine swaps** move funds between on-chain and Lightning through a swap provider (Boltz protocol): submarine = on-chain in, Lightning out; reverse = Lightning in, on-chain out. ## 2. BOLT11 invoices — `utils/Bolt11Utils.ts` Zeus does NOT use a stock bolt11 npm decoder; it has a hand-rolled one (based on light-bolt11-decoder) with two load-bearing performance features: 1. **LRU cache** (`CACHE_LIMIT = 256`), keyed by the lowercased payment request. The Activity list decodes the same invoices repeatedly; the cache collapses that. Cache hits return **the same object instance** to every caller. 2. **Lazy self-replacing getters** for `destination`, `payeeNodeKey`, `signature`, `recoveryFlag`. Recovering the payee node key from the invoice signature is an ECDSA public-key recovery via `@noble/secp256k1` (~5–20 ms per call on a phone — the comment in the file says so). `decode()` defers it: those four fields are installed as `Object.defineProperty` getters that run recovery on first access, then replace themselves with plain values. Callers that only read `payment_hash` / `description` / `expiry` / `timestamp` pay zero crypto cost. If the invoice carries an explicit payee tag (tag 19), `destination`/`payeeNodeKey` are set eagerly from it and the getters are skipped. **Consequences — the rules:** - **Never naively serialize or spread a decoded invoice** (`JSON.stringify(decoded)`, `{...decoded}`). The lazy getters are enumerable, so both operations force the expensive secp256k1 recovery immediately — for every cached invoice you touch — defeating the entire design. Read individual fields instead. - **Never mutate the returned object.** It is shared cache state; your mutation poisons every future `decode()` of that invoice. - `decode()` throws on non-`ln` strings — callers gate with `AddressUtils.isValidLightningPaymentRequest` first (see `utils/handleAnything.ts`). - Amount fields: `satoshis` (number|null), `millisatoshis` (string|null), plus Zeus-convention `num_satoshis` / `num_msat` (strings, `'0'` when absent). The interface is `DecodedBolt11` in the same file. Tests: `utils/Bolt11Utils.test.ts`. ## 3. Paying: MPP, AMP, keysend, fee limits Payment dispatch lives in `stores/TransactionsStore.ts` (`sendPayment` → `sendPaymentInternal`); the send UI is `views/PaymentRequest.tsx`. ### MPP vs AMP and their version gates - `supportsMPP()` and `supportsAMP()` are per-backend capability flags (see `zeus-backends-and-capabilities` for the doctrine). On the LND family (`backends/LND.ts`, `backends/EmbeddedLND.ts`, `backends/LightningNodeConnect.ts`) they are node-version-gated: MPP requires LND ≥ v0.10.0, AMP requires LND ≥ v0.13.0. `backends/LdkNode.ts`: MPP true, AMP false. CLNRest, LndHub, NostrWalletConnect: both false. - AMP is also **forced on** when the invoice's feature bit 30 is `is_required` (`views/PaymentRequest.tsx`, `lockAtomicMultiPathPayment`). Enabling AMP sets `no_inflight_updates = true`. Tor also sets `no_inflight_updates = true` ("Tor can't handle streaming updates"). ### The `max_parts` quirk (verify before relying on defaults) - `TransactionsStore.sendPaymentInternal`: `data.max_parts = max_parts ? max_parts : '1'` — the **store default is `'1'`** (single path) for any caller that doesn't pass it. - `views/PaymentRequest.tsx` `triggerPayment`: `max_parts: enableMultiPathPayment ? maxParts : '16'` with state default `maxParts: '16'` — the **main send screen always passes `'16'`**, whether or not the MPP toggle is on. So payments from PaymentRequest allow 16 shards; payments from other call sites (e.g. programmatic sends) allow 1. - `sendPaymentSilently` has `if (max_parts) { data.max_parts = max_parts || '16'; }` — the `|| '16'` branch is unreachable (dead fallback); if the caller omits `max_parts` the field is simply not set. ### Keysend Constants at the top of `stores/TransactionsStore.ts`: | TLV record type | Content | Encoding in Zeus | | --- | --- | --- | | `5482373484` | payment preimage (sender-generated) | random bytes → base64 (`preimage.toString('base64')`) | | `34349334` | optional text message | UTF-8 → hex → base64 (`Base64Utils.hexToBase64(Base64Utils.utf8ToHex(message))`) | Plus: `data.dest = Base64Utils.hexToBase64(pubkey)` and `data.last_hop_pubkey = Base64Utils.hexToBase64(...)` — **LND REST wants raw bytes base64-encoded, not hex strings**; forgetting the conversion is a classic bug. `payment_hash` is `hexToBase64(sha256(preimage))`. Function selection is one of the few sanctioned `implementation` branches (see `zeus-architecture-contract`): `cln-rest` / `embedded-lnd` / `ldk-node` with a pubkey go through `BackendUtils.sendKeysend`; everything else — including remote LND REST, which has no dedicated keysend endpoint — goes through `BackendUtils.payLightningInvoice` carrying `dest_custom_records`. ### Routing fee conventions — `utils/FeeUtils.ts` `calculateDefaultRoutingFee(amount)`: - amount > 1000 sats → fee cap = 5% (`DEFAULT_ROUTING_FEE_PERCENT = 0.05`), rounded. - amount ≤ 1000 sats → fee cap = the amount itself (i.e. 100%; tiny payments routinely need proportionally huge fees). `views/PaymentRequest.tsx` passes `max_fee_percent` (default `'5.0'`) for cln-rest, which takes a percent instead of a sat limit; the store only forwards `max_fee_percent` when `implementation === 'cln-rest'`. ## 4. LNURL family and lightning addresses Zeus uses the `js-lnurl` package (`getParams`, `findlnurl`, `decodelnurl` — imported in `utils/handleAnything.ts`; version pinned in `package.json`). LNURL flows are routed by the input router (section 11) to one of four screens based on `params.tag`: | tag | Meaning | Zeus screen | | --- | --- | --- | | `payRequest` (LNURL-pay / LUD-06) | server gives min/max, you request an invoice from its callback | `views/LnurlPay/` (or `ChoosePaymentMethod` when ecash is enabled) | | `withdrawRequest` (LNURL-withdraw) | server offers sats; you submit YOUR invoice to its callback | `views/Receive.tsx` with `lnurlParams` | | `channelRequest` (LNURL-channel) | server opens a channel to you | `views/LnurlChannel.tsx` | | `login` (LNURL-auth) | key-based login challenge | `views/LnurlAuth.tsx`, gated by `supportsLnurlAuth()` | **Lightning address (LUD-16)** resolution is inline in `handleAnything.ts`: split `user@domain`, fetch `https://<domain>/.well-known/lnurlp/<user>` (plain `http://` for `.onion`). Per LUD-16 the domain is always lowercased and the username normally is too — **except for `cryptoqr.net` addresses**, whose usernames are URL-encoded merchant payloads where hex-digit casing matters server-side (`isCryptoQR` check; covered by `utils/handleAnything.test.ts`). Before the LNURL-pay lookup, Zeus also tries a **BOLT12 DNS lookup** (BIP-353 style: TXT record at `<user>.user._bitcoin-payment.<domain>` via `cloudflare-dns.com/dns-query`) and offers a payment-method choice if both exist. **Onion gap:** direct `.onion` lightning-address lookups go through `doTorRequest` when Tor is enabled, but generic LNURL parameter fetches via `js-lnurl`'s `getParams` do NOT route over Zeus's internal Tor — there is a literal `// TODO handle fetching of params with internal Tor` in `handleAnything.ts`; `.onion` LNURL endpoints currently error out of that path. Open gap, not a bug you introduced. Nested URI schemes `lnurlp://` / `lnurlw://` / `lnurlc://` / `lnurlauth://` are rewritten to `https://` (or `http://` for `.onion`) before processing. ## 5. ZEUS Pay / Zaplocker — `stores/LightningAddressStore.ts` + `stores/LnurlPayStore.ts` ZEUS Pay gives a self-custodial wallet a lightning address (`user@zeuspay.com`) even though the phone is usually offline. Server: `LNURL_HOST = 'https://zeuspay.com'`. Three address types exist (`address_type` sent to the server): `'zaplocker'` (hodl-invoice scheme, below), `'cashu'` (payments held as Cashu mint quotes, redeemed via `/api/lnurl/nuts/redeem`), and `'nwc'`. Gate: `supportsLightningAddress()` in `utils/BackendUtils.ts` is the composite `supportsCustomPreimages() || supportsCashuWallet()`. **Zaplocker lifecycle (receiver side):** 1. **Pre-generate preimages**: `generatePreimages` creates **250 preimages per batch** (32-byte entropy each), computes `hash = sha256(preimage)`, schnorr-signs each hash with the user's nostr private key, and POSTs `{pubkey, hashes, nostrSignatures, ...}` to `zeuspay.com/api/lnurl/submitHashes`. Preimages stay ONLY on the phone (storage key `zeuspay-lightning-address-hashes`). Auto-replenishes when the server reports fewer than 50 unused hashes. 2. **Someone pays**: zeuspay.com issues a hodl invoice against one of your hashes and holds the HTLC. It cannot settle — it doesn't know the preimage. 3. **Redeem** (`lookupPreimageAndRedeemZaplocker`): the app looks up the preimage for the hash, creates a local invoice with that **fixed preimage** (`BackendUtils.createInvoice({ preimage, expiry: '86400', ... })`), and POSTs it to `/api/lnurl/redeem`; the server pays it, which reveals the preimage and settles the held HTLC. This is why the gate is `supportsCustomPreimages` — the backend must accept caller-chosen preimages. **Anti-fraud attestations (nostr kind 55869):** - **Payer side** (`LnurlPayStore.broadcastAttestation`, triggered from `views/PaymentRequest.tsx` when `isZaplocker`): publish a kind-`55869` event, signed by an **ephemeral key**, whose `p` tag is `getPublicKey(paymentHash)` — the payment hash itself is used as a nostr secret key so anyone holding the hash can find attestations — and whose content is the bolt11 invoice being paid. - **Receiver side** (`LightningAddressStore.lookupAttestations`): for each hash, query relays for kind 55869 with `#p = getPublicKey(hash)`, then `analyzeAttestation` checks the embedded invoice actually commits to that hash and amount. Exactly one valid attestation → success status; **more than one attestation → status `'error'`** (a fraud signal: two different "payers" claiming the same hash means someone is lying). - The payer also verifies the receiver's zaplocker setup: `LnurlPayStore.load` checks schnorr signatures over the payment hash (`isPmtHashSigValid`) and the relay list (`isRelaysSigValid`) against the receiver's advertised nostr pubkey. ## 6. LSP integration — three generations, all in `stores/LSPStore.ts` Default endpoints (Olympus, per network) are exported from `stores/SettingsStore.ts` (`DEFAULT_LSP_MAINNET = 'https://0conf.lnolymp.us'`, LSPS1 REST `https://lsps1.lnolymp.us`, etc.); resolution via `getLspConfig` — endpoint/default details belong to `zeus-config-and-flags`. ### Generation 1: Olympus "Flow 2.0" REST (JIT channels on receive) - `getZeroConfFee` POSTs the msat amount to `<flowHost>/api/v1/fee`; `getZeroConfInvoice(bolt11)` POSTs to `/api/v1/proposal` and resolves `data.jit_bolt11` — a **wrapped invoice** routing through the LSP, which opens a 0-conf channel just in time and deducts its fee. - **The wrapping MUTATES the requested amount** (`stores/InvoicesStore.ts`, `createInvoice`): when Flow LSP is active and `value > zeroConfFee`, Zeus creates the local invoice for `req.value = requested − zeroConfFee`, then swaps the displayed payment request for `jit_bolt11` (which asks the payer for the full amount). The payer sees the full amount; your node's invoice is smaller by the fee. - **Silent degradation**: if `getZeroConfInvoice` rejects, Zeus clears the error state and shows the UNWRAPPED invoice — receiving still works if a channel already exists, but no JIT channel will be opened. Don't "fix" the swallowed error without understanding this. - **LNURL-withdraw interaction**: the withdraw callback must receive the wrapped invoice — `qs.pr = jit_bolt11 || invoice.getPaymentRequest` in `InvoicesStore`. Passing the unwrapped one would make the withdrawing service pay an invoice the LSP never sees. ### Generation 2/3: LSPS0 transport + LSPS1 (buy channel) / LSPS7 (extend lease)
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen