beignet 0.7.5 → 0.7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,205 +1,208 @@
1
1
  # Beignet
2
2
 
3
- A self-custodial Bitcoin wallet library for JavaScript/TypeScript, with a full Lightning Network implementation.
3
+ A self-custodial Bitcoin wallet library for JavaScript/TypeScript with a **full Lightning Network implementation**. Beignet implements the Lightning protocol and channel state machine in TypeScript rather than wrapping LND, CLN or LDK: it speaks BOLT 8 over a real TCP socket and runs its own BOLT 2 state machine.
4
4
 
5
- ## Overview
5
+ Two layers, one mnemonic:
6
6
 
7
- Beignet provides two layers of Bitcoin wallet functionality:
7
+ - **On-chain wallet:** HD keys, address generation, UTXO tracking, transaction building, PSBT/hardware signing, multisig, watch-only, Electrum connectivity.
8
+ - **Lightning:** channel lifecycle, onion-routed payments, BOLT 11 invoices, BOLT 12 offers, gossip and pathfinding, anchors, splicing, taproot channels, watchtower client. Interop-tested against LND, Core Lightning and Eclair on regtest.
8
9
 
9
- - **On-chain wallet** — HD key management, address generation, UTXO tracking, transaction building, and Electrum server connectivity.
10
- - **Lightning Network** — A complete BOLT-compliant Lightning implementation in TypeScript, supporting channel management, onion-routed payments, BOLT 11 invoices, gossip-based routing, and real TCP transport. Tested against LND, CLN, and Eclair on regtest.
10
+ Requires **Node.js 18+**. MIT licensed.
11
11
 
12
- ## Table of Contents
12
+ **Jump to:** [Install](#install) · [Examples](#try-the-examples) · [On-chain wallet](#on-chain-wallet) · [Lightning](#lightning) · [Daemon & CLI](#http-daemon--cli) · [Protocol layer](#protocol-layer-advanced) · [Tests](#tests) · [Status & limitations](#status--limitations)
13
13
 
14
- 1. [Getting Started](#getting-started)
15
- 2. [On-Chain Wallet](#on-chain-wallet)
16
- - [Leveled Logging](#leveled-logging)
17
- 3. [Lightning Network](#lightning-network)
18
- - [Lightning Quick Start (BeignetNode)](#lightning-quick-start-beignetnode)
19
- - [Decision-Support APIs](#decision-support-apis)
20
- - [HTTP Daemon](#http-daemon)
21
- - [Advanced API (LightningNode)](#advanced-api-lightningnode)
22
- - [Architecture](#architecture)
23
- - [BOLT Coverage](#bolt-coverage)
24
- - [Module Reference](#module-reference)
25
- 4. [Running Tests](#running-tests)
26
- 5. [Interop Testing](#interop-testing)
27
- 6. [React Native](#react-native)
28
- 7. [Documentation](#documentation)
29
- 8. [Support](#support)
30
-
31
- ## Getting Started
14
+ ## Install
32
15
 
33
16
  ```bash
34
- # Using npm
35
- npm install beignet
17
+ npm install beignet # or: yarn add beignet
18
+ ```
19
+
20
+ Smallest thing that works. `net` and `tls` are injected so the same code runs on Node and React Native:
36
21
 
37
- # Using Yarn
38
- yarn add beignet
22
+ ```typescript
23
+ import net from 'net';
24
+ import tls from 'tls';
25
+ import { Wallet, generateMnemonic } from 'beignet';
26
+
27
+ const result = await Wallet.create({
28
+ mnemonic: generateMnemonic(),
29
+ electrumOptions: { net, tls }
30
+ });
31
+ if (result.isErr()) throw result.error;
32
+ const wallet = result.value;
33
+
34
+ console.log(await wallet.getAddress());
35
+ console.log(wallet.getBalance());
39
36
  ```
40
37
 
41
- > Requires **Node.js 18+**
38
+ From here: [the on-chain wallet](#on-chain-wallet) for sending, PSBTs, multisig and watch-only, or [Lightning](#lightning) for channels and payments.
42
39
 
43
- ### Build from source
40
+ ## Try the examples
41
+
42
+ The fastest way to understand the whole system is to run the two REPL examples against a live wallet and a live node. Both are checked-in TypeScript you can read and edit.
44
43
 
45
44
  ```bash
46
45
  git clone git@github.com:coreyphillips/beignet.git && cd beignet
47
- npm install && npm run build
46
+ npm install
48
47
  ```
49
48
 
50
- ### Run the examples
51
-
52
- Both examples launch an interactive REPL with a live wallet/node instance:
49
+ ### 1. On-chain wallet REPL
53
50
 
54
51
  ```bash
55
- # On-chain wallet REPL
56
52
  npm run example
53
+ ```
57
54
 
58
- # Lightning node REPL (recommended — uses BeignetNode)
59
- npm run example:lightning
55
+ Creates a mainnet wallet (a fresh mnemonic unless you pass one), syncs it against a public Electrum server, prints the balance and a receive address, then drops you at a `>` prompt with the wallet bound to `wallet`. Type `help()` for the command list.
56
+
57
+ ```js
58
+ > wallet.getBalance()
59
+ > await wallet.getAddress()
60
+ > await wallet.refreshWallet()
61
+ > await wallet.send({ address: 'bc1q...', amount: 10000, satsPerByte: 2 })
62
+ ```
60
63
 
61
- # Low-level Lightning node REPL (uses LightningNode directly)
62
- npm run example:lightning -- --low-level
64
+ State persists as JSON under `example/walletData/`. Pass a mnemonic as the first argument to reuse a wallet: `npm run example -- "abandon abandon ... about"`.
63
65
 
64
- # Lightning node with a specific mnemonic and alias
65
- npm run example:lightning -- abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about --alias mynode
66
+ ### 2. Lightning node REPL
66
67
 
67
- # Two-node payment flow demo (shows complete lifecycle)
68
- npm run example:lightning -- --payment-flow
68
+ ```bash
69
+ npm run example:lightning
69
70
  ```
70
71
 
71
- ## On-Chain Wallet
72
+ Boots a real Lightning node (`BeignetNode`) with an auto-created wallet, storage and funding provider, waits for it to become operational, prints info/balance/health, and drops you at a `beignet>` prompt with the node bound to `node`. Type `help()` for the command list. Top-level `await` works.
72
73
 
73
- ```javascript
74
- import { Wallet, generateMnemonic } from 'beignet';
74
+ ```js
75
+ beignet> await node.getNewAddress() // fund this on-chain, then:
76
+ beignet> await node.connectAndOpenChannel(pubkey, host, port, 200000)
77
+ beignet> node.createInvoice(1000, 'coffee').bolt11
78
+ beignet> await node.payInvoice('lnbc...')
79
+ beignet> node.getLiquiditySnapshot()
80
+ ```
75
81
 
76
- const mnemonic = generateMnemonic();
82
+ The flags you will actually reach for (everything after `--`):
77
83
 
78
- const createRes = await Wallet.create({ mnemonic });
79
- if (createRes.isErr()) return;
80
- const wallet = createRes.value;
84
+ | Flag | Effect |
85
+ |------|--------|
86
+ | `mainnet` \| `testnet` \| `regtest` | Network, as a bare positional arg (default `mainnet`) |
87
+ | `<12 or 24 words>` | Reuse a mnemonic, as bare positional args (default generates one) |
88
+ | `--electrum-host <h>` `--electrum-port <p>` | Point at your own Electrum server |
89
+ | `--alias <name>` | Node alias in `node_announcement` |
81
90
 
82
- // Get receiving address
83
- const address = await wallet.getAddress();
91
+ ```bash
92
+ # named regtest node against a local Electrum server
93
+ npm run example:lightning -- regtest --electrum-host 127.0.0.1 --electrum-port 60001 --alias mynode
94
+ ```
84
95
 
85
- // Get wallet balance
86
- const balance = wallet.getBalance();
96
+ Tor, full-graph gossip, the low-level `LightningNode` variant and the non-interactive payment-API walkthrough have flags too: see [the flag reference](example/REPL_TESTING.md#repl-flags).
87
97
 
88
- // Send sats
89
- const sendRes = await wallet.send({
90
- address: 'bc1q...',
91
- amount: 50000,
92
- satPerByte: 2,
93
- });
98
+ Node state lives in a SQLite DB under `~/.beignet/data/<hash-of-mnemonic>/` (the `--low-level` example uses `example/lightningData/node.db`).
94
99
 
95
- // Refresh wallet state from Electrum
96
- await wallet.refreshWallet();
97
- ```
100
+ Both examples run straight off the TypeScript sources through `ts-node`, so no build step is needed. Use `npm run build` when you want the compiled `dist/`.
101
+
102
+ **→ [example/REPL_TESTING.md](example/REPL_TESTING.md) is a copy-pasteable walkthrough** of the whole lifecycle in the REPL: funding, peers, channels, invoices, payments, keysend, offers, splicing, closing, backup.
98
103
 
99
- ### Advanced On-Chain Usage
104
+ ## Which entry point?
105
+
106
+ | Import | Contains | Use when |
107
+ |--------|----------|----------|
108
+ | `beignet` | `Wallet`, `generateMnemonic`, types | You want the on-chain wallet |
109
+ | `beignet/cli` | `BeignetNode`, `startDaemon`, error helpers | **You want Lightning.** Sats-denominated, string IDs, structured errors |
110
+ | `beignet/lightning` | Namespaced protocol modules (`node`, `channel`, `onion`, ...) | You need the raw BOLT layer: bigint msat, Buffer IDs, wire messages |
111
+
112
+ ## On-chain wallet
100
113
 
101
114
  ```typescript
102
- import { Wallet, generateMnemonic } from 'beignet';
103
115
  import net from 'net';
104
116
  import tls from 'tls';
117
+ import { Wallet, generateMnemonic } from 'beignet';
105
118
 
106
- const wallet = await Wallet.create({
119
+ const res = await Wallet.create({
107
120
  mnemonic: generateMnemonic(),
108
- passphrase: 'optional-passphrase',
109
- electrumOptions: {
110
- servers: { host: '127.0.0.1', ssl: 50002, tcp: 50001, protocol: 'ssl' },
111
- net,
112
- tls,
113
- },
114
- network: 'mainnet',
115
- addressType: 'p2wpkh',
116
- coinSelectPreference: 'consolidate',
121
+ electrumOptions: { net, tls } // required: inject the socket implementations
117
122
  });
123
+ if (res.isErr()) throw res.error;
124
+ const wallet = res.value;
118
125
 
119
- // Send to multiple outputs
120
- await wallet.value.sendMany({
121
- txs: [
122
- { address: 'addr1', amount: 1000 },
123
- { address: 'addr2', amount: 2000 },
124
- ],
125
- });
126
+ const address = await wallet.getAddress();
127
+ const balance = wallet.getBalance();
126
128
 
127
- // Sweep a private key
128
- await wallet.value.sweepPrivateKey({
129
- privateKey: 'L...',
130
- toAddress: 'bc1q...',
131
- satsPerByte: 5,
132
- });
129
+ await wallet.send({ address: 'bc1q...', amount: 50_000, satsPerByte: 2 });
130
+ await wallet.sendMany({ txs: [{ address: 'bc1q...', amount: 1000 }] });
131
+ await wallet.refreshWallet();
133
132
 
134
- // List UTXOs
135
- const utxos = wallet.value.listUtxos();
133
+ const utxos = wallet.listUtxos();
134
+ const history = await wallet.getAddressHistory('bc1q...');
135
+ ```
136
+
137
+ Every fallible call returns a `Result<T>`: check `isErr()` before reading `.value`. Amounts are always satoshis.
138
+
139
+ Options worth knowing on `Wallet.create`: `network` (`EAvailableNetworks.mainnet` | `testnet` | `regtest` | `signet`), `addressType` (`p2wpkh` default, `p2sh-p2wpkh`, `p2pkh`, `p2tr`), `passphrase`, `account`, `storage`, `logger`, `coinSelectPreference`, `feeEstimationSource`, `gapLimitOptions`.
140
+
141
+ <details>
142
+ <summary><b>Custom Electrum servers, failover, fee sources, BIP21</b></summary>
136
143
 
137
- // Get transaction history
138
- const history = await wallet.value.getAddressHistory('bc1q...');
144
+ ```typescript
145
+ import { EAvailableNetworks, EProtocol, Wallet } from 'beignet';
146
+
147
+ const res = await Wallet.create({
148
+ mnemonic,
149
+ network: EAvailableNetworks.mainnet,
150
+ feeEstimationSource: 'electrum', // 'electrum' | 'http' | 'auto' (default)
151
+ electrumOptions: {
152
+ net,
153
+ tls,
154
+ servers: [
155
+ { host: 'bitcoin.lu.ke', ssl: 50002, tcp: 50001, protocol: EProtocol.ssl },
156
+ { host: 'mempool.space', ssl: 60602, tcp: 60601, protocol: EProtocol.ssl }
157
+ ]
158
+ }
159
+ });
139
160
  ```
140
161
 
141
- ### Watch-Only Wallets
162
+ - **Failover:** with multiple servers the wallet rotates through them in order on connect/reconnect failure, then through hardcoded fallback peers for the network, with a per-server cooldown so dead servers are not hammered. Inspect `wallet.electrum.currentServer` and `wallet.electrum.rotationCount`.
163
+ - **Fee source:** `'electrum'` queries only the connected server via `blockchain.estimatefee`, so fee lookups never leak to mempool.space/blocktank over clearnet. `'auto'` prefers Electrum and falls back to HTTP. All remote rates are clamped to 5000 sat/vB.
164
+ - **Networks:** mainnet, testnet, regtest and signet work end to end (wallet, Electrum, CLI/daemon `--network signet`, Lightning chain hash and `tbs` invoice prefix). Signet shares testnet address formats and coin type 1.
165
+ - **BIP21:** `encodeBip21({ address, amountSats?, label?, message? })` builds a `bitcoin:` URI.
142
166
 
143
- A watch-only wallet is constructed from an account-level extended public key
144
- (xpub/ypub/zpub for mainnet, tpub/upub/vpub for testnet/regtest) instead of a
145
- mnemonic. The key is assumed to sit at the account level
146
- (m/purpose'/coin'/account', e.g. m/84'/0'/0' for p2wpkh), so receive and
147
- change addresses derive publicly as xpub/0/i and xpub/1/i. SLIP-132 version
148
- bytes are normalized automatically: a zpub/vpub implies p2wpkh and a
149
- ypub/upub implies p2sh-p2wpkh; a plain xpub/tpub uses the `addressType`
150
- option (default p2wpkh). Because one account xpub yields exactly one address
151
- type, a watch-only wallet monitors only that type.
167
+ </details>
152
168
 
153
- Everything read-only works: address generation, gap-limit scanning, Electrum
154
- refresh, balances, transaction history, UTXOs, fee estimates and address
155
- subscriptions. Anything that requires private keys
156
- (send/sendMax/sendMany/sweepPrivateKey/getPrivateKey and the internal signing
157
- paths) fails with the typed `WatchOnlySigningError`
158
- (`code: 'WATCH_ONLY_CANNOT_SIGN'`, message `watch-only wallet cannot sign`).
169
+ <details>
170
+ <summary><b>Watch-only wallets (account xpub)</b></summary>
159
171
 
160
- Watch-only is a library feature only for now: the HTTP daemon always runs
161
- with a mnemonic.
172
+ Built from an account-level extended public key instead of a mnemonic. The key is assumed to sit at `m/purpose'/coin'/account'` (e.g. `m/84'/0'/0'`), so addresses derive as `xpub/0/i` and `xpub/1/i`. SLIP-132 version bytes are normalized: `zpub`/`vpub` implies p2wpkh, `ypub`/`upub` implies p2sh-p2wpkh, a plain `xpub`/`tpub` uses `addressType` (default p2wpkh). One account xpub yields exactly one address type, so a watch-only wallet monitors only that type.
162
173
 
163
174
  ```typescript
164
- import { Wallet } from 'beignet';
165
-
166
175
  const res = await Wallet.createWatchOnly({
167
176
  xpub: 'zpub6r...',
168
- network: 'bitcoin',
169
- electrumOptions: { net, tls },
177
+ network: EAvailableNetworks.mainnet,
178
+ electrumOptions: { net, tls }
170
179
  });
171
180
  if (res.isErr()) return;
172
181
  const watchOnly = res.value;
173
182
 
174
- const address = await watchOnly.getAddress(); // works
175
- const balance = watchOnly.getBalance(); // works
176
- const sendRes = await watchOnly.send({ address: 'bc1q...', amount: 1000 });
177
- // sendRes.isErr() === true; sendRes.error.message === 'watch-only wallet cannot sign'
183
+ await watchOnly.getAddress(); // works
184
+ watchOnly.getBalance(); // works
185
+
186
+ const send = await watchOnly.send({ address: 'bc1q...', amount: 1000 });
187
+ // send.isErr() === true, message: 'watch-only wallet cannot sign'
178
188
  ```
179
189
 
180
- ### External Signer (Hardware Wallet) PSBT Flow
190
+ The full read-only surface works: address generation, gap-limit scanning, Electrum refresh, balances, history, UTXOs, fee estimates, address subscriptions. Anything needing private keys (`send`/`sendMax`/`sendMany`/`sweepPrivateKey`/`getPrivateKey`) fails with the typed `WatchOnlySigningError` (`code: 'WATCH_ONLY_CANNOT_SIGN'`). Library-only for now: the HTTP daemon always runs with a mnemonic.
191
+
192
+ </details>
193
+
194
+ <details>
195
+ <summary><b>Hardware wallets and external signers (PSBT)</b></summary>
181
196
 
182
- `buildPsbt` runs the normal transaction setup (coin selection, change, fee)
183
- but stops before signing and returns a base64 PSBT populated with everything
184
- a hardware signer needs: `witnessUtxo` (or `nonWitnessUtxo` for legacy
185
- p2pkh), `redeemScript` for p2sh-p2wpkh, `tapInternalKey` plus
186
- `tapBip32Derivation` for p2tr, and `bip32Derivation` (fingerprint + path +
187
- pubkey) on every wallet input. It works on both full and watch-only wallets.
188
- Note for watch-only wallets: the true master fingerprint is unknowable from
189
- an account xpub, so the xpub's parent fingerprint is used; signers should
190
- locate keys by derivation path.
197
+ `buildPsbt` runs the normal setup (coin selection, change, fee) but stops before signing, returning a base64 PSBT populated with what a hardware signer needs: `witnessUtxo` (or `nonWitnessUtxo` for legacy p2pkh), `redeemScript` for p2sh-p2wpkh, `tapInternalKey` plus `tapBip32Derivation` for p2tr, and `bip32Derivation` on every wallet input. Works on full and watch-only wallets.
191
198
 
192
199
  ```typescript
193
- // 1. Build (works on a watch-only wallet)
194
- const build = await wallet.buildPsbt({
195
- address: 'bc1q...',
196
- amount: 50000,
197
- satsPerByte: 4,
198
- });
200
+ // 1. Build (never touches private keys)
201
+ const build = await wallet.buildPsbt({ address: 'bc1q...', amount: 50_000, satsPerByte: 4 });
199
202
  if (build.isErr()) return;
200
203
  const { psbtBase64, fee, vsizeEstimate } = build.value;
201
204
 
202
- // 2. Sign externally (hardware wallet / HWI / another machine)
205
+ // 2. Sign externally (hardware wallet, HWI, another machine)
203
206
  const signedBase64 = await myHardwareWallet.signPsbt(psbtBase64);
204
207
 
205
208
  // 3. Import: validates a signature on EVERY input, finalizes, does NOT broadcast
@@ -210,211 +213,157 @@ const { txHex, txid } = imported.value;
210
213
  // 4. Broadcast when ready
211
214
  await wallet.broadcastTransaction(txHex);
212
215
 
213
- // Multi-party signing: merge partially signed copies of the same PSBT
216
+ // Multi-party: merge partially signed copies of the same PSBT
214
217
  const combined = wallet.combinePsbts([copyA, copyB]);
215
218
  ```
216
219
 
217
- The HTTP daemon exposes the same flow on its (mnemonic-backed) wallet via
218
- `POST /psbt/build`, `POST /psbt/import-signed` and `POST /psbt/combine`, and
219
- the CLI via `beignet psbt build|import-signed|combine`.
220
-
221
- ### Multisig P2WSH Wallets (sortedmulti)
222
-
223
- `Wallet.createMultisig` creates a descriptor-based sorted-multisig wallet:
224
- `wsh(sortedmulti(threshold, key1, key2, ...))`, the interoperable standard
225
- used by Bitcoin Core, Sparrow and Specter. Derivation follows BIP 48 with
226
- script type 2 (`m/48'/coin'/account'/2'`, receive `/0/*`, change `/1/*`) and
227
- public keys are ordered per BIP 67 at every index, so any wallet built from
228
- the same account xpubs produces identical addresses regardless of the order
229
- the cosigners were listed in.
230
-
231
- Cosigners are supplied as account-level extended public keys (`xpub`/`tpub`,
232
- or the SLIP-132 multisig encodings `Zpub`/`Vpub`, normalized automatically).
233
- When a mnemonic is provided, this wallet IS one of the cosigners: its BIP 48
234
- account xpub is derived and included automatically (pass `ourXpub` to assert
235
- it explicitly; a mismatch is rejected). Omit the mnemonic for a watch-only
236
- multisig coordinator: the full read-only surface (scanning, balances,
237
- history, subscriptions) works, signing does not.
238
-
239
- Spending is PSBT-only. Direct spends (`send`/`sendMany`/`sendMax`) fail with
240
- the typed `MultisigSpendError` (`code: 'MULTISIG_REQUIRES_PSBT'`). `buildPsbt`
241
- attaches the `witnessScript` and one `bip32Derivation` entry per cosigner to
242
- every input; `signPsbtWithOurKey` adds this cosigner's partial signature
243
- without finalizing; `importSignedPsbt` counts the VALID partial signatures on
244
- each input against the witnessScript threshold and refuses to finalize below
245
- it (the error names how many signatures it has and needs).
246
- `exportDescriptors()` emits the checksummed `wsh(sortedmulti(...))` receive
247
- and change descriptors for import into Bitcoin Core/Sparrow/Specter; our key
248
- carries its full key origin, cosigners known only as xpubs carry a
249
- fingerprint-only origin. Multisig is a library-only feature for now: the
250
- HTTP daemon wallet stays single-sig.
251
-
252
- Full 2-of-3 walkthrough:
220
+ For watch-only wallets the true master fingerprint is unknowable from an account xpub, so the xpub's parent fingerprint is used: signers should locate keys by derivation path.
221
+
222
+ Also on the daemon (`POST /psbt/build`, `/psbt/import-signed`, `/psbt/combine`) and the CLI (`beignet psbt build|import-signed|combine`).
223
+
224
+ </details>
225
+
226
+ <details>
227
+ <summary><b>Multisig (P2WSH sortedmulti)</b></summary>
228
+
229
+ `Wallet.createMultisig` creates a descriptor-based sorted-multisig wallet, `wsh(sortedmulti(threshold, key1, key2, ...))`: the interoperable standard used by Bitcoin Core, Sparrow and Specter. Derivation follows BIP 48 script type 2 (`m/48'/coin'/account'/2'`, receive `/0/*`, change `/1/*`) and keys are BIP 67 ordered at every index, so any wallet built from the same account xpubs produces identical addresses regardless of cosigner order.
230
+
231
+ Cosigners are account-level extended public keys (`xpub`/`tpub`, or SLIP-132 `Zpub`/`Vpub`, normalized automatically). With a mnemonic, this wallet IS one of the cosigners: its BIP 48 account xpub is derived and included automatically (pass `ourXpub` to assert it; a mismatch is rejected). Omit the mnemonic for a watch-only coordinator.
232
+
233
+ Spending is PSBT-only. `send`/`sendMany`/`sendMax` fail with `MultisigSpendError` (`code: 'MULTISIG_REQUIRES_PSBT'`).
253
234
 
254
235
  ```typescript
255
- import { Wallet } from 'beignet';
256
-
257
- // Each cosigner shares their BIP 48 account xpub (m/48'/0'/0'/2' on mainnet).
258
- // Grab ours from exportDescriptors() or derive it with any BIP 48 tool.
259
-
260
- // 1. Cosigner A creates their multisig wallet (A holds mnemonicA).
261
- const walletA = (
262
- await Wallet.createMultisig({
263
- threshold: 2,
264
- mnemonic: mnemonicA, // we are one cosigner; our xpub is added automatically
265
- cosigners: [xpubB, xpubC],
266
- network: 'bitcoin',
267
- electrumOptions: { net, tls },
268
- })
269
- ).value;
270
-
271
- // Cosigner B does the same in their own instance/machine.
272
- const walletB = (
273
- await Wallet.createMultisig({
274
- threshold: 2,
275
- mnemonic: mnemonicB,
276
- cosigners: [xpubA, xpubC],
277
- network: 'bitcoin',
278
- electrumOptions: { net, tls },
279
- })
280
- ).value;
236
+ // 1. Each cosigner builds the same quorum from the others' BIP 48 account xpubs.
237
+ const a = await Wallet.createMultisig({
238
+ threshold: 2,
239
+ mnemonic: mnemonicA, // we are one cosigner; our xpub is added automatically
240
+ cosigners: [xpubB, xpubC],
241
+ network: EAvailableNetworks.mainnet,
242
+ electrumOptions: { net, tls }
243
+ });
244
+ const b = await Wallet.createMultisig({ threshold: 2, mnemonic: mnemonicB, cosigners: [xpubA, xpubC], /* ... */ });
281
245
 
282
246
  // An optional watch-only coordinator holds no keys at all.
283
- const coordinator = (
284
- await Wallet.createMultisig({
285
- threshold: 2,
286
- cosigners: [xpubA, xpubB, xpubC],
287
- network: 'bitcoin',
288
- electrumOptions: { net, tls },
289
- })
290
- ).value;
291
-
292
- // 2. Fund the multisig: every instance derives the same addresses.
293
- const deposit = await walletA.getAddress(); // == walletB/coordinator address
294
-
295
- // 3. Build the unsigned PSBT (works on any instance, coordinator included).
296
- const built = await walletA.buildPsbt({
297
- address: 'bc1q...',
298
- amount: 50000,
299
- satsPerByte: 4,
300
- });
247
+ const c = await Wallet.createMultisig({ threshold: 2, cosigners: [xpubA, xpubB, xpubC], /* ... */ });
248
+
249
+ if (a.isErr() || b.isErr() || c.isErr()) return;
250
+ const [walletA, walletB, coordinator] = [a.value, b.value, c.value];
251
+
252
+ // 2. Fund it: every instance derives the same addresses.
253
+ const deposit = await walletA.getAddress();
254
+
255
+ // 3. Build the unsigned PSBT (any instance, coordinator included).
256
+ const built = await walletA.buildPsbt({ address: 'bc1q...', amount: 50_000, satsPerByte: 4 });
257
+ if (built.isErr()) return;
301
258
  const unsigned = built.value.psbtBase64;
302
259
 
303
- // 4. Each cosigner signs their own copy (below threshold nothing finalizes).
304
- const signedA = walletA.signPsbtWithOurKey(unsigned).value;
305
- const signedB = walletB.signPsbtWithOurKey(unsigned).value;
260
+ // 4. Each cosigner signs their own copy (nothing finalizes below threshold).
261
+ const signedA = walletA.signPsbtWithOurKey(unsigned);
262
+ const signedB = walletB.signPsbtWithOurKey(unsigned);
263
+ if (signedA.isErr() || signedB.isErr()) return;
306
264
 
307
- // 5. Combine the partials, finalize at threshold, broadcast.
308
- const combined = coordinator.combinePsbts([signedA, signedB]).value;
309
- const finalized = coordinator.importSignedPsbt(combined).value; // 2-of-3 met
310
- await coordinator.broadcastTransaction(finalized.txHex);
265
+ // 5. Combine, finalize at threshold, broadcast.
266
+ const combined = coordinator.combinePsbts([signedA.value, signedB.value]);
267
+ if (combined.isErr()) return;
268
+ const finalized = coordinator.importSignedPsbt(combined.value); // 2-of-3 met
269
+ if (finalized.isErr()) return;
270
+ await coordinator.broadcastTransaction(finalized.value.txHex);
311
271
 
312
- // Importing with only one signature fails loudly:
272
+ // Below threshold it fails loudly:
313
273
  // 'Input 0 is below the multisig threshold: have 1 signature(s), need 2.'
314
274
 
315
- // Interop: import the wallet into Bitcoin Core/Sparrow/Specter.
316
- const descriptors = walletA.exportDescriptors().value;
275
+ // Interop: import into Bitcoin Core / Sparrow / Specter.
276
+ coordinator.exportDescriptors();
317
277
  // wsh(sortedmulti(2,[fp/48h/0h/0h/2h]xpub.../0/*,[fp]xpub.../0/*,...))#checksum
318
278
  ```
319
279
 
320
- ### Networks, Fee Estimates & Electrum Failover
280
+ `buildPsbt` attaches the `witnessScript` and one `bip32Derivation` per cosigner to every input. `importSignedPsbt` counts VALID partial signatures per input against the witnessScript threshold and refuses to finalize below it. Library-only for now: the daemon wallet stays single-sig.
321
281
 
322
- - **Networks:** `mainnet`, `testnet`, `regtest`, and `signet` are supported end to end (on-chain wallet, Electrum, CLI/daemon via `--network signet`, and the Lightning node config, which uses the signet chain hash and `tbs` invoice prefix). Signet shares testnet's address formats and derivation paths (coin type 1); only the chain differs.
323
- - **Fee estimation source:** `Wallet.create({ feeEstimationSource })` accepts `'electrum' | 'http' | 'auto'` (default `'auto'`). `'electrum'` queries only the connected Electrum server via `blockchain.estimatefee`, so fee lookups never leak to mempool.space/blocktank over clearnet; `'auto'` prefers Electrum and falls back to HTTP only when Electrum is unavailable or returns unusable values. All remote-sourced rates are clamped to at most 5000 sat/vB. The daemon exposes the same option as `feeEstimationSource` / `--fee-source` / `BEIGNET_FEE_SOURCE`.
324
- - **Electrum failover:** when multiple `electrumOptions.servers` are provided, the wallet rotates through them in order on connect/reconnect failure (then through hardcoded fallback peers for the network), with a per-server cooldown so dead servers are not hammered. `wallet.electrum.currentServer` and `wallet.electrum.rotationCount` expose the current server and rotation history.
325
- - **BIP21:** `encodeBip21({ address, amountSats?, label?, message? })` builds a `bitcoin:` payment URI; the daemon's `POST /address/new` accepts `{ bip21: true, amountSats?, label?, message? }` and the CLI supports `address --bip21 [--amount <sats>] [--label L] [--message M]`.
282
+ </details>
326
283
 
327
- ### Storage & Encryption
284
+ <details>
285
+ <summary><b>Encrypted storage and leveled logging</b></summary>
328
286
 
329
- The wallet persists its state through the host-injected `TStorage` interface (`storage: { getData, setData }` on `Wallet.create`). Values are handed to the host as-is, so by default they are stored in plaintext. The persisted data is addresses, address indexes, UTXOs, transactions, balance and fee estimates. No private keys and no mnemonic are ever written, so exposure is a privacy concern (full wallet history), not fund loss.
287
+ The wallet persists through the host-injected `TStorage` interface (`storage: { getData, setData }`), and values are handed over as-is, so by default they are stored in plaintext. Persisted data is addresses, indexes, UTXOs, transactions, balance and fee estimates: no private keys and no mnemonic are ever written, so exposure is a privacy concern (full wallet history), not fund loss.
330
288
 
331
- To encrypt at rest, wrap any `TStorage` with `createEncryptedStorage` before passing it in. Values are encrypted with AES-256-GCM under a key derived from the seed via HKDF, and pre-existing plaintext values are passed through unchanged and migrate lazily as they are rewritten:
289
+ Wrap any `TStorage` with `createEncryptedStorage` to encrypt at rest with AES-256-GCM under an HKDF-derived key from the seed. Pre-existing plaintext values pass through unchanged and migrate lazily as they are rewritten.
332
290
 
333
291
  ```typescript
334
- import { createEncryptedStorage, Wallet } from 'beignet';
335
292
  import * as bip39 from 'bip39';
293
+ import { createConsoleLogger, createEncryptedStorage, Wallet } from 'beignet';
336
294
 
337
295
  const seed = bip39.mnemonicToSeedSync(mnemonic);
338
296
  const wallet = await Wallet.create({
339
297
  mnemonic,
340
298
  storage: createEncryptedStorage({ getData, setData }, seed),
341
- // ...
342
- });
343
- ```
344
-
345
- ### Leveled Logging
346
-
347
- Diagnostic output (debug/info/warn/error) flows through a small injectable logger, kept separate from the Lightning node's persisted structured action log (`getActionLog`). The `ILogger` interface is four methods, `debug`/`info`/`warn`/`error`(`message: string, meta?: unknown`), with level filtering `debug < info < warn < error` plus `'silent'`:
348
-
349
- ```typescript
350
- import { Wallet, createConsoleLogger, noopLogger } from 'beignet';
351
-
352
- const wallet = await Wallet.create({
353
- mnemonic,
354
299
  logger: createConsoleLogger('warn'), // only warn + error reach the console
355
- // logger: noopLogger, // fully silent
356
- // logger: myLogger, // any ILogger: route into your own stack
357
- // ...
300
+ electrumOptions: { net, tls }
358
301
  });
359
302
  ```
360
303
 
361
- - **`Wallet.create({ logger })`** defaults to `createConsoleLogger('info')`, which preserves the wallet's historical console output. `disableMessages` is independent and keeps its existing meaning: it only gates `onMessage` callbacks.
362
- - **`LightningNode`** accepts `logger` in `INodeConfig` / `fromMnemonic` options and defaults to `noopLogger` (the node prints nothing, as before). Every structured action-log entry is additionally mirrored to `logger.debug('category:action', data)`.
363
- - **`BeignetNode.create({ logger, logLevel })`**: log entries that pass `logLevel` are forwarded to the injected logger (in addition to the `'log'` event), and the logger is injected into the underlying `Wallet` and `LightningNode`. Without `logger`, behavior is unchanged (events only).
364
- - **Daemon:** `beignet start --log-level <debug|info|warn|error|silent>` (or `BEIGNET_LOG_LEVEL`, or `logLevel` in `~/.beignet/config.json`) prints leveled diagnostics to stderr. Unset keeps the daemon silent (the default); stdout stays reserved for command output.
304
+ Diagnostics flow through a small injectable `ILogger` (`debug`/`info`/`warn`/`error`, each `(message, meta?)`), with filtering `debug < info < warn < error` plus `'silent'`. This is separate from the Lightning node's persisted structured action log (`getActionLog`).
365
305
 
366
- ## Lightning Network
306
+ - `Wallet.create({ logger })` defaults to `createConsoleLogger('info')`, preserving historical console output. `disableMessages` is independent: it only gates `onMessage` callbacks.
307
+ - `LightningNode` defaults to `noopLogger` (silent). Every action-log entry is also mirrored to `logger.debug('category:action', data)`.
308
+ - `BeignetNode.create({ logger, logLevel })` forwards passing entries to the logger (in addition to the `'log'` event) and injects it into the underlying `Wallet` and `LightningNode`.
309
+ - Daemon: `beignet start --log-level <debug|info|warn|error|silent>` (or `BEIGNET_LOG_LEVEL`, or `logLevel` in `~/.beignet/config.json`) prints to stderr. Unset keeps the daemon silent; stdout stays reserved for command output.
367
310
 
368
- ### Lightning Quick Start (BeignetNode)
311
+ </details>
369
312
 
370
- `BeignetNode` (from `beignet/cli`) is the recommended API — it wraps the protocol layer with a simpler, JSON-friendly interface: satoshi-denominated amounts, string channel IDs, and structured error codes.
313
+ ## Lightning
371
314
 
372
- For detailed deployment guidance, see [AI Agent Deployment Guide](docs/AI_AGENT_GUIDE.md).
315
+ > **Beignet is under active development.** Evaluate it on regtest, signet, or with small
316
+ > amounts you can afford to lose. Read [Status & limitations](#status--limitations) before
317
+ > putting meaningful mainnet funds behind it: this is a self-custodial Lightning
318
+ > implementation, and channel funds are only as safe as the node watching them.
319
+
320
+ `BeignetNode` from `beignet/cli` is the recommended API: it wraps the protocol layer with satoshi amounts, string channel IDs and structured error codes.
373
321
 
374
322
  ```typescript
375
323
  import { BeignetNode, isRetryableError } from 'beignet/cli';
376
324
 
377
- // Create a node (auto-creates wallet, storage, funding provider)
325
+ // Creates the wallet, storage and funding provider for you
378
326
  const node = await BeignetNode.create({
379
- mnemonic: 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
327
+ mnemonic: 'abandon abandon ... about',
380
328
  network: 'regtest',
381
329
  electrumHost: '127.0.0.1',
382
- electrumPort: 60001,
330
+ electrumPort: 60001
383
331
  });
384
332
 
385
- // Get node info and health
386
- console.log(node.getInfo()); // { nodeId, network, alias, ... }
387
- console.log(node.getHealth()); // { status: 'ready', peers, channels, ... }
388
- console.log(node.isReady()); // true when node has active channels
333
+ node.getInfo(); // { nodeId, network, alias, ... }
334
+ node.getHealth(); // { status: 'ready', peers, channels, ... }
335
+ node.isReady(); // true once the node has active channels
389
336
 
390
- // Create an invoice
391
337
  const inv = node.createInvoice(1000, 'coffee');
392
338
  console.log(inv.bolt11);
393
339
 
394
- // Pay an invoice with automatic retry logic
395
340
  try {
396
341
  const payment = await node.payInvoice('lnbcrt10n1...');
397
342
  console.log(payment.status); // 'COMPLETED'
398
343
  } catch (err) {
399
344
  if (isRetryableError(err)) {
400
- // Transient failure — safe to retry (no route, timeout, etc.)
345
+ // transient: no route, timeout. Safe to retry
401
346
  } else {
402
- // Permanent failure — do not retry (invalid invoice, expired, etc.)
347
+ // permanent: invalid invoice, expired. Do not retry
403
348
  }
404
349
  }
405
350
 
406
- // List channels, payments, invoices
407
- console.log(node.listChannels());
408
- console.log(node.listPayments());
409
- console.log(node.listInvoices());
351
+ node.listChannels();
352
+ node.listPayments();
353
+ node.listInvoices();
410
354
 
411
- // Clean shutdown
412
355
  await node.destroy();
413
356
  ```
414
357
 
415
- ### Decision-Support APIs
358
+ Events: `node:ready`, `channel:ready`, `channel:closed`, `peer:connect`, `peer:disconnect`, `peer:error`, `payment:sent`, `payment:received`, `node:error`, `log`.
359
+
360
+ Useful variants: `payInvoiceSafe` (never throws), `payInvoiceWithRetry({ maxRetries, backoffMs, maxFeeSats })`, `sendPaymentAsync` (returns the hash immediately), `connectAndOpenChannel`, `openChannelAndWait`, `sendKeysend`, `createOffer`/`payOffer`, `spliceIn`/`spliceOut`, `backup`, `gracefulShutdown`.
361
+
362
+ **→ [docs/AI_AGENT_GUIDE.md](docs/AI_AGENT_GUIDE.md)** covers deployment in depth: channel strategy, liquidity management, monitoring and Prometheus metrics, pre-flight validation, safety rails, retry/backoff patterns, idempotency keys, spend limits, drain mode, backup and recovery, mainnet checklist.
363
+
364
+ ### Decision-support APIs
416
365
 
417
- Beignet includes built-in advisors that differentiate it from other Lightning libraries:
366
+ Built-in advisors, not usually found in a Lightning library:
418
367
 
419
368
  ```typescript
420
369
  // Channel balance analysis with actionable recommendations
@@ -424,420 +373,306 @@ for (const rec of liquidity.recommendations) {
424
373
  console.log(`[${rec.priority}] ${rec.type}: ${rec.reason}`);
425
374
  }
426
375
 
427
- // Graph-based peer suggestions for channel opens
428
- const suggestions = node.getChannelSuggestions(3);
429
-
430
- // On-chain fee trend analysis (OPEN_NOW / WAIT / NEUTRAL)
431
- const fees = node.getFeeSnapshot();
432
-
433
- // Payment success probability + estimated fee before sending
434
- const estimate = node.estimatePayment(bolt11);
435
-
436
- // 11-check mainnet readiness report with weighted score
437
- const readiness = node.getMainnetReadiness();
438
- console.log('Score:', readiness.score + '/100', 'Ready:', readiness.ready);
376
+ node.getChannelSuggestions(3); // graph-based peer suggestions for opens
377
+ node.getFeeSnapshot(); // on-chain fee trend: OPEN_NOW / WAIT / NEUTRAL
378
+ node.estimatePayment(bolt11); // success probability + estimated fee, pre-send
379
+ node.getMainnetReadiness(); // 11-check weighted readiness report
439
380
  ```
440
381
 
441
- ### Advisor Execution (circular rebalancing + fee auto-tuning)
382
+ <details>
383
+ <summary><b>Advisor execution: circular rebalancing and fee auto-tuning</b></summary>
442
384
 
443
- The advisor can also *act*, not just recommend. Both features are **off by
444
- default** and only run when explicitly enabled in the node options.
385
+ The advisor can act, not just recommend. Both features are **off by default**.
445
386
 
446
387
  ```typescript
447
- // One-shot circular rebalance: self-payment out over fromChannelId and back
448
- // in over toChannelId. Aborts WITHOUT paying if the route fee > maxFeeSats.
449
- const result = await node.rebalanceChannel(
450
- fromChannelId, toChannelId, 50_000, /* maxFeeSats: */ 50
451
- );
452
-
453
- // Inspect what the executor would do (read-only)
454
- const recs = node.getAdvisorRecommendations(); // analyze() + rebalancePlan[]
388
+ // One-shot circular rebalance: self-payment out over `from` and back in over `to`.
389
+ // Aborts WITHOUT paying if the route fee exceeds maxFeeSats.
390
+ await node.rebalanceChannel(fromChannelId, toChannelId, 50_000, /* maxFeeSats */ 50);
455
391
 
456
- // Run the advisor's rebalance plan under a per-day fee budget
457
- const summary = await node.executeRebalances(/* budgetSatsPerDay: */ 500);
392
+ node.getAdvisorRecommendations(); // read-only: analyze() + rebalancePlan[]
393
+ await node.executeRebalances(/* budgetSatsPerDay */ 500);
458
394
  ```
459
395
 
460
- Automatic modes (opt-in via `BeignetNodeOptions` / `INodeConfig`):
396
+ Automatic modes, opt-in via `BeignetNodeOptions` / `INodeConfig`:
461
397
 
462
398
  ```typescript
463
399
  const node = await BeignetNode.create({
464
400
  mnemonic,
465
- // Periodically executes the rebalance plan. Routing fees spent on
466
- // rebalances are capped at budgetSatsPerDay per UTC day; the running spend
467
- // is persisted, so restarts never overspend the same day. The budget
468
- // resets at midnight UTC.
401
+ // Periodically executes the rebalance plan. Routing fees spent on rebalances
402
+ // are capped per UTC day and the running spend is persisted, so restarts
403
+ // never overspend the same day. Resets at midnight UTC.
469
404
  autoRebalance: { enabled: true, budgetSatsPerDay: 500, minImbalancePct: 20 },
470
405
  // Every intervalMs (default 6h) nudges each channel's proportional fee:
471
- // +25% when outbound is depleted (<20% local) but still forwarding,
472
- // -25% when the channel saw no forwards in the window, clamped to
473
- // [floorPpm, ceilPpm]. One adjustment per channel per interval.
406
+ // +25% when outbound is depleted (<20% local) but still forwarding, -25% when
407
+ // the channel saw no forwards in the window, clamped to [floorPpm, ceilPpm].
408
+ // One adjustment per channel per interval.
474
409
  autoTuneFees: { enabled: true, floorPpm: 1, ceilPpm: 5_000 }
475
410
  });
476
411
  ```
477
412
 
478
- Daemon/CLI surfaces: `POST /rebalance`, `GET /advisor/recommendations`,
479
- `POST /advisor/execute-rebalances`; `beignet rebalance <from> <to> <sats>
480
- --max-fee <sats>`, `beignet advisor recommendations`, `beignet advisor
481
- execute-rebalances [--budget <sats>]`.
482
-
483
- ### Watchtowers
484
-
485
- Penalty enforcement normally requires this node's own chain monitor to be online:
486
- if a counterparty broadcasts a revoked commitment while you are offline, nobody
487
- sweeps the breach. The **watchtower client** closes that gap. At every revocation
488
- it builds an encrypted *justice kit* (the revoked commitment's breach hint plus a
489
- pre-signed to_local penalty) and ships it to one or more remote towers over the
490
- standard BOLT 8 Noise transport. When a tower later sees the breach transaction on
491
- chain, it decrypts the kit and broadcasts the penalty on your behalf — reclaiming
492
- the channel even though you never came back online.
493
-
494
- - **Altruist only.** Sessions use `reward = 0`; towers take no cut. There is no
495
- server mode (beignet is a tower *client*, not a tower).
496
- - **LND-tower compatible.** Implements LND's `wtwire` protocol (Init/CreateSession/
497
- StateUpdate/DeleteSession, message types 600-607) and the version-0 justice blob
498
- (XChaCha20-Poly1305, breach hint = `SHA256(txid)[:16]`, key = `SHA256(txid‖txid)`),
499
- so it interoperates with existing public LND altruist towers.
500
- - **Legacy + anchor channels.** The to_local revocation penalty (the fund-critical
501
- breach punishment) is packed for both; taproot channels are not yet backed up.
502
- - **Durable.** Per-tower session state and the un-acked update backlog are persisted
503
- (encrypted at rest) and drained with exponential backoff on reconnect. An un-acked
504
- update is never dropped silently.
505
-
506
- Configure towers as `pubkey@host:port` URIs (off when empty):
507
-
508
- ```ts
413
+ Daemon: `POST /rebalance`, `GET /advisor/recommendations`, `POST /advisor/execute-rebalances`.
414
+ CLI: `beignet rebalance <from> <to> <sats> --max-fee <sats>`, `beignet advisor recommendations`, `beignet advisor execute-rebalances [--budget <sats>]`.
415
+
416
+ </details>
417
+
418
+ <details>
419
+ <summary><b>Watchtowers (altruist client)</b></summary>
420
+
421
+ Penalty enforcement normally needs this node's chain monitor to be online: if a counterparty broadcasts a revoked commitment while you are offline, nobody sweeps the breach. The watchtower client closes that gap. At every revocation it builds an encrypted justice kit (the revoked commitment's breach hint plus a pre-signed to_local penalty) and ships it to remote towers over BOLT 8. When a tower later sees the breach on chain it decrypts the kit and broadcasts the penalty for you.
422
+
423
+ ```typescript
509
424
  const node = await BeignetNode.create({
510
425
  mnemonic,
511
- watchtowers: ['03abc...@tower.example.com:9911']
426
+ watchtowers: ['03abc...@tower.example.com:9911'] // off when empty
512
427
  });
513
428
  ```
514
429
 
515
- Daemon/CLI surfaces: `GET /watchtowers`, `POST /watchtower/add`,
516
- `DELETE /watchtower/remove`; `beignet watchtower list`, `beignet watchtower add
517
- <pubkey@host:port>`, `beignet watchtower remove <uri>`; daemon flag `--watchtower`
518
- (repeatable) or `BEIGNET_WATCHTOWERS` (comma-separated).
430
+ - **Altruist only.** Sessions use `reward = 0`. There is no server mode: beignet is a tower client, not a tower.
431
+ - **LND-tower compatible.** Implements LND's `wtwire` protocol (Init/CreateSession/StateUpdate/DeleteSession, message types 600-607) and the version-0 justice blob (XChaCha20-Poly1305, breach hint `SHA256(txid)[:16]`, key `SHA256(txid‖txid)`), so it works with existing public LND altruist towers.
432
+ - **Legacy + anchor channels.** The to_local revocation penalty (the fund-critical punishment) is packed for both. Taproot channels are not yet backed up.
433
+ - **Durable.** Per-tower session state and the un-acked backlog are persisted (encrypted at rest) and drained with exponential backoff on reconnect. An un-acked update is never dropped silently.
434
+
435
+ Daemon: `GET /watchtowers`, `POST /watchtower/add`, `DELETE /watchtower/remove`.
436
+ CLI: `beignet watchtower list|add <pubkey@host:port>|remove <uri>`, daemon flag `--watchtower` (repeatable) or `BEIGNET_WATCHTOWERS`.
519
437
 
520
- ### HTTP Daemon
438
+ </details>
521
439
 
522
- BeignetNode can also run as an HTTP/SSE daemon for language-agnostic integrations:
440
+ ## HTTP daemon & CLI
441
+
442
+ The same node runs as an HTTP/SSE daemon for language-agnostic integrations, driven by a JSON CLI.
523
443
 
524
444
  ```bash
525
- # Start the daemon (generates OpenAPI spec at /openapi.json)
526
- npx beignet --mnemonic "abandon ..." --network regtest --electrum-host 127.0.0.1 --electrum-port 60001
445
+ # 1. Generate a mnemonic + ~/.beignet/config.json
446
+ npx beignet init --network regtest
447
+
448
+ # 2. Start the daemon (add --daemon to background it)
449
+ BEIGNET_ELECTRUM_HOST=127.0.0.1 BEIGNET_ELECTRUM_PORT=60001 BEIGNET_ELECTRUM_TLS=false \
450
+ npx beignet start --network regtest --api-token mytoken
451
+
452
+ # 3. Drive it with the CLI (thin HTTP client, JSON out)
453
+ npx beignet info --pretty
454
+ npx beignet address
455
+ npx beignet channel connect-and-open <pubkey> <host> <port> 200000
456
+ npx beignet invoice create 1000 "coffee"
457
+ npx beignet invoice pay <bolt11>
458
+ ```
527
459
 
528
- # Create an invoice
529
- curl -X POST http://localhost:2112/invoice/create -H 'Authorization: Bearer <token>' \
460
+ Electrum and most other settings come from `~/.beignet/config.json` or the environment (`BEIGNET_MNEMONIC`, `BEIGNET_ELECTRUM_HOST`, `BEIGNET_ELECTRUM_PORT`, `BEIGNET_NETWORK`, ...). Run `npx beignet help` for the full command and flag list.
461
+
462
+ Or over HTTP directly:
463
+
464
+ ```bash
465
+ curl -X POST http://localhost:2112/invoice/create -H 'Authorization: Bearer mytoken' \
530
466
  -H 'Content-Type: application/json' -d '{"amountSats": 1000, "description": "coffee"}'
531
467
 
532
- # Pay an invoice
533
- curl -X POST http://localhost:2112/invoice/pay -H 'Authorization: Bearer <token>' \
468
+ curl -X POST http://localhost:2112/invoice/pay -H 'Authorization: Bearer mytoken' \
534
469
  -H 'Content-Type: application/json' -d '{"bolt11": "lnbcrt10n1..."}'
535
470
 
536
- # Stream events (payments, channel state changes)
537
- curl -N http://localhost:2112/events -H 'Authorization: Bearer <token>'
538
-
539
- # Simple readiness check (auth-exempt, for load balancers)
540
- curl http://localhost:2112/ready
471
+ curl -N http://localhost:2112/events -H 'Authorization: Bearer mytoken' # SSE stream
472
+ curl http://localhost:2112/ready # load-balancer probe
541
473
  ```
542
474
 
543
- #### Response Format
475
+ - Responses are `{ "ok": true, "result": {...} }` or `{ "ok": false, "error": { "code": "...", "message": "..." } }`.
476
+ - Full spec at `GET /openapi.json`.
477
+ - `GET /health`, `/ready`, `/openapi.json` and `/metrics` are auth-exempt; everything else requires the bearer token **when one is configured**. Auth is off unless you set `apiToken` or `apiKeys` (named keys with `readonly`/`invoice`/`admin` scopes), so configure a token before exposing the daemon anywhere. It binds `127.0.0.1` by default.
478
+ - Embed it instead of shelling out: `import { startDaemon } from 'beignet/cli'`.
544
479
 
545
- All responses use: `{ "ok": true, "result": {...} }` or `{ "ok": false, "error": { "code": "...", "message": "..." } }`.
546
- Full spec: `GET /openapi.json` (no auth required).
480
+ ## Protocol layer (advanced)
547
481
 
548
- ### Advanced API (LightningNode)
549
-
550
- Most users should use `BeignetNode` above. Use `LightningNode` only if you need direct access to the protocol layer (bigint amounts, Buffer IDs, raw BOLT messages).
482
+ Use this only if you need the BOLT layer directly: bigint msat, Buffer IDs, raw wire messages. `beignet/lightning` exports **namespaces**, not flat symbols.
551
483
 
552
484
  ```typescript
553
- import { Wallet, generateMnemonic } from 'beignet';
554
- import { LightningNode, WalletFundingProvider, Network } from 'beignet/lightning';
555
485
  import net from 'net';
556
486
  import tls from 'tls';
487
+ import { Wallet, generateMnemonic } from 'beignet';
488
+ import { invoice, node as ln, wallet as lnWallet } from 'beignet/lightning';
557
489
 
558
490
  const mnemonic = generateMnemonic();
559
491
 
560
- // 1. Create an on-chain wallet (same mnemonic funds both layers)
561
- const wallet = (await Wallet.create({
562
- mnemonic,
563
- electrumOptions: { net, tls },
564
- })).value;
492
+ // 1. On-chain wallet (the same mnemonic funds both layers)
493
+ const res = await Wallet.create({ mnemonic, electrumOptions: { net, tls } });
494
+ if (res.isErr()) throw res.error;
565
495
 
566
- // 2. Create a Lightning node with auto-funding from the wallet
567
- const fundingProvider = new WalletFundingProvider(wallet);
568
- const node = LightningNode.fromMnemonic(mnemonic, {
569
- network: Network.REGTEST,
496
+ // 2. Lightning node with auto-funding from the wallet
497
+ const node = ln.LightningNode.fromMnemonic(mnemonic, {
498
+ network: invoice.Network.REGTEST,
570
499
  enableNetworking: true,
571
- fundingProvider,
500
+ fundingProvider: new lnWallet.WalletFundingProvider(res.value)
572
501
  });
573
502
 
574
- // 3. Connect to a peer and open a channel — fully automatic
503
+ // 3. Connect and open: fully automatic with a funding provider
575
504
  await node.connectPeer('03...pubkey', '127.0.0.1', 9735);
576
505
  node.openChannel('03...pubkey', 100_000n);
577
506
 
578
- // 4. Create a BOLT 11 invoice
579
- const invoice = node.createInvoice({
580
- amountMsat: 50_000n,
581
- description: 'Payment for coffee',
582
- });
583
-
584
- // 5. Pay a BOLT 11 invoice
507
+ // 4. Invoice and payment
508
+ node.createInvoice({ amountMsat: 50_000n, description: 'coffee' });
585
509
  node.sendPayment(invoiceString);
586
510
 
587
- // 6. Listen for events
588
- node.on('channel:ready', (channelId) => {
589
- console.log('Channel ready:', channelId.toString('hex'));
590
- });
591
- node.on('payment:received', (payment) => {
592
- console.log('Received:', payment.amountMsat, 'msat');
593
- });
594
- node.on('node:error', (err) => {
595
- console.error(`[${err.code}]`, err.message);
596
- });
511
+ // 5. Events. Channel-scoped events carry an object, not a bare id
512
+ node.on('channel:ready', ({ channelId }) => console.log(channelId.toString('hex')));
513
+ node.on('payment:received', (p) => console.log(p.amountMsat, 'msat'));
514
+ node.on('node:error', (err) => console.error(`[${err.code}]`, err.message));
597
515
  ```
598
516
 
599
- **Without a wallet** — you can skip `fundingProvider` and handle funding manually:
600
-
601
- ```typescript
602
- const node = LightningNode.fromMnemonic(mnemonic, {
603
- network: Network.REGTEST,
604
- enableNetworking: true,
605
- });
606
-
607
- const channel = node.openChannel('03...pubkey', 100_000n);
608
- // Build your own funding tx, then:
609
- const channelId = node.createFunding(channel, fundingTxid, outputIndex, signature);
610
- ```
611
-
612
- ### Architecture
613
-
614
- Beignet's Lightning implementation follows a layered, transport-agnostic design:
517
+ Without a `fundingProvider`, build the funding transaction yourself and call `node.createFunding(channel, fundingTxid, outputIndex, signature)` after `openChannel`.
615
518
 
616
519
  ```
617
- LightningNode ← High-level API (EventEmitter)
618
- ├── ChannelManager ← Multiplexes messages to Channel instances
619
- │ └── Channel ← BOLT 2 state machine (returns ChannelAction[])
620
- ├── PeerManager ← TCP connections + Noise_XK encrypted transport
621
- │ └── Peer ← Per-connection BOLT 8 handshake + message framing
622
- ├── NetworkGraph ← BOLT 7 gossip topology + Dijkstra pathfinding
623
- ├── InvoiceManager ← BOLT 11 encode/decode/sign
624
- ├── ChainMonitor ← BOLT 5 force-close detection + sweep
625
- └── FundingProvider? ← Auto-builds + broadcasts funding txs (via Wallet)
520
+ LightningNode High-level API (EventEmitter)
521
+ ├── ChannelManager Multiplexes messages to Channel instances
522
+ │ └── Channel BOLT 2 state machine (returns ChannelAction[])
523
+ ├── PeerManager TCP connections + Noise_XK encrypted transport
524
+ │ └── Peer Per-connection BOLT 8 handshake + message framing
525
+ ├── NetworkGraph BOLT 7 gossip topology + Dijkstra pathfinding
526
+ ├── InvoiceManager BOLT 11 encode/decode/sign
527
+ ├── ChainMonitor BOLT 5 force-close detection + sweep
528
+ └── FundingProvider? Auto-builds + broadcasts funding txs (via Wallet)
626
529
  ```
627
530
 
628
- **Key design principle:** The `Channel` class is fully transport-agnostic. Every method returns `ChannelAction[]` arrays (send message, broadcast tx, watch output, etc.) that the `ChannelManager` maps to real transport or chain operations. This makes the state machine fully testable without network I/O.
531
+ **Key design principle:** `Channel` is fully transport-agnostic. Every method returns a `ChannelAction[]` (send message, broadcast tx, watch output, ...) that `ChannelManager` maps to real transport or chain operations, which makes the state machine testable without network I/O.
629
532
 
630
- ### BOLT Coverage
533
+ **→ [src/lightning/README.md](src/lightning/README.md)** documents the protocol layer in detail: data flow, events reference, typed payment errors, channel lifecycle, zero-conf, anchors, dual funding, splicing, offers, onion messages, forwarding, chain monitoring.
631
534
 
632
- | BOLT | Specification | Status |
633
- |------|--------------|--------|
634
- | 1 | Base Protocol | Peer messaging, init, error, ping/pong, feature negotiation |
535
+ <details>
536
+ <summary><b>BOLT coverage</b></summary>
537
+
538
+ | BOLT | Specification | Implemented |
539
+ |------|--------------|-------------|
540
+ | 1 | Base Protocol | Peer messaging, init, error, ping/pong, feature negotiation, peer storage |
635
541
  | 2 | Channel Management | Full state machine: open, fund, normal operation, shutdown, close, reestablish; v2 dual-funded opens (interactive-tx), splicing, quiescence |
636
542
  | 3 | Transactions | Commitment txs, HTLC scripts, funding scripts, anchor outputs, fee calculation; simple taproot channels (MuSig2 funding, Schnorr HTLC sigs) |
637
543
  | 4 | Onion Routing | Sphinx encryption, TLV hop payloads, payment_secret, failure codes, route blinding, onion messages |
638
544
  | 5 | On-Chain | Force-close detection, HTLC sweep, output resolution, chain monitoring, wallet-funded anchor fee bumping (commitment CPFP + zero-fee HTLC fee-attach) |
639
- | 7 | Gossip | Channel/node announcements, network graph, Dijkstra routing, gossip sync |
545
+ | 7 | Gossip | Channel/node announcements, network graph, Dijkstra routing, gossip sync, Rapid Gossip Sync |
640
546
  | 8 | Transport | Noise_XK handshake, encrypted transport, key rotation |
641
547
  | 9 | Features | DATA_LOSS_PROTECT, STATIC_REMOTE_KEY, PAYMENT_SECRET, TLV_ONION, BASIC_MPP, CHANNEL_TYPE, GOSSIP_QUERIES, ANCHORS_ZERO_FEE_HTLC_TX (default), ROUTE_BLINDING, ONION_MESSAGES, QUIESCE, SCID_ALIAS, ZERO_CONF, KEYSEND, OPTION_TAPROOT, OPTION_WILL_FUND |
548
+ | 10 | DNS Bootstrap | Seed resolution for discovering initial peers |
642
549
  | 11 | Invoices | Encode, decode, sign, verify, amount formatting, hold invoices |
643
- | 12 | Offers | Offer encode/decode, invoice_request/invoice exchange over onion messages, receive-side settlement, async payment offers |
550
+ | 12 | Offers | Offer encode/decode, invoice_request/invoice over onion messages, receive-side settlement, async payment offers |
644
551
  | bLIP-51 | Liquidity Ads | lease_rates/request_funds/will_fund negotiation, lease fee accounting, CLTV-locked lessor to_local, advisor lease quoting |
645
552
 
646
- ### Module Reference
553
+ </details>
647
554
 
648
- The Lightning implementation is organized into 21 modules under `src/lightning/`:
555
+ <details>
556
+ <summary><b>Module reference (23 modules under <code>src/lightning/</code>)</b></summary>
649
557
 
650
558
  | Module | Description |
651
559
  |--------|-------------|
652
- | `crypto/` | ChaCha20-Poly1305 AEAD, ECDH, HKDF key derivation, MuSig2 (BIP327) for taproot channels |
653
- | `message/` | Wire protocol encode/decode for all channel, gossip, and control messages |
560
+ | `crypto/` | ChaCha20-Poly1305 AEAD, ECDH, HKDF, MuSig2 (BIP 327) for taproot channels |
561
+ | `message/` | Wire encode/decode for all channel, gossip and control messages |
654
562
  | `features/` | Feature flag bitmap management (BOLT 9) |
655
- | `transport/` | Noise_XK handshake, encrypted transport cipher, TCP peer connections, PeerManager |
656
- | `keys/` | HD key derivation, per-commitment secrets (shachain), transaction signing, wallet key derivation |
657
- | `script/` | Funding (2-of-2 multisig), commitment tx outputs, HTLC scripts, revocation, anchor outputs, taproot commitment/HTLC scripts |
658
- | `channel/` | Channel state machine, ChannelManager, commitment builder, channel actions, validation, liquidity ads |
563
+ | `transport/` | Noise_XK handshake, transport cipher, TCP/WebSocket peer connections, PeerManager |
564
+ | `keys/` | HD derivation, per-commitment secrets (shachain), signing, wallet keys |
565
+ | `script/` | Funding 2-of-2 multisig, commitment outputs, HTLC scripts, revocation, anchors, taproot scripts |
566
+ | `channel/` | Channel state machine, ChannelManager, commitment builder, actions, validation, liquidity ads |
659
567
  | `chain/` | ChainMonitor, ChainWatcher, output resolver, closing tx, sweep tx, Electrum backend |
660
- | `invoice/` | BOLT 11 invoice encoding/decoding, bech32 word conversion, signature verification |
568
+ | `invoice/` | BOLT 11 encoding/decoding, bech32 words, signature verification |
661
569
  | `gossip/` | NetworkGraph, Dijkstra pathfinding, gossip sync state machine, SCID encoding |
662
- | `onion/` | Sphinx crypto, onion packet construction/processing, hop payloads, failure handling, blinded paths |
663
- | `onion-message/` | BOLT 4 onion message construction/processing (carries BOLT 12 and async-payment messages) |
570
+ | `onion/` | Sphinx crypto, packet construction/processing, hop payloads, failures, blinded paths |
571
+ | `onion-message/` | Onion message construction/processing (carries BOLT 12 and async-payment messages) |
664
572
  | `offer/` | BOLT 12 offers: encode/decode, OfferManager invoice_request/invoice flows |
665
573
  | `async-payments/` | Hold invoices and AsyncPaymentManager (LSP held-forward, release_held_htlc, wake) |
666
574
  | `interactive-tx/` | Interactive transaction construction for v2 dual-funded opens and splicing |
667
- | `node/` | LightningNode orchestrator — the main entry point for the Lightning API |
668
- | `wallet/` | WalletFundingProvider — adapts the on-chain Wallet for auto-funded channel opens |
669
- | `bootstrap/` | DNS seed resolution for discovering initial Lightning peers |
670
- | `advisor/` | Liquidity, fee, and channel suggestion advisors for AI agents |
671
- | `storage/` | SQLite persistence backend, channel state serialization/deserialization |
672
- | `validation/` | Input validation utilities shared across modules |
673
-
674
- The `BeignetNode` wrapper (`src/cli/beignet-node.ts`) provides a simplified, JSON-friendly API on top of `LightningNode` for AI agents and programmatic use.
675
-
676
- ### LightningNode API
677
-
678
- `LightningNode` is an `EventEmitter` that provides the high-level API:
679
-
680
- **Peer Management:**
681
- - `connectPeer(pubkey, host, port)` — Establish encrypted connection
682
- - `disconnectPeer(pubkey)` — Disconnect from peer
683
- - `listPeers()` — List connected peers
684
- - `getNodeId()` — Get this node's public key
685
-
686
- **Channel Operations:**
687
- - `openChannel(peerPubkey, fundingSatoshis, pushMsat?)` — Open a channel (auto-funds when `fundingProvider` is set)
688
- - `createFunding(channel, txid, outputIndex, signature)` — Manual funding (when no `fundingProvider`)
689
- - `handleFundingConfirmed(channelId)` — Notify funding tx confirmed
690
- - `closeChannel(channelId, scriptPubkey)` — Cooperative close
691
- - `forceCloseChannel(channelId, destinationScript)` — Force close (unilateral)
692
- - `listChannels()` — List all channels
693
- - `getChannel(channelId)` — Get channel details
694
-
695
- **Payments:**
696
- - `createInvoice(options)` — Generate a BOLT 11 invoice
697
- - `sendPayment(invoiceString)` — Send a payment
698
- - `sendPaymentToRoute(route, paymentHash, ...)` — Send via explicit route
699
-
700
- **Chain Events:**
701
- - `handleNewBlock(height)` — Process new block
702
- - `handleOutputSpent(txid, index, spendingTx, height)` — Track spent outputs
703
-
704
- **Events:**
705
- - `payment:received` — Incoming payment fulfilled
706
- - `payment:sent` — Outgoing payment succeeded
707
- - `payment:failed` — Outgoing payment failed
708
- - `channel:ready` — Channel entered NORMAL state
709
- - `channel:closed` — Channel closed
710
- - `peer:connect` / `peer:disconnect` — Peer connection changes
711
- - `node:error` — Structured error (code, message, channelId, timestamp)
712
-
713
- ## Running Tests
714
-
715
- ```bash
716
- # Run lightning unit tests (2740+ tests, no infrastructure needed)
717
- npm run test:lightning
575
+ | `watchtower/` | Altruist watchtower client: wtwire protocol, justice blobs, tower sessions |
576
+ | `backup/` | Static channel backup (SCB) export/import |
577
+ | `node/` | LightningNode orchestrator, the main protocol-layer entry point |
578
+ | `wallet/` | WalletFundingProvider, adapts the on-chain Wallet for auto-funded opens |
579
+ | `bootstrap/` | DNS seed resolution for discovering initial peers |
580
+ | `advisor/` | Liquidity, fee and channel-suggestion advisors |
581
+ | `storage/` | SQLite persistence backend, channel state serialization |
582
+ | `validation/` | Input validation shared across modules |
718
583
 
719
- # Run CLI unit tests (720 CLI tests, no infrastructure needed)
720
- npm run test:cli
584
+ `beignet/lightning` re-exports each of these as a namespace (`crypto`, `message`, `node`, ...). `async-payments` and `watchtower` are reachable via their source paths.
721
585
 
722
- # Run daemon/Electrum integration tests (requires Electrum server)
723
- npm run test:integration
586
+ </details>
724
587
 
725
- # Run interop tests against LND/CLN/Eclair (requires Docker)
726
- npm run test:interop
588
+ ## Tests
727
589
 
728
- # Run everything (requires Docker + Electrum)
729
- npm run test:all
590
+ ```bash
591
+ npm run test:lightning # 4000+ Lightning unit tests, no infrastructure needed
592
+ npm run test:cli # 900+ CLI + daemon unit tests, no infrastructure needed
593
+ npm run test:conformance # 250+ official BOLT vector cases (subset of test:lightning)
594
+ npm run test:integration # daemon/Electrum integration (needs an Electrum server)
595
+ npm run test:interop # 190+ cases vs LND/CLN/Eclair (needs Docker)
596
+ npm run test:all # Lightning + CLI + interop (needs Docker + Electrum)
730
597
  ```
731
598
 
732
- ### Test Coverage
599
+ Counts are floors, not snapshots. Run the suites for exact numbers.
733
600
 
734
- The Lightning implementation has **2740+ lightning unit tests + 129 interop tests + 720 CLI tests** across many phases:
601
+ The on-chain wallet suites live in `tests/*.test.ts` and connect to **live public
602
+ Electrum servers**, so they need network access and can fail on a server outage
603
+ rather than on your change. Each script runs `yarn build` first, so yarn has to be
604
+ installed:
735
605
 
736
- | Phase | Tests | Coverage |
737
- |-------|-------|---------|
738
- | Crypto & Messages | 115 | ChaCha20-Poly1305, HKDF, ECDH, codec, TLV, init, error, feature flags |
739
- | Transport (BOLT 8) | — | Noise_XK handshake, cipher, ping/pong, peer connections, PeerManager |
740
- | Keys & Scripts (BOLT 3) | — | Key derivation, shachain, signer, funding, commitment, HTLC, revocation |
741
- | Channel State Machine (BOLT 2) | 161 | Message encode/decode, channel types, validation, Channel, commitment builder, ChannelManager |
742
- | Chain Monitor (BOLT 5) | 67 | Closing tx, sweep tx, output resolver, chain monitor, force close |
743
- | Invoices (BOLT 11) | 98 | Types, words, amount, signing, decode, encode |
744
- | Gossip & Routing (BOLT 7) | 104 | SCID, messages, validation, network graph, pathfinding |
745
- | Onion & Payments (BOLT 4) | 83 | Sphinx crypto, hop payloads, onion construction/processing, failure handling |
746
- | Node API | 50 | LightningNode orchestrator, invoice management, payment send/receive, HTLC forwarding |
747
- | PeerManager Integration | 18 | PeerManager wiring, peer management, event forwarding |
748
- | Production Hardening | — | Error visibility, input validation, resource management, BOLT 1 error propagation |
749
- | **Interop (LND/CLN/Eclair)** | **129** | **Multi-implementation interop: TCP handshake, channel lifecycle, bidirectional payments, anchor channels, anchor force-close with wallet-funded CPFP + HTLC-timeout fee-attach, crash recovery against LND v0.20.0, CLN, and Eclair** |
606
+ ```bash
607
+ npm run test:wallet # also test:transaction, test:electrum, test:storage,
608
+ # test:derivation, test:receive, test:boost
609
+ npm test # everything: build, on-chain, Lightning, CLI, interop
610
+ ```
750
611
 
751
- ## Interop Testing
612
+ The on-chain files without a dedicated script (multisig, PSBT, watch-only,
613
+ descriptors, signet and others) run through mocha directly:
752
614
 
753
- The interop test suite validates beignet against real Lightning implementations on Bitcoin regtest.
615
+ ```bash
616
+ npx mocha --exit -r ts-node/register 'tests/multisig.test.ts'
617
+ ```
754
618
 
755
- ### Prerequisites
619
+ `test:conformance` runs the official BOLT test vectors (BOLT 1 bigsize/TLV, BOLT 3 commitments and anchors and per-commitment secrets, BOLT 4 onion/route-blinding/onion-errors, BOLT 7 extended queries, BOLT 8 transport, BOLT 11 invoices, BOLT 12 offers/signatures) under `tests/lightning/conformance/`.
756
620
 
757
- - Docker and Docker Compose
758
- - Node.js 18+
621
+ <details>
622
+ <summary><b>Interop testing against real implementations</b></summary>
759
623
 
760
- ### Setup
624
+ The interop suite drives beignet against real nodes on Bitcoin regtest.
761
625
 
762
626
  ```bash
763
- # Start bitcoind + LND + CLN + Eclair containers
764
- docker compose -f docker/docker-compose.yml up -d
765
-
766
- # Wait for nodes to sync (~30 seconds)
767
-
768
- # Run interop tests
627
+ docker compose -f docker/docker-compose.yml up -d # wait ~30s for nodes to sync
769
628
  npm run test:interop
770
629
  ```
771
630
 
772
- ### What's Tested
773
-
774
- #### LND (43 tests)
775
-
776
- | Tier | Tests | Validates |
777
- |------|-------|-----------|
778
- | **1: TCP & Init** | 5 | BOLT 8 Noise_XK handshake, BOLT 1 init exchange, feature negotiation, disconnect/reconnect, ping/pong survival |
779
- | **2: Channel Open** | 3 | LND opens channel to beignet, balance verification, error-free lifecycle |
780
- | **3: LND pays beignet** | 3 | Receive payment from LND, payment_secret validation, multiple sequential payments |
781
- | **4: Beignet pays LND** | 3 | Pay LND invoice, outbound payment_secret, graceful failure handling |
782
- | **5-9: Advanced** | 11 | Channel close, reestablish, gossip sync, MPP payments, SCID aliases |
783
- | **10: Inbound connections** | 4 | LND connects to beignet listener, channel open from inbound peer |
784
- | **11-13: Anchor & Recovery** | 14 | Anchor channels, beignet-funded opens, crash recovery |
785
-
786
- #### CLN (42 tests)
787
-
788
- | Tier | Tests | Validates |
789
- |------|-------|-----------|
790
- | **1: TCP & Init** | 5 | BOLT 8 handshake, init exchange, feature negotiation |
791
- | **2-9: Channel & Payments** | 23 | Channel lifecycle, bidirectional payments, close, reestablish, gossip, MPP, SCID aliases |
792
- | **10: Inbound** | 4 | Inbound connections from CLN |
793
- | **12-14: Anchor & Recovery** | 10 | Anchor channels, beignet-funded opens, crash recovery |
794
-
795
- #### Eclair (42 tests)
631
+ Services in `docker/docker-compose.yml`:
796
632
 
797
- | Tier | Tests | Validates |
798
- |------|-------|-----------|
799
- | **1: TCP & Init** | 5 | BOLT 8 handshake, init exchange, feature negotiation |
800
- | **2-9: Channel & Payments** | 23 | Channel lifecycle, bidirectional payments, close, reestablish, gossip, MPP |
801
- | **10: Inbound** | 4 | Inbound connections from Eclair |
802
- | **12-14: Anchor & Recovery** | 10 | Anchor channels, beignet-funded opens, crash recovery |
633
+ | Service | Image | Ports |
634
+ |---------|-------|-------|
635
+ | bitcoind | Bitcoin Core 29.1 (regtest) | RPC 43782, ZMQ 28334/28335/28336 |
636
+ | lnd | `lightninglabs/lnd:v0.20.0-beta` | P2P 9735, REST 8081 |
637
+ | cln | `elementsproject/lightningd:v26.06.1` | CLNRest 3010 |
638
+ | eclair | `polarlightning/eclair:0.13.1` | HTTP API 8082 |
639
+ | electrs | `getumbrel/electrs:v0.10.10` | Electrum 60001 |
803
640
 
804
- Interop tests are excluded from `npm run test:lightning` (which runs only unit tests). Use `npm run test:interop` to run them, or `npm run test:all` to run everything.
641
+ Covered per implementation: BOLT 8 handshake and BOLT 1 init/feature negotiation, disconnect/reconnect and ping/pong survival, channel open in both directions, bidirectional payments and payment_secret validation, MPP, SCID aliases, cooperative close, reestablish, gossip sync, inbound connections, anchor channels, anchor force-close with wallet-funded CPFP and HTLC-timeout fee-attach, and crash recovery. Beyond the shared matrix: taproot channel lifecycle vs LND (open, pay both directions, reestablish, coop and force close, penalty, SCB recovery), splice matrix and lease/liquidity-ads flows vs CLN, `simple_close` vs Eclair, blinded-path payments, and the watchtower client vs an LND tower.
805
642
 
806
- ### Docker Compose Services
643
+ Interop tests are excluded from `npm run test:lightning`.
807
644
 
808
- The `docker/docker-compose.yml` includes:
809
- - **bitcoind** — Bitcoin Core regtest node (RPC port 43782, ZMQ on 28334/28335)
810
- - **LND** — Lightning Network Daemon v0.20.0-beta (P2P port 9735, REST port 8081)
811
- - **CLN** — Core Lightning (CLNRest API on port 3010)
812
- - **Eclair** — ACINQ Eclair (HTTP API on port 8082)
645
+ </details>
813
646
 
814
- ## Known Limitations
647
+ ## Status & limitations
815
648
 
816
- Beignet is under active development. The following features are missing or carry caveats:
649
+ Beignet is under active development. Known gaps and caveats:
817
650
 
818
- | Feature | Status | Impact |
651
+ | Feature | Status | Detail |
819
652
  |---------|--------|--------|
820
- | **Watchtowers** | Client implemented (altruist) | Ships encrypted justice data to remote LND altruist towers at every revocation so a breach is punished while you are offline (see [Watchtowers](#watchtowers)). Legacy + anchor channels only; taproot channels are not yet backed up, and server mode is out of scope. |
821
- | **LSP / LSPS protocols** | Not implemented | No automated inbound liquidity acquisition via LSPS0/1/2. Liquidity ads (bLIP-51) are supported for negotiated leases; otherwise open channels manually. |
822
- | **Trampoline routing** | Not implemented | All route computation is local. Cannot delegate pathfinding to a trampoline node. |
823
- | **BOLT 12 offers** | Newer | Offer creation/decoding, invoice_request/invoice over onion messages, and receive-side settlement are implemented, but the surface is newer and less battle-tested than BOLT 11. Prefer BOLT 11 invoices for production. |
824
- | **Async payments** | Implemented (LSP-dependent) | Hold invoices plus AsyncPaymentManager let an offline receiver be paid, but the receiver's LSP must run the held-forward/wake flow. |
825
- | **Simple taproot channels** | Experimental | Opens, payments both directions, force-close, and reestablish validated against LND v0.20 on regtest; the feature bit is still in staging upstream. Not recommended for mainnet balances yet. |
826
- | **Splicing / dual funding** | Partial | Splice-out and splice-in validated live against CLN on mainnet; v2 dual-funded opens implemented. CLN-initiated splices, repeat splices, and multi-UTXO splice-ins are untested. |
827
- | **Mainnet battle-testing** | Limited | Interop-tested against LND/CLN/Eclair on regtest. Exercise caution with large mainnet balances. |
828
- | **Mobile background** | Limited | Works on React Native but lacks mobile-specific optimizations (background sync, push notifications). |
829
-
830
- **Recommended safeguards for production use:**
831
- - Set `maxPaymentSats` and `dailySpendLimitSats` to cap exposure
832
- - Use `validatePayment()` before every send to catch problems early
833
- - Enable `backupPath` for automated database backups
834
- - Use `electrumServers` (plural) for connection redundancy
835
- - Monitor `node:error` events and `/health` endpoint
836
- - Start with small channel sizes and increase gradually
653
+ | Mainnet battle-testing | Limited | Interop-tested on regtest, with some flows validated live on mainnet. Exercise caution with large balances. |
654
+ | Watchtowers | Client only (altruist) | Punishes breaches while you are offline via remote LND altruist towers. Legacy + anchor channels only: taproot channels are not backed up. No server mode. |
655
+ | LSP / LSPS protocols | Not implemented | No automated inbound liquidity via LSPS0/1/2. Liquidity ads (bLIP-51) cover negotiated leases; otherwise open channels manually. |
656
+ | Trampoline routing | Not implemented | All route computation is local. |
657
+ | BOLT 12 offers | Newer | Offers, invoice_request/invoice over onion messages and receive-side settlement work, but the surface is less battle-tested than BOLT 11. Prefer BOLT 11 in production. |
658
+ | Async payments | LSP-dependent | Hold invoices plus AsyncPaymentManager let an offline receiver be paid, but the receiver's LSP must run the held-forward/wake flow. |
659
+ | Simple taproot channels | Experimental | Full lifecycle validated against LND v0.20 on regtest, but the feature bit is still in staging upstream. Not recommended for mainnet balances. |
660
+ | Splicing / dual funding | Partial | Splice-out and splice-in validated live against CLN; v2 dual-funded opens implemented both as initiator and acceptor. CLN-initiated splices, repeat splices and multi-UTXO splice-ins are untested. |
661
+ | Mobile background | Limited | Works on React Native but has no background sync or push-notification support. |
662
+
663
+ Recommended safeguards in production:
664
+
665
+ - Cap exposure with `maxPaymentSats` and `dailySpendLimitSats`.
666
+ - Call `validatePayment()` before every send.
667
+ - Set `backupPath` for automated database backups, and keep an SCB (`beignet backup scb`).
668
+ - Pass multiple `electrumServers` for connection redundancy.
669
+ - Configure watchtowers so breaches are punished while you are offline.
670
+ - Monitor `node:error` events and the `/health` endpoint.
671
+ - Start with small channels and increase gradually.
837
672
 
838
673
  ## React Native
839
674
 
840
- You can use `react-native-tcp-socket` as a drop-in replacement for `net` & `tls`:
675
+ `react-native-tcp-socket` is a drop-in replacement for `net` and `tls`:
841
676
 
842
677
  ```json
843
678
  {
@@ -850,12 +685,18 @@ You can use `react-native-tcp-socket` as a drop-in replacement for `net` & `tls`
850
685
 
851
686
  ## Documentation
852
687
 
853
- - [HTML](docs/html/classes/Wallet.html)
854
- - [Markdown](docs/markdown/classes/Wallet.md)
688
+ | Document | Contents |
689
+ |----------|----------|
690
+ | [example/REPL_TESTING.md](example/REPL_TESTING.md) | Copy-pasteable REPL walkthrough of the full node lifecycle |
691
+ | [docs/AI_AGENT_GUIDE.md](docs/AI_AGENT_GUIDE.md) | Deployment, monitoring, safety rails, HTTP daemon patterns |
692
+ | [src/lightning/README.md](src/lightning/README.md) | Protocol-layer reference and usage guide |
693
+ | [docs/ROADMAP.md](docs/ROADMAP.md) | Feature roadmap and progress |
694
+ | [docs/RECOVERY-PROTOCOL.md](docs/RECOVERY-PROTOCOL.md) | Proposed replicated state-continuity design |
695
+ | [API reference](docs/markdown/classes/Wallet.md) | Generated typedoc ([HTML](docs/html/classes/Wallet.html)) |
855
696
 
856
697
  ## Support
857
698
 
858
- If you are experiencing any problems, please open an issue or reach out to us on [Telegram](https://t.me/bitkitchat).
699
+ Open an issue, or reach out on [Telegram](https://t.me/bitkitchat).
859
700
 
860
701
  ## License
861
702