beignet 0.30.2 → 0.31.1

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 (136) hide show
  1. package/README.md +15 -10
  2. package/dist/cli/beignet-node.js +124 -139
  3. package/dist/cli/beignet-node.js.map +1 -1
  4. package/dist/cli/cli.js +5 -0
  5. package/dist/cli/cli.js.map +1 -1
  6. package/dist/cli/config.js +7 -0
  7. package/dist/cli/config.js.map +1 -1
  8. package/dist/cli/daemon-options.js +2 -0
  9. package/dist/cli/daemon-options.js.map +1 -1
  10. package/dist/cli/daemon.js +29 -10
  11. package/dist/cli/daemon.js.map +1 -1
  12. package/dist/cli/electrum-tls-config.js +105 -0
  13. package/dist/cli/electrum-tls-config.js.map +1 -0
  14. package/dist/cli/offline-receive.js +16 -6
  15. package/dist/cli/offline-receive.js.map +1 -1
  16. package/dist/cli/onchain-sweep.js.map +1 -1
  17. package/dist/cli/openapi.js +1359 -60
  18. package/dist/cli/openapi.js.map +1 -1
  19. package/dist/electrum/index.js +241 -155
  20. package/dist/electrum/index.js.map +1 -1
  21. package/dist/lightning/chain/chain-monitor.js +46 -0
  22. package/dist/lightning/chain/chain-monitor.js.map +1 -1
  23. package/dist/lightning/chain/chain-watcher.js +50 -3
  24. package/dist/lightning/chain/chain-watcher.js.map +1 -1
  25. package/dist/lightning/channel/channel-manager.js +87 -7
  26. package/dist/lightning/channel/channel-manager.js.map +1 -1
  27. package/dist/lightning/channel/channel-state.js.map +1 -1
  28. package/dist/lightning/channel/channel.js +45 -15
  29. package/dist/lightning/channel/channel.js.map +1 -1
  30. package/dist/lightning/channel/htlc-snapshots.js +40 -0
  31. package/dist/lightning/channel/htlc-snapshots.js.map +1 -0
  32. package/dist/lightning/direct-funding/receiver/engine.js +143 -14
  33. package/dist/lightning/direct-funding/receiver/engine.js.map +1 -1
  34. package/dist/lightning/direct-funding/receiver/index.js +2 -1
  35. package/dist/lightning/direct-funding/receiver/index.js.map +1 -1
  36. package/dist/lightning/direct-funding/receiver/types.js +6 -1
  37. package/dist/lightning/direct-funding/receiver/types.js.map +1 -1
  38. package/dist/lightning/direct-funding/receiver/verify.js +23 -1
  39. package/dist/lightning/direct-funding/receiver/verify.js.map +1 -1
  40. package/dist/lightning/gossip/network-graph.js +50 -3
  41. package/dist/lightning/gossip/network-graph.js.map +1 -1
  42. package/dist/lightning/gossip/pathfinding.js +74 -179
  43. package/dist/lightning/gossip/pathfinding.js.map +1 -1
  44. package/dist/lightning/gossip/pay-all.js +4 -7
  45. package/dist/lightning/gossip/pay-all.js.map +1 -1
  46. package/dist/lightning/gossip/types.js.map +1 -1
  47. package/dist/lightning/message/splice.js +11 -36
  48. package/dist/lightning/message/splice.js.map +1 -1
  49. package/dist/lightning/node/lightning-node.js +133 -89
  50. package/dist/lightning/node/lightning-node.js.map +1 -1
  51. package/dist/lightning/offer/merkle.js +3 -21
  52. package/dist/lightning/offer/merkle.js.map +1 -1
  53. package/dist/lightning/recovery/capsule.js +13 -0
  54. package/dist/lightning/recovery/capsule.js.map +1 -1
  55. package/dist/lightning/recovery/frame-codec.js +7 -5
  56. package/dist/lightning/recovery/frame-codec.js.map +1 -1
  57. package/dist/lightning/recovery/journal.js +43 -45
  58. package/dist/lightning/recovery/journal.js.map +1 -1
  59. package/dist/lightning/recovery/recovery-manager.js +22 -0
  60. package/dist/lightning/recovery/recovery-manager.js.map +1 -1
  61. package/dist/lightning/script/htlc-taproot.js +2 -17
  62. package/dist/lightning/script/htlc-taproot.js.map +1 -1
  63. package/dist/lightning/storage/serialization.js +30 -18
  64. package/dist/lightning/storage/serialization.js.map +1 -1
  65. package/dist/lightning/storage/sqlite-storage.js +193 -53
  66. package/dist/lightning/storage/sqlite-storage.js.map +1 -1
  67. package/dist/lightning/storage/types.js.map +1 -1
  68. package/dist/lightning/transport/peer-manager.js +20 -1
  69. package/dist/lightning/transport/peer-manager.js.map +1 -1
  70. package/dist/lightning/watchtower/watchtower-client.js +3 -1
  71. package/dist/lightning/watchtower/watchtower-client.js.map +1 -1
  72. package/dist/shapes/electrum.js +2 -1
  73. package/dist/shapes/electrum.js.map +1 -1
  74. package/dist/transaction/index.js +359 -306
  75. package/dist/transaction/index.js.map +1 -1
  76. package/dist/types/cli/beignet-node.d.ts +11 -13
  77. package/dist/types/cli/electrum-tls-config.d.ts +12 -0
  78. package/dist/types/cli/types.d.ts +9 -1
  79. package/dist/types/electrum/index.d.ts +18 -4
  80. package/dist/types/lightning/chain/chain-monitor.d.ts +4 -0
  81. package/dist/types/lightning/chain/chain-watcher.d.ts +7 -0
  82. package/dist/types/lightning/channel/channel-manager.d.ts +5 -0
  83. package/dist/types/lightning/channel/channel.d.ts +3 -0
  84. package/dist/types/lightning/channel/htlc-snapshots.d.ts +5 -0
  85. package/dist/types/lightning/direct-funding/receiver/engine.d.ts +5 -0
  86. package/dist/types/lightning/direct-funding/receiver/index.d.ts +2 -2
  87. package/dist/types/lightning/direct-funding/receiver/types.d.ts +10 -0
  88. package/dist/types/lightning/direct-funding/receiver/verify.d.ts +12 -0
  89. package/dist/types/lightning/gossip/network-graph.d.ts +7 -1
  90. package/dist/types/lightning/gossip/pathfinding.d.ts +5 -4
  91. package/dist/types/lightning/gossip/types.d.ts +6 -0
  92. package/dist/types/lightning/node/lightning-node.d.ts +6 -3
  93. package/dist/types/lightning/recovery/frame-codec.d.ts +1 -1
  94. package/dist/types/lightning/recovery/journal.d.ts +0 -3
  95. package/dist/types/lightning/recovery/recovery-manager.d.ts +1 -0
  96. package/dist/types/lightning/recovery/types.d.ts +1 -1
  97. package/dist/types/lightning/storage/serialization.d.ts +4 -1
  98. package/dist/types/lightning/storage/sqlite-storage.d.ts +13 -2
  99. package/dist/types/lightning/storage/types.d.ts +5 -0
  100. package/dist/types/lightning/transport/peer-manager.d.ts +3 -0
  101. package/dist/types/transaction/index.d.ts +10 -6
  102. package/dist/types/transaction.js.map +1 -1
  103. package/dist/types/types/transaction.d.ts +1 -0
  104. package/dist/types/types/wallet.d.ts +24 -1
  105. package/dist/types/utils/boost.d.ts +4 -0
  106. package/dist/types/utils/derive-path-cached.d.ts +2 -0
  107. package/dist/types/utils/index.d.ts +1 -0
  108. package/dist/types/utils/multisig.d.ts +5 -0
  109. package/dist/types/utils/node-version.d.ts +3 -0
  110. package/dist/types/utils/tls-verification.d.ts +3 -1
  111. package/dist/types/utils/wallet.d.ts +5 -2
  112. package/dist/types/wallet/constants.d.ts +2 -0
  113. package/dist/types/wallet/index.d.ts +45 -14
  114. package/dist/types/wallet.js.map +1 -1
  115. package/dist/utils/boost.js +12 -0
  116. package/dist/utils/boost.js.map +1 -0
  117. package/dist/utils/derive-path-cached.js +31 -0
  118. package/dist/utils/derive-path-cached.js.map +1 -0
  119. package/dist/utils/index.js +1 -0
  120. package/dist/utils/index.js.map +1 -1
  121. package/dist/utils/multisig.js +16 -5
  122. package/dist/utils/multisig.js.map +1 -1
  123. package/dist/utils/node-version.js +40 -0
  124. package/dist/utils/node-version.js.map +1 -0
  125. package/dist/utils/tls-verification.js +12 -5
  126. package/dist/utils/tls-verification.js.map +1 -1
  127. package/dist/utils/wallet.js +18 -2
  128. package/dist/utils/wallet.js.map +1 -1
  129. package/dist/wallet/constants.js +3 -1
  130. package/dist/wallet/constants.js.map +1 -1
  131. package/dist/wallet/index.js +715 -335
  132. package/dist/wallet/index.js.map +1 -1
  133. package/docs/AI_AGENT_GUIDE.md +2 -0
  134. package/docs/RECOVERY-PROTOCOL.md +2 -0
  135. package/docs/SPLICE-LOCK-DEPTH.md +12 -1
  136. package/package.json +5 -2
package/README.md CHANGED
@@ -7,7 +7,7 @@ Two layers, one mnemonic:
7
7
  - **On-chain wallet:** HD keys, address generation, UTXO tracking, transaction building, PSBT/hardware signing, multisig, watch-only, Electrum connectivity.
8
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.
9
9
 
10
- Requires **Node.js 18+**. MIT licensed.
10
+ Requires **Node.js 22.23.4+, 24.22.1+ or 26.12.1+**. Earlier releases on those lines, and all of Node.js 18 and 20, lack the October 21, 2026 Node.js security fixes; the daemon warns at startup when it runs on one. MIT licensed.
11
11
 
12
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
 
@@ -52,16 +52,16 @@ npm install
52
52
  npm run example
53
53
  ```
54
54
 
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.
55
+ Creates a regtest 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
56
 
57
57
  ```js
58
58
  > wallet.getBalance()
59
59
  > await wallet.getAddress()
60
60
  > await wallet.refreshWallet()
61
- > await wallet.send({ address: 'bc1q...', amount: 10000, satsPerByte: 2 })
61
+ > await wallet.send({ address: 'bcrt1q...', amount: 10000, satsPerByte: 2 })
62
62
  ```
63
63
 
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"`.
64
+ State persists as JSON under `example/walletData/`. Pass a mnemonic to reuse a wallet: `npm run example -- "abandon abandon ... about"`. Pass `testnet` or `mainnet` to change the network.
65
65
 
66
66
  ### 2. Lightning node REPL
67
67
 
@@ -69,7 +69,7 @@ State persists as JSON under `example/walletData/`. Pass a mnemonic as the first
69
69
  npm run example:lightning
70
70
  ```
71
71
 
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
+ Boots a real regtest 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.
73
73
 
74
74
  ```js
75
75
  beignet> await node.getNewAddress() // fund this on-chain, then:
@@ -83,7 +83,7 @@ The flags you will actually reach for (everything after `--`):
83
83
 
84
84
  | Flag | Effect |
85
85
  |------|--------|
86
- | `mainnet` \| `testnet` \| `regtest` | Network, as a bare positional arg (default `mainnet`) |
86
+ | `mainnet` \| `testnet` \| `regtest` | Network, as a bare positional arg (default `regtest`) |
87
87
  | `<12 or 24 words>` | Reuse a mnemonic, as bare positional args (default generates one) |
88
88
  | `--electrum-host <h>` `--electrum-port <p>` | Point at your own Electrum server |
89
89
  | `--alias <name>` | Node alias in `node_announcement` |
@@ -93,6 +93,8 @@ The flags you will actually reach for (everything after `--`):
93
93
  npm run example:lightning -- regtest --electrum-host 127.0.0.1 --electrum-port 60001 --alias mynode
94
94
  ```
95
95
 
96
+ Both examples print the network and the mnemonic before anything else. A generated mnemonic exists only in that output, so write it down before funding the wallet, and pass your own on mainnet.
97
+
96
98
  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).
97
99
 
98
100
  Node state lives in a SQLite DB under `~/.beignet/data/<hash-of-mnemonic>/` (the `--low-level` example uses `example/lightningData/node.db`).
@@ -160,7 +162,7 @@ const res = await Wallet.create({
160
162
  ```
161
163
 
162
164
  - **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
- - **Certificate verification:** by default a TLS Electrum connection is encrypted but the server certificate is not checked, because the client library dials with `rejectUnauthorized: false`. An on-path attacker can therefore stand in for the server. On Node, pass `tls: withTlsVerification(tls)` to accept only certificates that chain to a trusted CA and match the host, and add `{ fingerprints: ['AB:CD:…'] }` to also accept self-signed servers by SHA-256 fingerprint (`openssl x509 -noout -fingerprint -sha256`). The CLI/daemon and React Native do not verify yet.
165
+ - **Certificate verification:** by default a TLS Electrum connection is encrypted but the server certificate is not checked, because the client library dials with `rejectUnauthorized: false`. An on-path attacker can therefore stand in for the server. On Node, pass `tls: withTlsVerification(tls)` to accept only certificates that chain to a trusted CA and match the host, and add `{ fingerprints: ['AB:CD:…'] }` to also accept self-signed servers by SHA-256 fingerprint (`openssl x509 -noout -fingerprint -sha256`). A caller that resolves the host itself and dials the address passes `{ servernames: { [address]: hostName } }` so the check still uses the name. The client library keeps one connection per network for the whole process, shared by every wallet on that network, so wallets in one process should all pass the same `tls`. The daemon verifies when `BEIGNET_ELECTRUM_TLS_VERIFY=true` (see `src/cli/README.md`); it is off by default. React Native does not verify yet.
164
166
  - **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.
165
167
  - **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.
166
168
  - **BIP21:** `encodeBip21({ address, amountSats?, label?, message? })` builds a `bitcoin:` URI.
@@ -291,6 +293,8 @@ coordinator.exportDescriptors();
291
293
 
292
294
  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. The staged send (`transaction`) is written without signing keys, so a key pair handed to `sweepPrivateKey` or `addExternalInputs` never reaches storage, and `send`, `sendMany`, `sendMax`, `buildPsbt` and `sweepPrivateKey` reset the staged send when they return, so a restart never replays an earlier call's recipients.
293
295
 
296
+ `getData` must return `ok(undefined)` for a key it does not hold and `err` only when the read fails. `Wallet.create` returns an error on a failed read rather than start from defaults and save them over the data it could not read; pass `resetOnReadFailure: true` to start from defaults anyway.
297
+
294
298
  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.
295
299
 
296
300
  ```typescript
@@ -403,6 +407,7 @@ Automatic modes, opt-in via `BeignetNodeOptions` / `INodeConfig`:
403
407
  ```typescript
404
408
  const node = await BeignetNode.create({
405
409
  mnemonic,
410
+ network: 'mainnet',
406
411
  // Periodically executes the rebalance plan. Routing fees spent on rebalances
407
412
  // are capped per UTC day and the running spend is persisted, so restarts
408
413
  // never overspend the same day. Resets at midnight UTC.
@@ -428,6 +433,7 @@ Penalty enforcement normally needs this node's chain monitor to be online: if a
428
433
  ```typescript
429
434
  const node = await BeignetNode.create({
430
435
  mnemonic,
436
+ network: 'mainnet',
431
437
  watchtowers: ['03abc...@tower.example.com:9911'] // off when empty
432
438
  });
433
439
  ```
@@ -670,9 +676,8 @@ LightningNode High-level API (EventEmitter)
670
676
 
671
677
  ## Tests
672
678
 
673
- Use **Node.js 20 (20.19+)** for development and `npm ci` to install the locked tools.
674
- The linter and test runner have newer Node requirements than the library API.
675
- CI uses Node.js 20.
679
+ Use **Node.js 24 (24.22.1+)** for development and `npm ci` to install the locked tools.
680
+ CI uses Node.js 24.22.1.
676
681
 
677
682
  The lint configuration keeps the established CommonJS imports, enum aliases,
678
683
  unused catch bindings, `any` warnings and Chai assertion style. Rules removed
@@ -25,13 +25,13 @@ var __importStar = (this && this.__importStar) || function (mod) {
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
26
  exports.BeignetNode = exports.jitInvoiceError = exports.holdInvoiceEvent = exports.jsonSafeEvent = exports.jitReceiveRefusal = exports.leaseRatesRefusal = exports.defaultMaxFeeMsat = exports.DEFAULT_MAX_FEE_FLOOR_SATS = exports.DEFAULT_MAX_FEE_PPM = exports.clampDirectFundingMinimum = exports.DAILY_SPEND_STATE_KEY = exports.MAX_ASYNC_SPEND_CLAIMS = exports.ASYNC_SPEND_CLAIM_TTL_MS = exports.jsonToRouteHops = exports.routeHopsToJson = exports.spliceRefusalError = exports.decodeOfferInput = exports.paymentAdmissionSats = exports.paymentSpendSats = exports.hopFeeMsat = exports.spendLimitSats = exports.decodeInvoiceInput = exports.ENGINE_PAYMENT_ERROR_CODES = exports.parseScid = exports.formatScid = exports.gossipPrimeLatch = exports.defaultDataDirForMnemonic = exports.parseRecoveryMode = void 0;
27
27
  const iroh_config_1 = require("./iroh-config");
28
+ const electrum_tls_config_1 = require("./electrum-tls-config");
28
29
  const iroh_node_1 = require("../lightning/transport/iroh-node");
29
30
  const offline_receive_1 = require("./offline-receive");
30
31
  const ffor_receive_1 = require("./ffor-receive");
31
32
  const path = __importStar(require("path"));
32
33
  const fs = __importStar(require("fs"));
33
34
  const net = __importStar(require("net"));
34
- const tls = __importStar(require("tls"));
35
35
  const dns_1 = require("dns");
36
36
  const instance_lock_1 = require("./instance-lock");
37
37
  const crypto = __importStar(require("crypto"));
@@ -41,6 +41,7 @@ const wallet_2 = require("../shapes/wallet");
41
41
  const logger_1 = require("../logger");
42
42
  const helpers_1 = require("../utils/helpers");
43
43
  const conversion_1 = require("../utils/conversion");
44
+ const boost_1 = require("../utils/boost");
44
45
  const wallet_3 = require("../types/wallet");
45
46
  const wallet_storage_1 = require("./wallet-storage");
46
47
  const onchain_sweep_1 = require("./onchain-sweep");
@@ -347,6 +348,10 @@ const nextUtcMidnight = () => {
347
348
  };
348
349
  const MAX_DIRECT_FUNDING_CHARGES = 1024;
349
350
  const DEFAULT_L402_MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
351
+ const NETWORK_NAMES = ['mainnet', 'testnet', 'signet', 'regtest'];
352
+ function unknownNetwork(network) {
353
+ throw new errors_1.BeignetError('INVALID_PARAMS', `unknown network '${String(network)}'`);
354
+ }
350
355
  const DEFAULT_ELECTRUM = {
351
356
  mainnet: { host: 'fulcrum.bitkit.blocktank.to', port: 8900, useTls: true },
352
357
  testnet: { host: 'electrum.blockstream.info', port: 60002, useTls: true },
@@ -764,12 +769,25 @@ class BeignetNode extends events_1.EventEmitter {
764
769
  static async create(opts = {}) {
765
770
  try {
766
771
  (0, iroh_config_1.validateIrohConfig)(opts);
772
+ (0, electrum_tls_config_1.validateElectrumTlsConfig)(opts);
767
773
  }
768
774
  catch (err) {
769
775
  throw new errors_1.BeignetError('INVALID_PARAMS', err.message);
770
776
  }
777
+ if (!opts.network) {
778
+ throw new errors_1.BeignetError('INVALID_PARAMS', "network is required: 'mainnet', 'testnet', 'signet' or 'regtest'");
779
+ }
780
+ if (!NETWORK_NAMES.includes(opts.network)) {
781
+ throw new errors_1.BeignetError('INVALID_PARAMS', `unknown network '${opts.network}': expected 'mainnet', 'testnet', 'signet' or 'regtest'`);
782
+ }
771
783
  const mnemonic = opts.mnemonic || (0, helpers_1.generateMnemonic)();
772
- const networkName = opts.network || 'mainnet';
784
+ const networkName = opts.network;
785
+ try {
786
+ (0, electrum_tls_config_1.checkElectrumTls)(networkName, opts);
787
+ }
788
+ catch (err) {
789
+ throw new errors_1.BeignetError('INVALID_PARAMS', err.message);
790
+ }
773
791
  const dataDir = opts.dataDir || defaultDataDirForMnemonic(mnemonic);
774
792
  const dirMode = (0, fs_utils_1.ensurePrivateDir)(dataDir);
775
793
  const instance = new BeignetNode(mnemonic, networkName, dataDir);
@@ -973,6 +991,24 @@ class BeignetNode extends events_1.EventEmitter {
973
991
  ipv4: electrumHost
974
992
  });
975
993
  }
994
+ const walletTls = (0, electrum_tls_config_1.electrumTlsModule)(opts, electrumHost, rawElectrumHost);
995
+ try {
996
+ (0, electrum_tls_config_1.claimElectrumTls)(this, networkName, opts);
997
+ }
998
+ catch (err) {
999
+ throw new errors_1.BeignetError('INVALID_PARAMS', err.message);
1000
+ }
1001
+ if (opts.electrumTlsVerify === true) {
1002
+ const pinnedFingerprints = opts.electrumTlsFingerprints?.length ?? 0;
1003
+ this.log('info', 'Verifying Electrum TLS certificates', {
1004
+ pinnedFingerprints
1005
+ });
1006
+ if (!electrumTls) {
1007
+ this.log('warn', 'The configured Electrum server is plain TCP, so its certificate is ' +
1008
+ 'never checked and pinned fingerprints are not used for it; only ' +
1009
+ 'the TLS fallback servers are verified', { host: rawElectrumHost, port: electrumPort, pinnedFingerprints });
1010
+ }
1011
+ }
976
1012
  const beignetNetwork = this.toBeignetNetwork(networkName);
977
1013
  const lnNetwork = this.toLnNetwork(networkName);
978
1014
  const coinType = this.toCoinType(networkName);
@@ -996,7 +1032,7 @@ class BeignetNode extends events_1.EventEmitter {
996
1032
  : {}),
997
1033
  electrumOptions: {
998
1034
  net,
999
- tls,
1035
+ tls: walletTls,
1000
1036
  servers: electrumServer
1001
1037
  },
1002
1038
  disableMessagesOnCreate: true,
@@ -1006,6 +1042,7 @@ class BeignetNode extends events_1.EventEmitter {
1006
1042
  storage: (0, wallet_storage_1.createWalletStorage)(this.storage)
1007
1043
  });
1008
1044
  if (walletResult.isErr()) {
1045
+ (0, electrum_tls_config_1.releaseElectrumTls)(this);
1009
1046
  throw new errors_1.BeignetError('WALLET_CREATE_FAILED', walletResult.error.message);
1010
1047
  }
1011
1048
  this.wallet = walletResult.value;
@@ -3488,12 +3525,15 @@ class BeignetNode extends events_1.EventEmitter {
3488
3525
  this._onchainSendLock = run.catch(() => undefined);
3489
3526
  return run;
3490
3527
  }
3528
+ _runWalletSend(fn) {
3529
+ return this._runOnchainSend(() => this.wallet.withSendLock(fn));
3530
+ }
3491
3531
  async sendOnchain(address, amountSats, satsPerVbyte) {
3492
3532
  this._checkMaxPayment(amountSats);
3493
3533
  this._checkSpendLimit(amountSats);
3494
- return this._runOnchainSend(async () => {
3534
+ return this._runWalletSend(async (locked) => {
3495
3535
  try {
3496
- const result = await this.wallet.send({
3536
+ const result = await locked.send({
3497
3537
  address,
3498
3538
  amount: amountSats,
3499
3539
  broadcast: false,
@@ -3541,9 +3581,9 @@ class BeignetNode extends events_1.EventEmitter {
3541
3581
  }
3542
3582
  }
3543
3583
  this._validateFeeRate(satsPerVbyte);
3544
- return this._runOnchainSend(async () => {
3584
+ return this._runWalletSend(async (locked) => {
3545
3585
  try {
3546
- const result = await this.wallet.buildPsbt({
3586
+ const result = await locked.buildPsbt({
3547
3587
  txs: outputs.map((o) => ({
3548
3588
  address: o.address,
3549
3589
  amount: o.amountSats
@@ -3740,9 +3780,9 @@ class BeignetNode extends events_1.EventEmitter {
3740
3780
  throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, `Invalid ${this.networkName} address: ${address}`);
3741
3781
  }
3742
3782
  this._validateFeeRate(satsPerVbyte);
3743
- return this._runOnchainSend(async () => {
3783
+ return this._runWalletSend(async (locked) => {
3744
3784
  try {
3745
- const result = await this.wallet.sendMax({
3785
+ const result = await locked.sendMax({
3746
3786
  address,
3747
3787
  satsPerByte: satsPerVbyte ?? this.wallet.feeEstimates.normal,
3748
3788
  rbf: this.wallet.rbf,
@@ -3814,7 +3854,7 @@ class BeignetNode extends events_1.EventEmitter {
3814
3854
  });
3815
3855
  }
3816
3856
  prepareOnchainSweep(request) {
3817
- return this._runOnchainSend(() => this.onchainSweeps().prepare(request));
3857
+ return this._runWalletSend(() => this.onchainSweeps().prepare(request));
3818
3858
  }
3819
3859
  submitOnchainSweep(requestId) {
3820
3860
  return this._runOnchainSend(() => this.onchainSweeps().submit(requestId));
@@ -3834,7 +3874,7 @@ class BeignetNode extends events_1.EventEmitter {
3834
3874
  ? `Transaction ${txid} cannot be replaced via RBF; use POST /tx/boost (CPFP) instead`
3835
3875
  : `Transaction ${txid} is not boostable (unknown, already confirmed, or balance too low)`);
3836
3876
  }
3837
- return this._boostRbf(txid, satsPerVbyte);
3877
+ return this._boost(txid, wallet_3.EBoostType.rbf, satsPerVbyte);
3838
3878
  }
3839
3879
  async boostOnchain(txid, satsPerVbyte) {
3840
3880
  this._validateTxid(txid);
@@ -3845,7 +3885,7 @@ class BeignetNode extends events_1.EventEmitter {
3845
3885
  }
3846
3886
  if (can.rbf) {
3847
3887
  try {
3848
- return await this._boostRbf(txid, satsPerVbyte);
3888
+ return await this._boost(txid, wallet_3.EBoostType.rbf, satsPerVbyte);
3849
3889
  }
3850
3890
  catch (e) {
3851
3891
  const rbfImpossible = e instanceof errors_1.BeignetError &&
@@ -3854,94 +3894,34 @@ class BeignetNode extends events_1.EventEmitter {
3854
3894
  throw e;
3855
3895
  }
3856
3896
  }
3857
- return this._boostCpfp(txid, satsPerVbyte);
3858
- }
3859
- async _boostRbf(txid, satsPerVbyte) {
3860
- return this._runOnchainSend(async () => {
3861
- const setup = await this.wallet.transaction.setupRbf({ txid });
3862
- if (setup.isErr()) {
3863
- await this.wallet.resetSendTransaction();
3864
- throw new errors_1.BeignetError(errors_1.BeignetErrorCode.NOT_BOOSTABLE, setup.error.message);
3865
- }
3866
- try {
3867
- if (satsPerVbyte !== undefined) {
3868
- const feeRes = this.wallet.transaction.updateFee({
3869
- satsPerByte: satsPerVbyte
3870
- });
3871
- if (feeRes.isErr()) {
3872
- throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, feeRes.error.message);
3873
- }
3874
- }
3875
- const original = this.wallet.unconfirmedTransactions[txid] ??
3876
- this.wallet.transactions[txid];
3877
- const originalFeeSats = original ? (0, conversion_1.btcToSats)(original.fee) : 0;
3878
- const newFeeSats = this.wallet.transaction.data.fee;
3879
- if (newFeeSats <= originalFeeSats) {
3880
- throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, `Replacement fee ${newFeeSats} sats does not exceed the original fee ${originalFeeSats} sats; raise satsPerVbyte`);
3881
- }
3882
- const createRes = await this.wallet.transaction.createTransaction();
3883
- if (createRes.isErr()) {
3884
- throw new errors_1.BeignetError('SEND_FAILED', createRes.error.message);
3885
- }
3886
- const info = await this._broadcastRawTx(createRes.value.hex);
3887
- await this._recordBoost(txid, info.txid, wallet_3.EBoostType.rbf, newFeeSats);
3888
- return {
3889
- ...info,
3890
- boostType: 'rbf',
3891
- feeSats: newFeeSats,
3892
- originalTxid: txid
3893
- };
3894
- }
3895
- finally {
3896
- await this.wallet.resetSendTransaction();
3897
- }
3898
- });
3897
+ return this._boost(txid, wallet_3.EBoostType.cpfp, satsPerVbyte);
3899
3898
  }
3900
- async _boostCpfp(txid, satsPerVbyte) {
3899
+ _boost(txid, type, satsPerVbyte) {
3901
3900
  return this._runOnchainSend(async () => {
3902
- const setup = await this.wallet.transaction.setupCpfp({
3901
+ const res = await this.wallet.boost({
3903
3902
  txid,
3903
+ type,
3904
3904
  ...(satsPerVbyte !== undefined ? { satsPerByte: satsPerVbyte } : {})
3905
3905
  });
3906
- if (setup.isErr()) {
3907
- await this.wallet.resetSendTransaction();
3908
- throw new errors_1.BeignetError(errors_1.BeignetErrorCode.NOT_BOOSTABLE, setup.error.message);
3909
- }
3910
- try {
3911
- const feeSats = setup.value.fee;
3912
- const createRes = await this.wallet.transaction.createTransaction();
3913
- if (createRes.isErr()) {
3914
- throw new errors_1.BeignetError('SEND_FAILED', createRes.error.message);
3915
- }
3916
- const info = await this._broadcastRawTx(createRes.value.hex);
3917
- await this._recordBoost(txid, info.txid, wallet_3.EBoostType.cpfp, feeSats);
3918
- return {
3919
- ...info,
3920
- boostType: 'cpfp',
3921
- feeSats,
3922
- originalTxid: txid
3923
- };
3924
- }
3925
- finally {
3926
- await this.wallet.resetSendTransaction();
3906
+ if (res.isErr()) {
3907
+ const code = res.error instanceof boost_1.BoostError
3908
+ ? {
3909
+ NOT_BOOSTABLE: errors_1.BeignetErrorCode.NOT_BOOSTABLE,
3910
+ INVALID_FEE: errors_1.BeignetErrorCode.INVALID_PARAMS,
3911
+ SEND_FAILED: errors_1.BeignetErrorCode.SEND_FAILED
3912
+ }[res.error.code]
3913
+ : errors_1.BeignetErrorCode.SEND_FAILED;
3914
+ throw new errors_1.BeignetError(code, res.error.message);
3927
3915
  }
3916
+ return {
3917
+ txid: res.value.id,
3918
+ hex: res.value.hex,
3919
+ boostType: type === wallet_3.EBoostType.rbf ? 'rbf' : 'cpfp',
3920
+ feeSats: res.value.fee,
3921
+ originalTxid: txid
3922
+ };
3928
3923
  });
3929
3924
  }
3930
- async _recordBoost(oldTxId, newTxId, type, fee) {
3931
- const res = await this.wallet.addBoostedTransaction({
3932
- oldTxId,
3933
- newTxId,
3934
- type,
3935
- fee
3936
- });
3937
- if (res.isErr()) {
3938
- this.log('warn', 'Failed to record boosted transaction', {
3939
- oldTxId,
3940
- newTxId,
3941
- error: res.error.message
3942
- });
3943
- }
3944
- }
3945
3925
  listBoostableTransactions() {
3946
3926
  const { rbf, cpfp } = this.wallet.getBoostableTransactions();
3947
3927
  return {
@@ -3958,9 +3938,9 @@ class BeignetNode extends events_1.EventEmitter {
3958
3938
  throw new errors_1.BeignetError(errors_1.BeignetErrorCode.NOTHING_TO_CONSOLIDATE, `Nothing to consolidate: wallet has ${utxoCount} spendable UTXO(s), need at least 2`);
3959
3939
  }
3960
3940
  const address = await this.getNewAddress();
3961
- return this._runOnchainSend(async () => {
3941
+ return this._runWalletSend(async (locked) => {
3962
3942
  try {
3963
- const result = await this.wallet.sendMax({
3943
+ const result = await locked.sendMax({
3964
3944
  address,
3965
3945
  satsPerByte: satsPerVbyte ?? this.wallet.feeEstimates.normal,
3966
3946
  rbf: this.wallet.rbf,
@@ -3992,6 +3972,15 @@ class BeignetNode extends events_1.EventEmitter {
3992
3972
  this.emit('onchain:rbf', { txids: data });
3993
3973
  return;
3994
3974
  }
3975
+ if (key === 'storageWriteFailed') {
3976
+ const failure = data;
3977
+ this.log('error', 'Wallet storage write failed', {
3978
+ key: failure.key,
3979
+ storageKey: failure.storageKey,
3980
+ error: failure.error
3981
+ });
3982
+ return;
3983
+ }
3995
3984
  const relayed = {
3996
3985
  transactionReceived: {
3997
3986
  event: 'transaction:received',
@@ -4412,10 +4401,7 @@ class BeignetNode extends events_1.EventEmitter {
4412
4401
  return this.raceWithTimeout(this.resolveCurrentWalletAddressScript().catch(() => undefined), this._closeAddressLookupTimeoutMs);
4413
4402
  }
4414
4403
  fforChannelId(channelId) {
4415
- if (typeof channelId !== 'string' || !/^[0-9a-fA-F]{64}$/.test(channelId)) {
4416
- throw new errors_1.BeignetError('INVALID_PARAMS', 'channelId must be 64 hex characters');
4417
- }
4418
- const idBuf = Buffer.from(channelId, 'hex');
4404
+ const idBuf = requireChannelIdHex(channelId);
4419
4405
  if (!this.node.getChannel(idBuf)) {
4420
4406
  throw new errors_1.BeignetError(errors_1.BeignetErrorCode.CHANNEL_NOT_FOUND, `Channel not found: ${idBuf.toString('hex')}`);
4421
4407
  }
@@ -4747,6 +4733,9 @@ class BeignetNode extends events_1.EventEmitter {
4747
4733
  }
4748
4734
  throw err;
4749
4735
  });
4736
+ if (r.action === 'force-close-refused') {
4737
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.FORCE_CLOSE_FAILED, r.error ?? 'Force close failed');
4738
+ }
4750
4739
  return {
4751
4740
  action: r.action,
4752
4741
  preimagesKnown: r.preimagesKnown,
@@ -4857,10 +4846,7 @@ class BeignetNode extends events_1.EventEmitter {
4857
4846
  };
4858
4847
  }
4859
4848
  forceCloseChannel(channelId, acceptStaleStateRisk = false) {
4860
- if (!/^[0-9a-fA-F]{64}$/.test(channelId)) {
4861
- throw new errors_1.BeignetError('INVALID_PARAMS', 'channelId must be 64 hex characters');
4862
- }
4863
- const idBuf = Buffer.from(channelId, 'hex');
4849
+ const idBuf = requireChannelIdHex(channelId);
4864
4850
  if (!this.node.getChannel(idBuf)) {
4865
4851
  throw new errors_1.BeignetError(errors_1.BeignetErrorCode.CHANNEL_NOT_FOUND, `Channel not found: ${idBuf.toString('hex')}`);
4866
4852
  }
@@ -4921,23 +4907,23 @@ class BeignetNode extends events_1.EventEmitter {
4921
4907
  : REESTABLISH_FORCE_CLOSE_REFUSAL);
4922
4908
  }
4923
4909
  }
4924
- rebroadcastClose(channelId) {
4925
- return this.node.rebroadcastClose(Buffer.from(channelId, 'hex'));
4910
+ async rebroadcastClose(channelId) {
4911
+ return this.node.rebroadcastClose(requireChannelIdHex(channelId));
4926
4912
  }
4927
4913
  listChannels() {
4928
4914
  return this.node.listChannels().map((ch) => this.toChannelInfo(ch));
4929
4915
  }
4930
4916
  getChannel(channelId) {
4931
- const ch = this.node.getChannel(Buffer.from(channelId, 'hex'));
4917
+ const ch = this.node.getChannel(requireChannelIdHex(channelId));
4932
4918
  if (!ch)
4933
4919
  return null;
4934
4920
  return this.toChannelInfo(ch);
4935
4921
  }
4936
4922
  getChannelHealth(channelId) {
4937
- return this.node.getChannelHealth(Buffer.from(channelId, 'hex'));
4923
+ return this.node.getChannelHealth(requireChannelIdHex(channelId));
4938
4924
  }
4939
4925
  getChannelDiagnostics(channelId) {
4940
- const channelIdBuf = Buffer.from(channelId, 'hex');
4926
+ const channelIdBuf = requireChannelIdHex(channelId);
4941
4927
  const channel = this.node.getChannelManager().getChannel(channelIdBuf);
4942
4928
  if (!channel)
4943
4929
  return null;
@@ -6958,9 +6944,15 @@ class BeignetNode extends events_1.EventEmitter {
6958
6944
  return { valid: true, proof };
6959
6945
  }
6960
6946
  updateChannelFee(channelId, feeratePerKw) {
6961
- return this.node.updateChannelFee(Buffer.from(channelId, 'hex'), feeratePerKw);
6947
+ return this.node.updateChannelFee(requireChannelIdHex(channelId), feeratePerKw);
6962
6948
  }
6963
6949
  updateChannelPolicy(channelId, policy) {
6950
+ return this.applyChannelPolicy(requireChannelIdHex(channelId), [channelId], policy);
6951
+ }
6952
+ updateAllChannelPolicies(policy) {
6953
+ return this.applyChannelPolicy('all', this.node.listChannels().map((ch) => ch.channelId.toString('hex')), policy);
6954
+ }
6955
+ applyChannelPolicy(target, channelIds, policy) {
6964
6956
  const update = {};
6965
6957
  if (policy.feeBaseMsat !== undefined)
6966
6958
  update.feeBaseMsat = policy.feeBaseMsat;
@@ -6972,17 +6964,14 @@ class BeignetNode extends events_1.EventEmitter {
6972
6964
  update.htlcMinimumMsat = requireMsatValue(policy.htlcMinimumMsat, 'htlcMinimumMsat');
6973
6965
  if (policy.htlcMaximumMsat !== undefined)
6974
6966
  update.htlcMaximumMsat = requireMsatValue(policy.htlcMaximumMsat, 'htlcMaximumMsat');
6975
- const targets = channelId === 'all'
6976
- ? this.node.listChannels().map((ch) => ch.channelId.toString('hex'))
6977
- : [channelId];
6978
- this.node.setChannelPolicy(channelId === 'all' ? 'all' : Buffer.from(channelId, 'hex'), update);
6979
- const policies = targets
6967
+ this.node.setChannelPolicy(target, update);
6968
+ const policies = channelIds
6980
6969
  .map((id) => this.getChannelPolicy(id))
6981
6970
  .filter((p) => p !== null);
6982
6971
  return { updated: policies.length, policies };
6983
6972
  }
6984
6973
  getChannelPolicy(channelId) {
6985
- const policy = this.node.getChannelPolicy(Buffer.from(channelId, 'hex'));
6974
+ const policy = this.node.getChannelPolicy(requireChannelIdHex(channelId));
6986
6975
  if (!policy)
6987
6976
  return null;
6988
6977
  return {
@@ -7069,7 +7058,7 @@ class BeignetNode extends events_1.EventEmitter {
7069
7058
  return info;
7070
7059
  }
7071
7060
  async waitForChannelReady(channelId, timeoutMs = 60000) {
7072
- return this.node.waitForChannelReady(Buffer.from(channelId, 'hex'), timeoutMs);
7061
+ return this.node.waitForChannelReady(requireChannelIdHex(channelId), timeoutMs);
7073
7062
  }
7074
7063
  async waitForReady(timeoutMs = 30000) {
7075
7064
  return this.node.waitForReady(timeoutMs);
@@ -7373,21 +7362,17 @@ class BeignetNode extends events_1.EventEmitter {
7373
7362
  paid = settlesInvoice(live);
7374
7363
  }
7375
7364
  else {
7376
- if (!durablePaid)
7377
- durablePaid = this.durablePaidHashes();
7365
+ if (!durablePaid) {
7366
+ durablePaid = this.storage.paymentHashesWith({
7367
+ status: 'COMPLETED',
7368
+ direction: 'INCOMING'
7369
+ });
7370
+ }
7378
7371
  paid = durablePaid.has(inv.paymentHash);
7379
7372
  }
7380
7373
  return this.toInvoiceInfo(inv, paid);
7381
7374
  });
7382
7375
  }
7383
- durablePaidHashes() {
7384
- const paid = new Set();
7385
- for (const { paymentHash, payment } of this.storage.loadAllPayments()) {
7386
- if (settlesInvoice(payment))
7387
- paid.add(paymentHash);
7388
- }
7389
- return paid;
7390
- }
7391
7376
  toInvoiceInfo(inv, paid) {
7392
7377
  const info = {
7393
7378
  bolt11: inv.bolt11,
@@ -7698,10 +7683,8 @@ class BeignetNode extends events_1.EventEmitter {
7698
7683
  async rebalanceChannel(fromChannelId, toChannelId, amountSats, maxFeeSats) {
7699
7684
  if (this.isGraphRestoring())
7700
7685
  await this.whenGraphRestored();
7701
- if (!/^[0-9a-fA-F]{64}$/.test(fromChannelId))
7702
- throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, 'fromChannelId must be 64 hex chars');
7703
- if (!/^[0-9a-fA-F]{64}$/.test(toChannelId))
7704
- throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, 'toChannelId must be 64 hex chars');
7686
+ const fromIdBuf = requireChannelIdHex(fromChannelId, 'fromChannelId');
7687
+ const toIdBuf = requireChannelIdHex(toChannelId, 'toChannelId');
7705
7688
  if (!Number.isInteger(amountSats) || amountSats <= 0)
7706
7689
  throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, 'amountSats must be a positive integer');
7707
7690
  if (!Number.isInteger(maxFeeSats) || maxFeeSats < 0)
@@ -7717,8 +7700,8 @@ class BeignetNode extends events_1.EventEmitter {
7717
7700
  let result;
7718
7701
  try {
7719
7702
  result = await this.node.rebalanceChannel({
7720
- fromChannelId: Buffer.from(fromChannelId, 'hex'),
7721
- toChannelId: Buffer.from(toChannelId, 'hex'),
7703
+ fromChannelId: fromIdBuf,
7704
+ toChannelId: toIdBuf,
7722
7705
  amountSats: BigInt(amountSats),
7723
7706
  maxFeeSats: BigInt(maxFeeSats)
7724
7707
  });
@@ -7907,13 +7890,13 @@ class BeignetNode extends events_1.EventEmitter {
7907
7890
  describeGraph(limit, offset) {
7908
7891
  const cappedLimit = Math.min(Math.max(1, Math.floor(limit ?? GRAPH_DESCRIBE_MAX_LIMIT)), GRAPH_DESCRIBE_MAX_LIMIT);
7909
7892
  const safeOffset = Math.max(0, Math.floor(offset ?? 0));
7910
- const all = this.node.getGraph().getAllChannels();
7893
+ const graph = this.node.getGraph();
7911
7894
  return {
7912
- totalChannels: all.length,
7895
+ totalChannels: graph.getChannelCount(),
7913
7896
  limit: cappedLimit,
7914
7897
  offset: safeOffset,
7915
- channels: all
7916
- .slice(safeOffset, safeOffset + cappedLimit)
7898
+ channels: graph
7899
+ .getChannelPage(safeOffset, cappedLimit)
7917
7900
  .map((ch) => this.toGraphChannelInfo(ch))
7918
7901
  };
7919
7902
  }
@@ -8849,7 +8832,9 @@ class BeignetNode extends events_1.EventEmitter {
8849
8832
  this._nodeStorageView?.close();
8850
8833
  this.removeAllListeners();
8851
8834
  try {
8852
- await this.wallet?.stop();
8835
+ const stopped = await this.wallet?.stop();
8836
+ if (!stopped?.isErr())
8837
+ (0, electrum_tls_config_1.releaseElectrumTls)(this);
8853
8838
  }
8854
8839
  catch {
8855
8840
  }
@@ -8881,7 +8866,7 @@ class BeignetNode extends events_1.EventEmitter {
8881
8866
  case 'signet':
8882
8867
  return wallet_3.EAvailableNetworks.signet;
8883
8868
  default:
8884
- return wallet_3.EAvailableNetworks.bitcoin;
8869
+ return unknownNetwork(network);
8885
8870
  }
8886
8871
  }
8887
8872
  toLnNetwork(network) {
@@ -8895,7 +8880,7 @@ class BeignetNode extends events_1.EventEmitter {
8895
8880
  case 'signet':
8896
8881
  return types_5.Network.SIGNET;
8897
8882
  default:
8898
- return types_5.Network.MAINNET;
8883
+ return unknownNetwork(network);
8899
8884
  }
8900
8885
  }
8901
8886
  toCoinType(network) {
@@ -8909,7 +8894,7 @@ class BeignetNode extends events_1.EventEmitter {
8909
8894
  case 'signet':
8910
8895
  return wallet_keys_1.LnCoinType.SIGNET;
8911
8896
  default:
8912
- return wallet_keys_1.LnCoinType.BITCOIN;
8897
+ return unknownNetwork(network);
8913
8898
  }
8914
8899
  }
8915
8900
  getBitcoinNetwork() {