beignet 0.1.1 → 0.3.0

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.
Files changed (198) hide show
  1. package/README.md +284 -1
  2. package/dist/cli/beignet-node.js +1032 -47
  3. package/dist/cli/beignet-node.js.map +1 -1
  4. package/dist/cli/cli.js +1217 -16
  5. package/dist/cli/cli.js.map +1 -1
  6. package/dist/cli/config.js +29 -1
  7. package/dist/cli/config.js.map +1 -1
  8. package/dist/cli/daemon.js +342 -16
  9. package/dist/cli/daemon.js.map +1 -1
  10. package/dist/cli/errors.js +2 -0
  11. package/dist/cli/errors.js.map +1 -1
  12. package/dist/cli/openapi.js +1207 -14
  13. package/dist/cli/openapi.js.map +1 -1
  14. package/dist/cli/restore.js +89 -0
  15. package/dist/cli/restore.js.map +1 -0
  16. package/dist/cli/wallet-storage.js +34 -0
  17. package/dist/cli/wallet-storage.js.map +1 -0
  18. package/dist/electrum/index.js +105 -10
  19. package/dist/electrum/index.js.map +1 -1
  20. package/dist/lightning/advisor/fee-tuner.js +42 -0
  21. package/dist/lightning/advisor/fee-tuner.js.map +1 -0
  22. package/dist/lightning/advisor/index.js +9 -1
  23. package/dist/lightning/advisor/index.js.map +1 -1
  24. package/dist/lightning/advisor/rebalance-planner.js +55 -0
  25. package/dist/lightning/advisor/rebalance-planner.js.map +1 -0
  26. package/dist/lightning/backup/index.js +18 -0
  27. package/dist/lightning/backup/index.js.map +1 -0
  28. package/dist/lightning/backup/scb.js +42 -0
  29. package/dist/lightning/backup/scb.js.map +1 -0
  30. package/dist/lightning/chain/chain-monitor.js +27 -0
  31. package/dist/lightning/chain/chain-monitor.js.map +1 -1
  32. package/dist/lightning/chain/output-resolver.js +55 -6
  33. package/dist/lightning/chain/output-resolver.js.map +1 -1
  34. package/dist/lightning/chain/types.js +1 -0
  35. package/dist/lightning/chain/types.js.map +1 -1
  36. package/dist/lightning/channel/channel-manager.js +64 -0
  37. package/dist/lightning/channel/channel-manager.js.map +1 -1
  38. package/dist/lightning/channel/channel-state.js.map +1 -1
  39. package/dist/lightning/channel/channel.js +124 -4
  40. package/dist/lightning/channel/channel.js.map +1 -1
  41. package/dist/lightning/channel/dual-funding.js +6 -5
  42. package/dist/lightning/channel/dual-funding.js.map +1 -1
  43. package/dist/lightning/channel/types.js +3 -1
  44. package/dist/lightning/channel/types.js.map +1 -1
  45. package/dist/lightning/channel/validation.js +3 -3
  46. package/dist/lightning/channel/validation.js.map +1 -1
  47. package/dist/lightning/crypto/index.js +1 -0
  48. package/dist/lightning/crypto/index.js.map +1 -1
  49. package/dist/lightning/crypto/message-signing.js +116 -0
  50. package/dist/lightning/crypto/message-signing.js.map +1 -0
  51. package/dist/lightning/crypto/xchacha20poly1305.js +77 -0
  52. package/dist/lightning/crypto/xchacha20poly1305.js.map +1 -0
  53. package/dist/lightning/features/flags.js +1 -0
  54. package/dist/lightning/features/flags.js.map +1 -1
  55. package/dist/lightning/gossip/messages.js +203 -1
  56. package/dist/lightning/gossip/messages.js.map +1 -1
  57. package/dist/lightning/gossip/types.js +3 -1
  58. package/dist/lightning/gossip/types.js.map +1 -1
  59. package/dist/lightning/index.js +2 -1
  60. package/dist/lightning/index.js.map +1 -1
  61. package/dist/lightning/keys/wallet-keys.js +1 -0
  62. package/dist/lightning/keys/wallet-keys.js.map +1 -1
  63. package/dist/lightning/message/index.js +1 -0
  64. package/dist/lightning/message/index.js.map +1 -1
  65. package/dist/lightning/message/peer-storage.js +43 -0
  66. package/dist/lightning/message/peer-storage.js.map +1 -0
  67. package/dist/lightning/message/types.js +2 -0
  68. package/dist/lightning/message/types.js.map +1 -1
  69. package/dist/lightning/node/lightning-node.js +1587 -236
  70. package/dist/lightning/node/lightning-node.js.map +1 -1
  71. package/dist/lightning/node/types.js +18 -1
  72. package/dist/lightning/node/types.js.map +1 -1
  73. package/dist/lightning/offer/offer-manager.js +2 -5
  74. package/dist/lightning/offer/offer-manager.js.map +1 -1
  75. package/dist/lightning/offer/tlv.js +3 -1
  76. package/dist/lightning/offer/tlv.js.map +1 -1
  77. package/dist/lightning/storage/encryption.js +67 -0
  78. package/dist/lightning/storage/encryption.js.map +1 -0
  79. package/dist/lightning/storage/index.js +1 -0
  80. package/dist/lightning/storage/index.js.map +1 -1
  81. package/dist/lightning/storage/serialization.js +6 -2
  82. package/dist/lightning/storage/serialization.js.map +1 -1
  83. package/dist/lightning/storage/sqlite-storage.js +387 -33
  84. package/dist/lightning/storage/sqlite-storage.js.map +1 -1
  85. package/dist/lightning/watchtower/blob.js +135 -0
  86. package/dist/lightning/watchtower/blob.js.map +1 -0
  87. package/dist/lightning/watchtower/index.js +55 -0
  88. package/dist/lightning/watchtower/index.js.map +1 -0
  89. package/dist/lightning/watchtower/justice.js +182 -0
  90. package/dist/lightning/watchtower/justice.js.map +1 -0
  91. package/dist/lightning/watchtower/tower-connection.js +208 -0
  92. package/dist/lightning/watchtower/tower-connection.js.map +1 -0
  93. package/dist/lightning/watchtower/types.js +3 -0
  94. package/dist/lightning/watchtower/types.js.map +1 -0
  95. package/dist/lightning/watchtower/watchtower-client.js +402 -0
  96. package/dist/lightning/watchtower/watchtower-client.js.map +1 -0
  97. package/dist/lightning/watchtower/wtwire.js +201 -0
  98. package/dist/lightning/watchtower/wtwire.js.map +1 -0
  99. package/dist/shapes/electrum.js +32 -1
  100. package/dist/shapes/electrum.js.map +1 -1
  101. package/dist/shapes/wallet.js +11 -1
  102. package/dist/shapes/wallet.js.map +1 -1
  103. package/dist/transaction/index.js +163 -15
  104. package/dist/transaction/index.js.map +1 -1
  105. package/dist/types/cli/beignet-node.d.ts +145 -3
  106. package/dist/types/cli/daemon.d.ts +2 -0
  107. package/dist/types/cli/errors.d.ts +2 -0
  108. package/dist/types/cli/restore.d.ts +10 -0
  109. package/dist/types/cli/types.d.ts +230 -1
  110. package/dist/types/cli/wallet-storage.d.ts +3 -0
  111. package/dist/types/electrum/index.d.ts +12 -0
  112. package/dist/types/electrum.js +1 -0
  113. package/dist/types/electrum.js.map +1 -1
  114. package/dist/types/lightning/advisor/fee-tuner.d.ts +20 -0
  115. package/dist/types/lightning/advisor/index.d.ts +4 -0
  116. package/dist/types/lightning/advisor/rebalance-planner.d.ts +12 -0
  117. package/dist/types/lightning/backup/index.d.ts +1 -0
  118. package/dist/types/lightning/backup/scb.d.ts +24 -0
  119. package/dist/types/lightning/chain/chain-monitor.d.ts +1 -0
  120. package/dist/types/lightning/chain/types.d.ts +1 -0
  121. package/dist/types/lightning/channel/channel-manager.d.ts +7 -0
  122. package/dist/types/lightning/channel/channel-state.d.ts +2 -0
  123. package/dist/types/lightning/channel/channel.d.ts +6 -0
  124. package/dist/types/lightning/channel/dual-funding.d.ts +2 -1
  125. package/dist/types/lightning/channel/types.d.ts +2 -0
  126. package/dist/types/lightning/channel/validation.d.ts +1 -1
  127. package/dist/types/lightning/crypto/index.d.ts +1 -0
  128. package/dist/types/lightning/crypto/message-signing.d.ts +9 -0
  129. package/dist/types/lightning/crypto/xchacha20poly1305.d.ts +3 -0
  130. package/dist/types/lightning/features/flags.d.ts +1 -0
  131. package/dist/types/lightning/gossip/messages.d.ts +5 -0
  132. package/dist/types/lightning/gossip/types.d.ts +2 -0
  133. package/dist/types/lightning/index.d.ts +1 -0
  134. package/dist/types/lightning/keys/wallet-keys.d.ts +2 -1
  135. package/dist/types/lightning/message/index.d.ts +1 -0
  136. package/dist/types/lightning/message/peer-storage.d.ts +9 -0
  137. package/dist/types/lightning/message/types.d.ts +2 -0
  138. package/dist/types/lightning/node/lightning-node.d.ts +118 -3
  139. package/dist/types/lightning/node/types.d.ts +64 -2
  140. package/dist/types/lightning/storage/encryption.d.ts +11 -0
  141. package/dist/types/lightning/storage/index.d.ts +1 -0
  142. package/dist/types/lightning/storage/serialization.d.ts +2 -0
  143. package/dist/types/lightning/storage/sqlite-storage.d.ts +43 -3
  144. package/dist/types/lightning/storage/types.d.ts +62 -0
  145. package/dist/types/lightning/watchtower/blob.d.ts +32 -0
  146. package/dist/types/lightning/watchtower/index.d.ts +9 -0
  147. package/dist/types/lightning/watchtower/justice.d.ts +29 -0
  148. package/dist/types/lightning/watchtower/tower-connection.d.ts +34 -0
  149. package/dist/types/lightning/watchtower/types.d.ts +44 -0
  150. package/dist/types/lightning/watchtower/watchtower-client.d.ts +67 -0
  151. package/dist/types/lightning/watchtower/wtwire.d.ts +90 -0
  152. package/dist/types/shapes/electrum.d.ts +3 -1
  153. package/dist/types/transaction/index.d.ts +6 -0
  154. package/dist/types/transaction.js.map +1 -1
  155. package/dist/types/types/electrum.d.ts +3 -2
  156. package/dist/types/types/transaction.d.ts +1 -1
  157. package/dist/types/types/wallet.d.ts +78 -4
  158. package/dist/types/utils/descriptors.d.ts +2 -0
  159. package/dist/types/utils/fees.d.ts +3 -0
  160. package/dist/types/utils/helpers.d.ts +3 -1
  161. package/dist/types/utils/index.d.ts +5 -0
  162. package/dist/types/utils/multisig.d.ts +29 -0
  163. package/dist/types/utils/transaction.d.ts +7 -0
  164. package/dist/types/utils/wallet-storage-encryption.d.ts +3 -0
  165. package/dist/types/utils/wallet.d.ts +1 -1
  166. package/dist/types/utils/watch-only.d.ts +12 -0
  167. package/dist/types/wallet/constants.d.ts +1 -0
  168. package/dist/types/wallet/index.d.ts +61 -4
  169. package/dist/types/wallet.js +3 -0
  170. package/dist/types/wallet.js.map +1 -1
  171. package/dist/utils/derivation-path.js +8 -7
  172. package/dist/utils/derivation-path.js.map +1 -1
  173. package/dist/utils/descriptors.js +56 -0
  174. package/dist/utils/descriptors.js.map +1 -0
  175. package/dist/utils/electrum.js +10 -2
  176. package/dist/utils/electrum.js.map +1 -1
  177. package/dist/utils/fees.js +19 -0
  178. package/dist/utils/fees.js.map +1 -0
  179. package/dist/utils/helpers.js +22 -5
  180. package/dist/utils/helpers.js.map +1 -1
  181. package/dist/utils/index.js +5 -0
  182. package/dist/utils/index.js.map +1 -1
  183. package/dist/utils/multisig.js +90 -0
  184. package/dist/utils/multisig.js.map +1 -0
  185. package/dist/utils/transaction.js +30 -3
  186. package/dist/utils/transaction.js.map +1 -1
  187. package/dist/utils/wallet-storage-encryption.js +45 -0
  188. package/dist/utils/wallet-storage-encryption.js.map +1 -0
  189. package/dist/utils/wallet.js +3 -1
  190. package/dist/utils/wallet.js.map +1 -1
  191. package/dist/utils/watch-only.js +102 -0
  192. package/dist/utils/watch-only.js.map +1 -0
  193. package/dist/wallet/constants.js +2 -1
  194. package/dist/wallet/constants.js.map +1 -1
  195. package/dist/wallet/index.js +787 -30
  196. package/dist/wallet/index.js.map +1 -1
  197. package/docs/ROADMAP.md +292 -0
  198. package/package.json +1 -1
package/README.md CHANGED
@@ -137,6 +137,210 @@ const utxos = wallet.value.listUtxos();
137
137
  const history = await wallet.value.getAddressHistory('bc1q...');
138
138
  ```
139
139
 
140
+ ### Watch-Only Wallets
141
+
142
+ A watch-only wallet is constructed from an account-level extended public key
143
+ (xpub/ypub/zpub for mainnet, tpub/upub/vpub for testnet/regtest) instead of a
144
+ mnemonic. The key is assumed to sit at the account level
145
+ (m/purpose'/coin'/account', e.g. m/84'/0'/0' for p2wpkh), so receive and
146
+ change addresses derive publicly as xpub/0/i and xpub/1/i. SLIP-132 version
147
+ bytes are normalized automatically: a zpub/vpub implies p2wpkh and a
148
+ ypub/upub implies p2sh-p2wpkh; a plain xpub/tpub uses the `addressType`
149
+ option (default p2wpkh). Because one account xpub yields exactly one address
150
+ type, a watch-only wallet monitors only that type.
151
+
152
+ Everything read-only works: address generation, gap-limit scanning, Electrum
153
+ refresh, balances, transaction history, UTXOs, fee estimates and address
154
+ subscriptions. Anything that requires private keys
155
+ (send/sendMax/sendMany/sweepPrivateKey/getPrivateKey and the internal signing
156
+ paths) fails with the typed `WatchOnlySigningError`
157
+ (`code: 'WATCH_ONLY_CANNOT_SIGN'`, message `watch-only wallet cannot sign`).
158
+
159
+ Watch-only is a library feature only for now: the HTTP daemon always runs
160
+ with a mnemonic.
161
+
162
+ ```typescript
163
+ import { Wallet } from 'beignet';
164
+
165
+ const res = await Wallet.createWatchOnly({
166
+ xpub: 'zpub6r...',
167
+ network: 'bitcoin',
168
+ electrumOptions: { net, tls },
169
+ });
170
+ if (res.isErr()) return;
171
+ const watchOnly = res.value;
172
+
173
+ const address = await watchOnly.getAddress(); // works
174
+ const balance = watchOnly.getBalance(); // works
175
+ const sendRes = await watchOnly.send({ address: 'bc1q...', amount: 1000 });
176
+ // sendRes.isErr() === true; sendRes.error.message === 'watch-only wallet cannot sign'
177
+ ```
178
+
179
+ ### External Signer (Hardware Wallet) PSBT Flow
180
+
181
+ `buildPsbt` runs the normal transaction setup (coin selection, change, fee)
182
+ but stops before signing and returns a base64 PSBT populated with everything
183
+ a hardware signer needs: `witnessUtxo` (or `nonWitnessUtxo` for legacy
184
+ p2pkh), `redeemScript` for p2sh-p2wpkh, `tapInternalKey` plus
185
+ `tapBip32Derivation` for p2tr, and `bip32Derivation` (fingerprint + path +
186
+ pubkey) on every wallet input. It works on both full and watch-only wallets.
187
+ Note for watch-only wallets: the true master fingerprint is unknowable from
188
+ an account xpub, so the xpub's parent fingerprint is used; signers should
189
+ locate keys by derivation path.
190
+
191
+ ```typescript
192
+ // 1. Build (works on a watch-only wallet)
193
+ const build = await wallet.buildPsbt({
194
+ address: 'bc1q...',
195
+ amount: 50000,
196
+ satsPerByte: 4,
197
+ });
198
+ if (build.isErr()) return;
199
+ const { psbtBase64, fee, vsizeEstimate } = build.value;
200
+
201
+ // 2. Sign externally (hardware wallet / HWI / another machine)
202
+ const signedBase64 = await myHardwareWallet.signPsbt(psbtBase64);
203
+
204
+ // 3. Import: validates a signature on EVERY input, finalizes, does NOT broadcast
205
+ const imported = wallet.importSignedPsbt(signedBase64);
206
+ if (imported.isErr()) return; // missing/invalid signatures are rejected loudly
207
+ const { txHex, txid } = imported.value;
208
+
209
+ // 4. Broadcast when ready
210
+ await wallet.broadcastTransaction(txHex);
211
+
212
+ // Multi-party signing: merge partially signed copies of the same PSBT
213
+ const combined = wallet.combinePsbts([copyA, copyB]);
214
+ ```
215
+
216
+ The HTTP daemon exposes the same flow on its (mnemonic-backed) wallet via
217
+ `POST /psbt/build`, `POST /psbt/import-signed` and `POST /psbt/combine`, and
218
+ the CLI via `beignet psbt build|import-signed|combine`.
219
+
220
+ ### Multisig P2WSH Wallets (sortedmulti)
221
+
222
+ `Wallet.createMultisig` creates a descriptor-based sorted-multisig wallet:
223
+ `wsh(sortedmulti(threshold, key1, key2, ...))`, the interoperable standard
224
+ used by Bitcoin Core, Sparrow and Specter. Derivation follows BIP 48 with
225
+ script type 2 (`m/48'/coin'/account'/2'`, receive `/0/*`, change `/1/*`) and
226
+ public keys are ordered per BIP 67 at every index, so any wallet built from
227
+ the same account xpubs produces identical addresses regardless of the order
228
+ the cosigners were listed in.
229
+
230
+ Cosigners are supplied as account-level extended public keys (`xpub`/`tpub`,
231
+ or the SLIP-132 multisig encodings `Zpub`/`Vpub`, normalized automatically).
232
+ When a mnemonic is provided, this wallet IS one of the cosigners: its BIP 48
233
+ account xpub is derived and included automatically (pass `ourXpub` to assert
234
+ it explicitly; a mismatch is rejected). Omit the mnemonic for a watch-only
235
+ multisig coordinator: the full read-only surface (scanning, balances,
236
+ history, subscriptions) works, signing does not.
237
+
238
+ Spending is PSBT-only. Direct spends (`send`/`sendMany`/`sendMax`) fail with
239
+ the typed `MultisigSpendError` (`code: 'MULTISIG_REQUIRES_PSBT'`). `buildPsbt`
240
+ attaches the `witnessScript` and one `bip32Derivation` entry per cosigner to
241
+ every input; `signPsbtWithOurKey` adds this cosigner's partial signature
242
+ without finalizing; `importSignedPsbt` counts the VALID partial signatures on
243
+ each input against the witnessScript threshold and refuses to finalize below
244
+ it (the error names how many signatures it has and needs).
245
+ `exportDescriptors()` emits the checksummed `wsh(sortedmulti(...))` receive
246
+ and change descriptors for import into Bitcoin Core/Sparrow/Specter; our key
247
+ carries its full key origin, cosigners known only as xpubs carry a
248
+ fingerprint-only origin. Multisig is a library-only feature for now: the
249
+ HTTP daemon wallet stays single-sig.
250
+
251
+ Full 2-of-3 walkthrough:
252
+
253
+ ```typescript
254
+ import { Wallet } from 'beignet';
255
+
256
+ // Each cosigner shares their BIP 48 account xpub (m/48'/0'/0'/2' on mainnet).
257
+ // Grab ours from exportDescriptors() or derive it with any BIP 48 tool.
258
+
259
+ // 1. Cosigner A creates their multisig wallet (A holds mnemonicA).
260
+ const walletA = (
261
+ await Wallet.createMultisig({
262
+ threshold: 2,
263
+ mnemonic: mnemonicA, // we are one cosigner; our xpub is added automatically
264
+ cosigners: [xpubB, xpubC],
265
+ network: 'bitcoin',
266
+ electrumOptions: { net, tls },
267
+ })
268
+ ).value;
269
+
270
+ // Cosigner B does the same in their own instance/machine.
271
+ const walletB = (
272
+ await Wallet.createMultisig({
273
+ threshold: 2,
274
+ mnemonic: mnemonicB,
275
+ cosigners: [xpubA, xpubC],
276
+ network: 'bitcoin',
277
+ electrumOptions: { net, tls },
278
+ })
279
+ ).value;
280
+
281
+ // An optional watch-only coordinator holds no keys at all.
282
+ const coordinator = (
283
+ await Wallet.createMultisig({
284
+ threshold: 2,
285
+ cosigners: [xpubA, xpubB, xpubC],
286
+ network: 'bitcoin',
287
+ electrumOptions: { net, tls },
288
+ })
289
+ ).value;
290
+
291
+ // 2. Fund the multisig: every instance derives the same addresses.
292
+ const deposit = await walletA.getAddress(); // == walletB/coordinator address
293
+
294
+ // 3. Build the unsigned PSBT (works on any instance, coordinator included).
295
+ const built = await walletA.buildPsbt({
296
+ address: 'bc1q...',
297
+ amount: 50000,
298
+ satsPerByte: 4,
299
+ });
300
+ const unsigned = built.value.psbtBase64;
301
+
302
+ // 4. Each cosigner signs their own copy (below threshold nothing finalizes).
303
+ const signedA = walletA.signPsbtWithOurKey(unsigned).value;
304
+ const signedB = walletB.signPsbtWithOurKey(unsigned).value;
305
+
306
+ // 5. Combine the partials, finalize at threshold, broadcast.
307
+ const combined = coordinator.combinePsbts([signedA, signedB]).value;
308
+ const finalized = coordinator.importSignedPsbt(combined).value; // 2-of-3 met
309
+ await coordinator.broadcastTransaction(finalized.txHex);
310
+
311
+ // Importing with only one signature fails loudly:
312
+ // 'Input 0 is below the multisig threshold: have 1 signature(s), need 2.'
313
+
314
+ // Interop: import the wallet into Bitcoin Core/Sparrow/Specter.
315
+ const descriptors = walletA.exportDescriptors().value;
316
+ // wsh(sortedmulti(2,[fp/48h/0h/0h/2h]xpub.../0/*,[fp]xpub.../0/*,...))#checksum
317
+ ```
318
+
319
+ ### Networks, Fee Estimates & Electrum Failover
320
+
321
+ - **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.
322
+ - **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`.
323
+ - **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.
324
+ - **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]`.
325
+
326
+ ### Storage & Encryption
327
+
328
+ 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.
329
+
330
+ 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:
331
+
332
+ ```typescript
333
+ import { createEncryptedStorage, Wallet } from 'beignet';
334
+ import * as bip39 from 'bip39';
335
+
336
+ const seed = bip39.mnemonicToSeedSync(mnemonic);
337
+ const wallet = await Wallet.create({
338
+ mnemonic,
339
+ storage: createEncryptedStorage({ getData, setData }, seed),
340
+ // ...
341
+ });
342
+ ```
343
+
140
344
  ## Lightning Network
141
345
 
142
346
  ### Lightning Quick Start (BeignetNode)
@@ -212,6 +416,85 @@ const readiness = node.getMainnetReadiness();
212
416
  console.log('Score:', readiness.score + '/100', 'Ready:', readiness.ready);
213
417
  ```
214
418
 
419
+ ### Advisor Execution (circular rebalancing + fee auto-tuning)
420
+
421
+ The advisor can also *act*, not just recommend. Both features are **off by
422
+ default** and only run when explicitly enabled in the node options.
423
+
424
+ ```typescript
425
+ // One-shot circular rebalance: self-payment out over fromChannelId and back
426
+ // in over toChannelId. Aborts WITHOUT paying if the route fee > maxFeeSats.
427
+ const result = await node.rebalanceChannel(
428
+ fromChannelId, toChannelId, 50_000, /* maxFeeSats: */ 50
429
+ );
430
+
431
+ // Inspect what the executor would do (read-only)
432
+ const recs = node.getAdvisorRecommendations(); // analyze() + rebalancePlan[]
433
+
434
+ // Run the advisor's rebalance plan under a per-day fee budget
435
+ const summary = await node.executeRebalances(/* budgetSatsPerDay: */ 500);
436
+ ```
437
+
438
+ Automatic modes (opt-in via `BeignetNodeOptions` / `INodeConfig`):
439
+
440
+ ```typescript
441
+ const node = await BeignetNode.create({
442
+ mnemonic,
443
+ // Periodically executes the rebalance plan. Routing fees spent on
444
+ // rebalances are capped at budgetSatsPerDay per UTC day; the running spend
445
+ // is persisted, so restarts never overspend the same day. The budget
446
+ // resets at midnight UTC.
447
+ autoRebalance: { enabled: true, budgetSatsPerDay: 500, minImbalancePct: 20 },
448
+ // Every intervalMs (default 6h) nudges each channel's proportional fee:
449
+ // +25% when outbound is depleted (<20% local) but still forwarding,
450
+ // -25% when the channel saw no forwards in the window, clamped to
451
+ // [floorPpm, ceilPpm]. One adjustment per channel per interval.
452
+ autoTuneFees: { enabled: true, floorPpm: 1, ceilPpm: 5_000 }
453
+ });
454
+ ```
455
+
456
+ Daemon/CLI surfaces: `POST /rebalance`, `GET /advisor/recommendations`,
457
+ `POST /advisor/execute-rebalances`; `beignet rebalance <from> <to> <sats>
458
+ --max-fee <sats>`, `beignet advisor recommendations`, `beignet advisor
459
+ execute-rebalances [--budget <sats>]`.
460
+
461
+ ### Watchtowers
462
+
463
+ Penalty enforcement normally requires this node's own chain monitor to be online:
464
+ if a counterparty broadcasts a revoked commitment while you are offline, nobody
465
+ sweeps the breach. The **watchtower client** closes that gap. At every revocation
466
+ it builds an encrypted *justice kit* (the revoked commitment's breach hint plus a
467
+ pre-signed to_local penalty) and ships it to one or more remote towers over the
468
+ standard BOLT 8 Noise transport. When a tower later sees the breach transaction on
469
+ chain, it decrypts the kit and broadcasts the penalty on your behalf — reclaiming
470
+ the channel even though you never came back online.
471
+
472
+ - **Altruist only.** Sessions use `reward = 0`; towers take no cut. There is no
473
+ server mode (beignet is a tower *client*, not a tower).
474
+ - **LND-tower compatible.** Implements LND's `wtwire` protocol (Init/CreateSession/
475
+ StateUpdate/DeleteSession, message types 600-607) and the version-0 justice blob
476
+ (XChaCha20-Poly1305, breach hint = `SHA256(txid)[:16]`, key = `SHA256(txid‖txid)`),
477
+ so it interoperates with existing public LND altruist towers.
478
+ - **Legacy + anchor channels.** The to_local revocation penalty (the fund-critical
479
+ breach punishment) is packed for both; taproot channels are not yet backed up.
480
+ - **Durable.** Per-tower session state and the un-acked update backlog are persisted
481
+ (encrypted at rest) and drained with exponential backoff on reconnect. An un-acked
482
+ update is never dropped silently.
483
+
484
+ Configure towers as `pubkey@host:port` URIs (off when empty):
485
+
486
+ ```ts
487
+ const node = await BeignetNode.create({
488
+ mnemonic,
489
+ watchtowers: ['03abc...@tower.example.com:9911']
490
+ });
491
+ ```
492
+
493
+ Daemon/CLI surfaces: `GET /watchtowers`, `POST /watchtower/add`,
494
+ `DELETE /watchtower/remove`; `beignet watchtower list`, `beignet watchtower add
495
+ <pubkey@host:port>`, `beignet watchtower remove <uri>`; daemon flag `--watchtower`
496
+ (repeatable) or `BEIGNET_WATCHTOWERS` (comma-separated).
497
+
215
498
  ### HTTP Daemon
216
499
 
217
500
  BeignetNode can also run as an HTTP/SSE daemon for language-agnostic integrations:
@@ -512,7 +795,7 @@ Beignet is under active development. The following features are missing or carry
512
795
 
513
796
  | Feature | Status | Impact |
514
797
  |---------|--------|--------|
515
- | **Watchtowers** | Not implemented | If your node goes offline, a counterparty could theoretically broadcast a revoked state. Mitigate with frequent backups and auto-reconnect. |
798
+ | **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. |
516
799
  | **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. |
517
800
  | **Trampoline routing** | Not implemented | All route computation is local. Cannot delegate pathfinding to a trampoline node. |
518
801
  | **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. |