beignet 0.2.0 → 0.4.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 (215) hide show
  1. package/README.md +306 -1
  2. package/dist/cli/auth.js +238 -0
  3. package/dist/cli/auth.js.map +1 -0
  4. package/dist/cli/beignet-node.js +1034 -57
  5. package/dist/cli/beignet-node.js.map +1 -1
  6. package/dist/cli/cli.js +1257 -18
  7. package/dist/cli/cli.js.map +1 -1
  8. package/dist/cli/config.js +45 -1
  9. package/dist/cli/config.js.map +1 -1
  10. package/dist/cli/daemon.js +398 -38
  11. package/dist/cli/daemon.js.map +1 -1
  12. package/dist/cli/errors.js +2 -0
  13. package/dist/cli/errors.js.map +1 -1
  14. package/dist/cli/index.js +6 -1
  15. package/dist/cli/index.js.map +1 -1
  16. package/dist/cli/openapi.js +1343 -89
  17. package/dist/cli/openapi.js.map +1 -1
  18. package/dist/cli/restore.js +89 -0
  19. package/dist/cli/restore.js.map +1 -0
  20. package/dist/cli/wallet-storage.js +34 -0
  21. package/dist/cli/wallet-storage.js.map +1 -0
  22. package/dist/electrum/index.js +107 -12
  23. package/dist/electrum/index.js.map +1 -1
  24. package/dist/index.js +1 -0
  25. package/dist/index.js.map +1 -1
  26. package/dist/lightning/advisor/fee-tuner.js +42 -0
  27. package/dist/lightning/advisor/fee-tuner.js.map +1 -0
  28. package/dist/lightning/advisor/index.js +9 -1
  29. package/dist/lightning/advisor/index.js.map +1 -1
  30. package/dist/lightning/advisor/rebalance-planner.js +55 -0
  31. package/dist/lightning/advisor/rebalance-planner.js.map +1 -0
  32. package/dist/lightning/backup/index.js +18 -0
  33. package/dist/lightning/backup/index.js.map +1 -0
  34. package/dist/lightning/backup/scb.js +42 -0
  35. package/dist/lightning/backup/scb.js.map +1 -0
  36. package/dist/lightning/chain/chain-monitor.js +27 -0
  37. package/dist/lightning/chain/chain-monitor.js.map +1 -1
  38. package/dist/lightning/chain/output-resolver.js +66 -15
  39. package/dist/lightning/chain/output-resolver.js.map +1 -1
  40. package/dist/lightning/chain/types.js +1 -0
  41. package/dist/lightning/chain/types.js.map +1 -1
  42. package/dist/lightning/channel/channel-manager.js +91 -24
  43. package/dist/lightning/channel/channel-manager.js.map +1 -1
  44. package/dist/lightning/channel/channel-state.js.map +1 -1
  45. package/dist/lightning/channel/channel.js +122 -4
  46. package/dist/lightning/channel/channel.js.map +1 -1
  47. package/dist/lightning/channel/commitment-builder.js +4 -7
  48. package/dist/lightning/channel/commitment-builder.js.map +1 -1
  49. package/dist/lightning/channel/dual-funding.js +6 -5
  50. package/dist/lightning/channel/dual-funding.js.map +1 -1
  51. package/dist/lightning/channel/splice-tx.js.map +1 -1
  52. package/dist/lightning/channel/types.js +3 -1
  53. package/dist/lightning/channel/types.js.map +1 -1
  54. package/dist/lightning/channel/validation.js +3 -3
  55. package/dist/lightning/channel/validation.js.map +1 -1
  56. package/dist/lightning/crypto/index.js +1 -0
  57. package/dist/lightning/crypto/index.js.map +1 -1
  58. package/dist/lightning/crypto/message-signing.js +116 -0
  59. package/dist/lightning/crypto/message-signing.js.map +1 -0
  60. package/dist/lightning/crypto/xchacha20poly1305.js +77 -0
  61. package/dist/lightning/crypto/xchacha20poly1305.js.map +1 -0
  62. package/dist/lightning/features/flags.js +1 -0
  63. package/dist/lightning/features/flags.js.map +1 -1
  64. package/dist/lightning/gossip/messages.js +37 -1
  65. package/dist/lightning/gossip/messages.js.map +1 -1
  66. package/dist/lightning/index.js +2 -1
  67. package/dist/lightning/index.js.map +1 -1
  68. package/dist/lightning/keys/signer.js +24 -3
  69. package/dist/lightning/keys/signer.js.map +1 -1
  70. package/dist/lightning/keys/wallet-keys.js +1 -0
  71. package/dist/lightning/keys/wallet-keys.js.map +1 -1
  72. package/dist/lightning/message/index.js +1 -0
  73. package/dist/lightning/message/index.js.map +1 -1
  74. package/dist/lightning/message/peer-storage.js +43 -0
  75. package/dist/lightning/message/peer-storage.js.map +1 -0
  76. package/dist/lightning/message/types.js +2 -0
  77. package/dist/lightning/message/types.js.map +1 -1
  78. package/dist/lightning/node/lightning-node.js +1593 -236
  79. package/dist/lightning/node/lightning-node.js.map +1 -1
  80. package/dist/lightning/node/types.js +18 -1
  81. package/dist/lightning/node/types.js.map +1 -1
  82. package/dist/lightning/offer/offer-manager.js +2 -5
  83. package/dist/lightning/offer/offer-manager.js.map +1 -1
  84. package/dist/lightning/offer/tlv.js +3 -1
  85. package/dist/lightning/offer/tlv.js.map +1 -1
  86. package/dist/lightning/storage/encryption.js +67 -0
  87. package/dist/lightning/storage/encryption.js.map +1 -0
  88. package/dist/lightning/storage/index.js +1 -0
  89. package/dist/lightning/storage/index.js.map +1 -1
  90. package/dist/lightning/storage/serialization.js +6 -2
  91. package/dist/lightning/storage/serialization.js.map +1 -1
  92. package/dist/lightning/storage/sqlite-storage.js +405 -33
  93. package/dist/lightning/storage/sqlite-storage.js.map +1 -1
  94. package/dist/lightning/watchtower/blob.js +221 -0
  95. package/dist/lightning/watchtower/blob.js.map +1 -0
  96. package/dist/lightning/watchtower/index.js +55 -0
  97. package/dist/lightning/watchtower/index.js.map +1 -0
  98. package/dist/lightning/watchtower/justice.js +272 -0
  99. package/dist/lightning/watchtower/justice.js.map +1 -0
  100. package/dist/lightning/watchtower/tower-connection.js +208 -0
  101. package/dist/lightning/watchtower/tower-connection.js.map +1 -0
  102. package/dist/lightning/watchtower/types.js +3 -0
  103. package/dist/lightning/watchtower/types.js.map +1 -0
  104. package/dist/lightning/watchtower/watchtower-client.js +472 -0
  105. package/dist/lightning/watchtower/watchtower-client.js.map +1 -0
  106. package/dist/lightning/watchtower/wtwire.js +201 -0
  107. package/dist/lightning/watchtower/wtwire.js.map +1 -0
  108. package/dist/logger.js +41 -0
  109. package/dist/logger.js.map +1 -0
  110. package/dist/shapes/electrum.js +32 -1
  111. package/dist/shapes/electrum.js.map +1 -1
  112. package/dist/shapes/wallet.js +11 -1
  113. package/dist/shapes/wallet.js.map +1 -1
  114. package/dist/transaction/index.js +163 -15
  115. package/dist/transaction/index.js.map +1 -1
  116. package/dist/types/cli/auth.d.ts +34 -0
  117. package/dist/types/cli/beignet-node.d.ts +148 -4
  118. package/dist/types/cli/daemon.d.ts +5 -0
  119. package/dist/types/cli/errors.d.ts +2 -0
  120. package/dist/types/cli/index.d.ts +1 -0
  121. package/dist/types/cli/restore.d.ts +10 -0
  122. package/dist/types/cli/types.d.ts +235 -1
  123. package/dist/types/cli/wallet-storage.d.ts +3 -0
  124. package/dist/types/electrum/index.d.ts +12 -0
  125. package/dist/types/electrum.js +1 -0
  126. package/dist/types/electrum.js.map +1 -1
  127. package/dist/types/index.d.ts +1 -0
  128. package/dist/types/lightning/advisor/fee-tuner.d.ts +20 -0
  129. package/dist/types/lightning/advisor/index.d.ts +4 -0
  130. package/dist/types/lightning/advisor/rebalance-planner.d.ts +12 -0
  131. package/dist/types/lightning/backup/index.d.ts +1 -0
  132. package/dist/types/lightning/backup/scb.d.ts +24 -0
  133. package/dist/types/lightning/chain/chain-monitor.d.ts +1 -0
  134. package/dist/types/lightning/chain/types.d.ts +1 -0
  135. package/dist/types/lightning/channel/channel-manager.d.ts +11 -0
  136. package/dist/types/lightning/channel/channel-state.d.ts +2 -0
  137. package/dist/types/lightning/channel/channel.d.ts +11 -5
  138. package/dist/types/lightning/channel/commitment-builder.d.ts +7 -7
  139. package/dist/types/lightning/channel/dual-funding.d.ts +2 -1
  140. package/dist/types/lightning/channel/splice-tx.d.ts +2 -2
  141. package/dist/types/lightning/channel/types.d.ts +2 -0
  142. package/dist/types/lightning/channel/validation.d.ts +1 -1
  143. package/dist/types/lightning/crypto/index.d.ts +1 -0
  144. package/dist/types/lightning/crypto/message-signing.d.ts +9 -0
  145. package/dist/types/lightning/crypto/xchacha20poly1305.d.ts +3 -0
  146. package/dist/types/lightning/features/flags.d.ts +1 -0
  147. package/dist/types/lightning/gossip/messages.d.ts +4 -0
  148. package/dist/types/lightning/index.d.ts +1 -0
  149. package/dist/types/lightning/keys/signer.d.ts +18 -1
  150. package/dist/types/lightning/keys/wallet-keys.d.ts +2 -1
  151. package/dist/types/lightning/message/index.d.ts +1 -0
  152. package/dist/types/lightning/message/peer-storage.d.ts +9 -0
  153. package/dist/types/lightning/message/types.d.ts +2 -0
  154. package/dist/types/lightning/node/lightning-node.d.ts +120 -4
  155. package/dist/types/lightning/node/types.d.ts +66 -1
  156. package/dist/types/lightning/storage/encryption.d.ts +11 -0
  157. package/dist/types/lightning/storage/index.d.ts +1 -0
  158. package/dist/types/lightning/storage/serialization.d.ts +2 -0
  159. package/dist/types/lightning/storage/sqlite-storage.d.ts +43 -3
  160. package/dist/types/lightning/storage/types.d.ts +62 -0
  161. package/dist/types/lightning/watchtower/blob.d.ts +46 -0
  162. package/dist/types/lightning/watchtower/index.d.ts +9 -0
  163. package/dist/types/lightning/watchtower/justice.d.ts +30 -0
  164. package/dist/types/lightning/watchtower/tower-connection.d.ts +34 -0
  165. package/dist/types/lightning/watchtower/types.d.ts +46 -0
  166. package/dist/types/lightning/watchtower/watchtower-client.d.ts +68 -0
  167. package/dist/types/lightning/watchtower/wtwire.d.ts +90 -0
  168. package/dist/types/logger.d.ts +17 -0
  169. package/dist/types/shapes/electrum.d.ts +3 -1
  170. package/dist/types/transaction/index.d.ts +6 -0
  171. package/dist/types/transaction.js.map +1 -1
  172. package/dist/types/types/electrum.d.ts +3 -2
  173. package/dist/types/types/transaction.d.ts +1 -1
  174. package/dist/types/types/wallet.d.ts +80 -4
  175. package/dist/types/utils/descriptors.d.ts +2 -0
  176. package/dist/types/utils/fees.d.ts +3 -0
  177. package/dist/types/utils/helpers.d.ts +3 -1
  178. package/dist/types/utils/index.d.ts +5 -0
  179. package/dist/types/utils/multisig.d.ts +29 -0
  180. package/dist/types/utils/transaction.d.ts +7 -0
  181. package/dist/types/utils/wallet-storage-encryption.d.ts +3 -0
  182. package/dist/types/utils/wallet.d.ts +1 -1
  183. package/dist/types/utils/watch-only.d.ts +12 -0
  184. package/dist/types/wallet/constants.d.ts +1 -0
  185. package/dist/types/wallet/index.d.ts +63 -4
  186. package/dist/types/wallet.js +3 -0
  187. package/dist/types/wallet.js.map +1 -1
  188. package/dist/utils/derivation-path.js +8 -7
  189. package/dist/utils/derivation-path.js.map +1 -1
  190. package/dist/utils/descriptors.js +56 -0
  191. package/dist/utils/descriptors.js.map +1 -0
  192. package/dist/utils/electrum.js +14 -4
  193. package/dist/utils/electrum.js.map +1 -1
  194. package/dist/utils/fees.js +19 -0
  195. package/dist/utils/fees.js.map +1 -0
  196. package/dist/utils/helpers.js +22 -5
  197. package/dist/utils/helpers.js.map +1 -1
  198. package/dist/utils/index.js +5 -0
  199. package/dist/utils/index.js.map +1 -1
  200. package/dist/utils/multisig.js +90 -0
  201. package/dist/utils/multisig.js.map +1 -0
  202. package/dist/utils/transaction.js +30 -3
  203. package/dist/utils/transaction.js.map +1 -1
  204. package/dist/utils/wallet-storage-encryption.js +45 -0
  205. package/dist/utils/wallet-storage-encryption.js.map +1 -0
  206. package/dist/utils/wallet.js +3 -1
  207. package/dist/utils/wallet.js.map +1 -1
  208. package/dist/utils/watch-only.js +102 -0
  209. package/dist/utils/watch-only.js.map +1 -0
  210. package/dist/wallet/constants.js +2 -1
  211. package/dist/wallet/constants.js.map +1 -1
  212. package/dist/wallet/index.js +810 -51
  213. package/dist/wallet/index.js.map +1 -1
  214. package/docs/ROADMAP.md +365 -0
  215. package/package.json +1 -1
@@ -23,7 +23,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
23
23
  return result;
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.BeignetNode = exports.defaultDataDirForMnemonic = void 0;
26
+ exports.BeignetNode = exports.jsonToRouteHops = exports.routeHopsToJson = exports.parseScid = exports.formatScid = exports.defaultDataDirForMnemonic = void 0;
27
27
  const path = __importStar(require("path"));
28
28
  const fs = __importStar(require("fs"));
29
29
  const net = __importStar(require("net"));
@@ -33,19 +33,25 @@ const instance_lock_1 = require("./instance-lock");
33
33
  const crypto = __importStar(require("crypto"));
34
34
  const events_1 = require("events");
35
35
  const wallet_1 = require("../wallet");
36
+ const logger_1 = require("../logger");
36
37
  const helpers_1 = require("../utils/helpers");
37
38
  const conversion_1 = require("../utils/conversion");
38
39
  const wallet_2 = require("../types/wallet");
40
+ const wallet_storage_1 = require("./wallet-storage");
39
41
  const electrum_1 = require("../types/electrum");
40
42
  const lightning_node_1 = require("../lightning/node/lightning-node");
41
43
  const wallet_funding_provider_1 = require("../lightning/wallet/wallet-funding-provider");
42
44
  const sqlite_storage_1 = require("../lightning/storage/sqlite-storage");
45
+ const encryption_1 = require("../lightning/storage/encryption");
46
+ const scb_1 = require("../lightning/backup/scb");
47
+ const bip39 = __importStar(require("bip39"));
43
48
  const rapid_sync_1 = require("../lightning/gossip/rapid-sync");
44
49
  const messages_1 = require("../lightning/gossip/messages");
50
+ const types_1 = require("../lightning/gossip/types");
45
51
  const electrum_backend_1 = require("../lightning/chain/electrum-backend");
46
- const types_1 = require("../lightning/invoice/types");
52
+ const types_2 = require("../lightning/invoice/types");
47
53
  const wallet_keys_1 = require("../lightning/keys/wallet-keys");
48
- const types_2 = require("../lightning/channel/types");
54
+ const types_3 = require("../lightning/channel/types");
49
55
  const decode_1 = require("../lightning/invoice/decode");
50
56
  const decode_2 = require("../lightning/offer/decode");
51
57
  const errors_1 = require("./errors");
@@ -60,9 +66,73 @@ function defaultDataDirForMnemonic(mnemonic, baseDir = DEFAULT_DATA_DIR) {
60
66
  return path.join(baseDir, walletTag);
61
67
  }
62
68
  exports.defaultDataDirForMnemonic = defaultDataDirForMnemonic;
69
+ function formatScid(scid) {
70
+ const { block, txIndex, outputIndex } = (0, types_1.decodeShortChannelId)(scid);
71
+ return `${block}x${txIndex}x${outputIndex}`;
72
+ }
73
+ exports.formatScid = formatScid;
74
+ function parseScid(scid) {
75
+ const human = scid.match(/^(\d+)x(\d+)x(\d+)$/);
76
+ if (human) {
77
+ return (0, types_1.encodeShortChannelId)({
78
+ block: parseInt(human[1], 10),
79
+ txIndex: parseInt(human[2], 10),
80
+ outputIndex: parseInt(human[3], 10)
81
+ });
82
+ }
83
+ if (/^[0-9a-fA-F]{16}$/.test(scid)) {
84
+ return Buffer.from(scid, 'hex');
85
+ }
86
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, `Invalid short channel id: ${scid} (expected <block>x<txIndex>x<output> or 16-char hex)`);
87
+ }
88
+ exports.parseScid = parseScid;
89
+ function routeHopsToJson(route) {
90
+ return route.hops.map((hop, idx) => {
91
+ const feeMsat = idx < route.hops.length - 1
92
+ ? hop.amountToForwardMsat - route.hops[idx + 1].amountToForwardMsat
93
+ : 0n;
94
+ return {
95
+ pubkey: hop.pubkey.toString('hex'),
96
+ shortChannelId: formatScid(hop.shortChannelId),
97
+ amountToForwardMsat: hop.amountToForwardMsat.toString(),
98
+ outgoingCltvValue: hop.outgoingCltvValue,
99
+ feeMsat: feeMsat.toString(),
100
+ cltvExpiryDelta: hop.cltvExpiryDelta
101
+ };
102
+ });
103
+ }
104
+ exports.routeHopsToJson = routeHopsToJson;
105
+ function jsonToRouteHops(hops) {
106
+ return hops.map((hop, idx) => {
107
+ if (!hop.pubkey || !/^[0-9a-fA-F]{66}$/.test(hop.pubkey)) {
108
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, `Route hop ${idx}: pubkey must be a 33-byte hex string`);
109
+ }
110
+ let amountToForwardMsat;
111
+ try {
112
+ amountToForwardMsat = BigInt(hop.amountToForwardMsat);
113
+ }
114
+ catch {
115
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, `Route hop ${idx}: amountToForwardMsat must be a decimal string`);
116
+ }
117
+ if (typeof hop.outgoingCltvValue !== 'number' ||
118
+ !Number.isInteger(hop.outgoingCltvValue) ||
119
+ hop.outgoingCltvValue < 0) {
120
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, `Route hop ${idx}: outgoingCltvValue must be a non-negative integer`);
121
+ }
122
+ return {
123
+ pubkey: Buffer.from(hop.pubkey, 'hex'),
124
+ shortChannelId: parseScid(hop.shortChannelId),
125
+ amountToForwardMsat,
126
+ outgoingCltvValue: hop.outgoingCltvValue
127
+ };
128
+ });
129
+ }
130
+ exports.jsonToRouteHops = jsonToRouteHops;
131
+ const GRAPH_DESCRIBE_MAX_LIMIT = 500;
63
132
  const DEFAULT_ELECTRUM = {
64
133
  mainnet: { host: 'fulcrum.bitkit.blocktank.to', port: 8900, useTls: true },
65
134
  testnet: { host: 'electrum.blockstream.info', port: 60002, useTls: true },
135
+ signet: { host: 'mempool.space', port: 60602, useTls: true },
66
136
  regtest: { host: '34.65.252.32', port: 18483, useTls: false }
67
137
  };
68
138
  async function resolveHostToIPv4(host) {
@@ -76,13 +146,6 @@ async function resolveHostToIPv4(host) {
76
146
  return host;
77
147
  }
78
148
  }
79
- const LOG_PRIORITY = {
80
- debug: 0,
81
- info: 1,
82
- warn: 2,
83
- error: 3,
84
- silent: 4
85
- };
86
149
  class BeignetNode extends events_1.EventEmitter {
87
150
  on(event, listener) {
88
151
  return super.on(event, listener);
@@ -106,18 +169,25 @@ class BeignetNode extends events_1.EventEmitter {
106
169
  this._failoverInProgress = false;
107
170
  this._connectTimeoutMs = 15000;
108
171
  this._dailySpentSats = 0;
172
+ this._dailySpentLightningSats = 0;
173
+ this._dailySpentOnchainSats = 0;
109
174
  this._dailySpendResetTime = 0;
110
175
  this._pendingSpendSats = 0;
111
176
  this._draining = false;
177
+ this.peerStorageEnabled = true;
178
+ this._peerRetrievedScb = null;
112
179
  this.mnemonic = mnemonic;
113
180
  this.networkName = networkName;
114
181
  this.dataDir = dataDir;
115
182
  }
116
183
  log(level, message, data) {
117
- if (LOG_PRIORITY[level] < LOG_PRIORITY[this.logLevel])
184
+ if (logger_1.LOG_LEVEL_PRIORITY[level] < logger_1.LOG_LEVEL_PRIORITY[this.logLevel])
118
185
  return;
119
186
  const entry = { level, message, data, timestamp: Date.now() };
120
187
  this.emit('log', entry);
188
+ if (this.logger && level !== 'silent') {
189
+ this.logger[level](message, data);
190
+ }
121
191
  }
122
192
  static async create(opts = {}) {
123
193
  const mnemonic = opts.mnemonic || (0, helpers_1.generateMnemonic)();
@@ -131,7 +201,10 @@ class BeignetNode extends events_1.EventEmitter {
131
201
  async init(opts) {
132
202
  if (opts.logLevel)
133
203
  this.logLevel = opts.logLevel;
204
+ if (opts.logger)
205
+ this.logger = opts.logger;
134
206
  this.autoGossipSync = opts.autoGossipSync ?? true;
207
+ this.peerStorageEnabled = opts.peerStorageEnabled ?? true;
135
208
  this.rapidGossipSync = opts.rapidGossipSync ?? true;
136
209
  this.rapidGossipSyncUrl = opts.rapidGossipSyncUrl;
137
210
  const networkName = this.networkName;
@@ -149,7 +222,11 @@ class BeignetNode extends events_1.EventEmitter {
149
222
  const beignetNetwork = this.toBeignetNetwork(networkName);
150
223
  const lnNetwork = this.toLnNetwork(networkName);
151
224
  const coinType = this.toCoinType(networkName);
152
- const chainHash = networkName === 'regtest' ? types_2.REGTEST_CHAIN_HASH : types_2.BITCOIN_CHAIN_HASH;
225
+ let chainHash = types_3.BITCOIN_CHAIN_HASH;
226
+ if (networkName === 'regtest')
227
+ chainHash = types_3.REGTEST_CHAIN_HASH;
228
+ if (networkName === 'signet')
229
+ chainHash = types_3.SIGNET_CHAIN_HASH;
153
230
  if (!opts.allowMultipleInstances) {
154
231
  const lockPath = path.join(this.dataDir, `${networkName}.lock`);
155
232
  try {
@@ -169,16 +246,24 @@ class BeignetNode extends events_1.EventEmitter {
169
246
  if (!opts.dataDir) {
170
247
  const legacyDb = path.join(DEFAULT_DATA_DIR, `${networkName}.db`);
171
248
  if (fs.existsSync(legacyDb) && !fs.existsSync(dbPath)) {
172
- console.warn(`[beignet] Found a legacy shared database at ${legacyDb}. ` +
249
+ const legacyMsg = `[beignet] Found a legacy shared database at ${legacyDb}. ` +
173
250
  `Storage is now per-wallet (${dbPath}), so it is no longer auto-loaded. ` +
174
- `If it held this wallet's channels, re-run with dataDir set to "${DEFAULT_DATA_DIR}" to use it.`);
251
+ `If it held this wallet's channels, re-run with dataDir set to "${DEFAULT_DATA_DIR}" to use it.`;
252
+ this.log('warn', legacyMsg);
253
+ if (!this.logger) {
254
+ console.warn(legacyMsg);
255
+ }
175
256
  }
176
257
  }
258
+ let encryptionKey;
259
+ if (opts.storageEncryption ?? true) {
260
+ encryptionKey = (0, encryption_1.deriveStorageKey)(bip39.mnemonicToSeedSync(this.mnemonic));
261
+ }
177
262
  this.storage = new sqlite_storage_1.SqliteStorage(dbPath, (err) => {
178
263
  this.log('warn', 'Skipped corrupted storage row during load', {
179
264
  error: err instanceof Error ? err.message : String(err)
180
265
  });
181
- });
266
+ }, encryptionKey ? { encryptionKey } : undefined);
182
267
  this.storage.open();
183
268
  const electrumServer = {
184
269
  host: electrumHost,
@@ -189,12 +274,18 @@ class BeignetNode extends events_1.EventEmitter {
189
274
  const walletResult = await wallet_1.Wallet.create({
190
275
  mnemonic: this.mnemonic,
191
276
  network: beignetNetwork,
277
+ ...(opts.feeEstimationSource
278
+ ? { feeEstimationSource: opts.feeEstimationSource }
279
+ : {}),
192
280
  electrumOptions: {
193
281
  net,
194
282
  tls,
195
283
  servers: electrumServer
196
284
  },
197
- disableMessagesOnCreate: true
285
+ disableMessagesOnCreate: true,
286
+ ...(this.logger ? { logger: this.logger } : {}),
287
+ rbf: true,
288
+ storage: (0, wallet_storage_1.createWalletStorage)(this.storage)
198
289
  });
199
290
  if (walletResult.isErr()) {
200
291
  throw new errors_1.BeignetError('WALLET_CREATE_FAILED', walletResult.error.message);
@@ -293,10 +384,16 @@ class BeignetNode extends events_1.EventEmitter {
293
384
  announcedAddresses,
294
385
  fundingProvider,
295
386
  preferAnchors: opts.preferAnchors,
387
+ largeChannels: opts.largeChannels,
296
388
  chainBackend: electrumBackend,
297
389
  feeEstimator: electrumBackend,
390
+ logger: this.logger,
298
391
  sweepDestinationScript,
299
- socks5Proxy
392
+ socks5Proxy,
393
+ peerStorageEnabled: this.peerStorageEnabled,
394
+ autoRebalance: opts.autoRebalance,
395
+ autoTuneFees: opts.autoTuneFees,
396
+ watchtowers: opts.watchtowers
300
397
  });
301
398
  if (!sweepDestinationScript) {
302
399
  this.scheduleSweepAddressRefresh();
@@ -344,13 +441,79 @@ class BeignetNode extends events_1.EventEmitter {
344
441
  this.node.on('channel:ready', (data) => {
345
442
  const channelId = data.channelId.toString('hex');
346
443
  this.log('info', 'Channel ready', { channelId });
444
+ this.refreshStaticChannelBackup();
347
445
  this.emit('channel:ready', { channelId });
348
446
  });
349
447
  this.node.on('channel:closed', (data) => {
350
448
  const channelId = data.channelId.toString('hex');
351
449
  this.log('info', 'Channel closed', { channelId });
450
+ this.refreshStaticChannelBackup();
352
451
  this.emit('channel:closed', { channelId });
353
452
  });
453
+ this.node.on('channel:resolved', () => {
454
+ this.refreshStaticChannelBackup();
455
+ });
456
+ this.node.on('channel:opening', (data) => {
457
+ const channelId = data.channelId.toString('hex');
458
+ const fundingTxid = data.fundingTxid.toString('hex');
459
+ this.log('info', 'Channel opening', { channelId, fundingTxid });
460
+ this.emit('channel:opening', { channelId, fundingTxid });
461
+ });
462
+ this.node.on('channel:pending-close', (data) => {
463
+ const channelId = data.channelId.toString('hex');
464
+ this.log('info', 'Channel pending close', {
465
+ channelId,
466
+ initiator: data.initiator
467
+ });
468
+ this.emit('channel:pending-close', {
469
+ channelId,
470
+ initiator: data.initiator
471
+ });
472
+ });
473
+ this.node.on('channel:force-closing', (data) => {
474
+ const channelId = data.channelId.toString('hex');
475
+ this.log('warn', 'Channel force-closing', {
476
+ channelId,
477
+ initiator: data.initiator
478
+ });
479
+ this.emit('channel:force-closing', {
480
+ channelId,
481
+ initiator: data.initiator
482
+ });
483
+ });
484
+ this.node.on('invoice:settled', (data) => {
485
+ const info = {
486
+ paymentHash: data.paymentHash.toString('hex'),
487
+ bolt11: data.bolt11,
488
+ amountSats: Number(data.amountMsat / 1000n)
489
+ };
490
+ this.log('info', 'Invoice settled', {
491
+ paymentHash: info.paymentHash,
492
+ amountSats: info.amountSats
493
+ });
494
+ this.emit('invoice:settled', info);
495
+ });
496
+ this.node.on('htlc:forwarded', (data) => {
497
+ this.emit('htlc:forwarded', {
498
+ inChannelId: data.inChannelId.toString('hex'),
499
+ outChannelId: data.outChannelId.toString('hex'),
500
+ amountInMsat: data.amountInMsat.toString(),
501
+ amountOutMsat: data.amountOutMsat.toString(),
502
+ feeMsat: data.feeMsat.toString()
503
+ });
504
+ });
505
+ this.node.on('htlc:fulfilled', (data) => {
506
+ this.emit('htlc:fulfilled', {
507
+ channelId: data.channelId.toString('hex'),
508
+ htlcId: data.htlcId.toString()
509
+ });
510
+ });
511
+ this.node.on('htlc:failed', (data) => {
512
+ this.emit('htlc:failed', {
513
+ channelId: data.channelId.toString('hex'),
514
+ htlcId: data.htlcId.toString()
515
+ });
516
+ });
354
517
  this.node.on('peer:connect', (pubkey) => {
355
518
  this.log('debug', 'Peer connected', { pubkey });
356
519
  if (this.autoGossipSync) {
@@ -378,6 +541,10 @@ class BeignetNode extends events_1.EventEmitter {
378
541
  this.log('info', 'Node ready');
379
542
  this.emit('node:ready');
380
543
  });
544
+ this.node.on('peer_storage:retrieved', (peerPubkey, blob) => this.handleRetrievedPeerStorage(peerPubkey, blob));
545
+ if (this.peerStorageEnabled) {
546
+ this.refreshStaticChannelBackup();
547
+ }
381
548
  try {
382
549
  await electrumBackend.estimateFee(6);
383
550
  }
@@ -489,6 +656,21 @@ class BeignetNode extends events_1.EventEmitter {
489
656
  getMnemonic() {
490
657
  return this.mnemonic;
491
658
  }
659
+ signMessage(message) {
660
+ if (typeof message !== 'string' || message.length === 0) {
661
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, 'message required');
662
+ }
663
+ return {
664
+ signature: this.node.signMessage(message),
665
+ pubkey: this.node.getNodeId()
666
+ };
667
+ }
668
+ verifyMessage(message, signature) {
669
+ if (typeof message !== 'string' || typeof signature !== 'string') {
670
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, 'message and signature required');
671
+ }
672
+ return this.node.verifyMessage(message, signature);
673
+ }
492
674
  getBalance() {
493
675
  const onchain = this.wallet.getBalance();
494
676
  const lnBalance = this.node.getBalance();
@@ -498,9 +680,9 @@ class BeignetNode extends events_1.EventEmitter {
498
680
  }
499
681
  getPendingCloseBalanceSats() {
500
682
  const recovering = new Set([
501
- types_2.ChannelState.FORCE_CLOSED,
502
- types_2.ChannelState.SHUTTING_DOWN,
503
- types_2.ChannelState.NEGOTIATING_CLOSING
683
+ types_3.ChannelState.FORCE_CLOSED,
684
+ types_3.ChannelState.SHUTTING_DOWN,
685
+ types_3.ChannelState.NEGOTIATING_CLOSING
504
686
  ]);
505
687
  let totalMsat = 0n;
506
688
  for (const ch of this.node.listChannels()) {
@@ -513,7 +695,7 @@ class BeignetNode extends events_1.EventEmitter {
513
695
  getErroredBalanceSats() {
514
696
  let totalMsat = 0n;
515
697
  for (const ch of this.node.listChannels()) {
516
- if (ch.state === types_2.ChannelState.ERRORED) {
698
+ if (ch.state === types_3.ChannelState.ERRORED) {
517
699
  totalMsat += ch.localBalanceMsat;
518
700
  }
519
701
  }
@@ -584,16 +766,96 @@ class BeignetNode extends events_1.EventEmitter {
584
766
  return result.value.addressIndex.address;
585
767
  }
586
768
  async sendOnchain(address, amountSats, satsPerVbyte) {
769
+ this._checkSpendLimit(amountSats);
587
770
  const result = await this.wallet.send({
588
771
  address,
589
772
  amount: amountSats,
590
773
  broadcast: false,
774
+ rbf: this.wallet.rbf,
591
775
  ...(satsPerVbyte !== undefined ? { satsPerByte: satsPerVbyte } : {})
592
776
  });
593
777
  if (result.isErr()) {
594
778
  throw new errors_1.BeignetError('SEND_FAILED', result.error.message);
595
779
  }
596
- const hex = result.value;
780
+ if (this._dailySpendLimitSats === undefined) {
781
+ return this._broadcastRawTx(result.value);
782
+ }
783
+ const totalSats = this._builtOnchainTotalSats(amountSats);
784
+ try {
785
+ this._checkSpendLimit(totalSats);
786
+ }
787
+ catch (e) {
788
+ await this.wallet.resetSendTransaction();
789
+ throw e;
790
+ }
791
+ this._pendingSpendSats += totalSats;
792
+ try {
793
+ const info = await this._broadcastRawTx(result.value);
794
+ this._recordSpend(totalSats, 'onchain');
795
+ return info;
796
+ }
797
+ finally {
798
+ this._pendingSpendSats -= totalSats;
799
+ }
800
+ }
801
+ async buildPsbt(outputs, satsPerVbyte) {
802
+ if (!Array.isArray(outputs) || outputs.length === 0) {
803
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, 'outputs array required');
804
+ }
805
+ for (const output of outputs) {
806
+ if (!output?.address || typeof output.amountSats !== 'number') {
807
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, 'each output requires address and amountSats');
808
+ }
809
+ }
810
+ this._validateFeeRate(satsPerVbyte);
811
+ const result = await this.wallet.buildPsbt({
812
+ txs: outputs.map((o) => ({ address: o.address, amount: o.amountSats })),
813
+ rbf: this.wallet.rbf,
814
+ ...(satsPerVbyte !== undefined ? { satsPerByte: satsPerVbyte } : {})
815
+ });
816
+ if (result.isErr()) {
817
+ throw new errors_1.BeignetError('PSBT_BUILD_FAILED', result.error.message);
818
+ }
819
+ const built = result.value;
820
+ return {
821
+ psbtBase64: built.psbtBase64,
822
+ feeSats: built.fee,
823
+ vsizeEstimate: built.vsizeEstimate,
824
+ satsPerVbyte: built.satsPerByte,
825
+ inputs: built.inputs.map((input) => ({
826
+ txid: input.tx_hash,
827
+ vout: input.tx_pos,
828
+ address: input.address,
829
+ valueSats: input.value,
830
+ path: input.path
831
+ })),
832
+ outputs: built.outputs.map((output) => ({
833
+ address: output.address,
834
+ valueSats: output.value
835
+ }))
836
+ };
837
+ }
838
+ importSignedPsbt(psbtBase64) {
839
+ if (!psbtBase64 || typeof psbtBase64 !== 'string') {
840
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, 'psbtBase64 required');
841
+ }
842
+ const result = this.wallet.importSignedPsbt(psbtBase64);
843
+ if (result.isErr()) {
844
+ throw new errors_1.BeignetError('PSBT_IMPORT_FAILED', result.error.message);
845
+ }
846
+ return { txid: result.value.txid, txHex: result.value.txHex };
847
+ }
848
+ combinePsbts(psbts) {
849
+ if (!Array.isArray(psbts) || psbts.length < 2) {
850
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, 'psbts array with at least two entries required');
851
+ }
852
+ const result = this.wallet.combinePsbts(psbts);
853
+ if (result.isErr()) {
854
+ throw new errors_1.BeignetError('PSBT_COMBINE_FAILED', result.error.message);
855
+ }
856
+ return { psbtBase64: result.value };
857
+ }
858
+ async _broadcastRawTx(hex) {
597
859
  const bitcoin = await Promise.resolve().then(() => __importStar(require('bitcoinjs-lib')));
598
860
  const tx = bitcoin.Transaction.fromHex(hex);
599
861
  const broadcastRes = await this.wallet.electrum.broadcastTransaction({
@@ -604,15 +866,217 @@ class BeignetNode extends events_1.EventEmitter {
604
866
  }
605
867
  return { txid: tx.getId(), hex };
606
868
  }
869
+ _validateFeeRate(satsPerVbyte, required = false) {
870
+ if (satsPerVbyte === undefined) {
871
+ if (required) {
872
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, 'satsPerVbyte required');
873
+ }
874
+ return;
875
+ }
876
+ if (typeof satsPerVbyte !== 'number' ||
877
+ !Number.isFinite(satsPerVbyte) ||
878
+ satsPerVbyte <= 0) {
879
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, 'satsPerVbyte must be a positive number');
880
+ }
881
+ }
882
+ _validateTxid(txid) {
883
+ if (typeof txid !== 'string' || !/^[0-9a-fA-F]{64}$/.test(txid)) {
884
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, 'txid must be a 64-character hex string');
885
+ }
886
+ }
887
+ async sendMaxOnchain(address, satsPerVbyte) {
888
+ if (!address ||
889
+ typeof address !== 'string' ||
890
+ !this.wallet.validateAddress(address)) {
891
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, `Invalid ${this.networkName} address: ${address}`);
892
+ }
893
+ this._validateFeeRate(satsPerVbyte);
894
+ const result = await this.wallet.sendMax({
895
+ address,
896
+ satsPerByte: satsPerVbyte ?? this.wallet.feeEstimates.normal,
897
+ rbf: this.wallet.rbf,
898
+ broadcast: false
899
+ });
900
+ if (result.isErr()) {
901
+ throw new errors_1.BeignetError('SEND_FAILED', result.error.message);
902
+ }
903
+ if (this._dailySpendLimitSats === undefined) {
904
+ return this._broadcastRawTx(result.value);
905
+ }
906
+ const totalSats = this.wallet.transaction.getTransactionInputValue({
907
+ inputs: this.wallet.transaction.data.inputs
908
+ });
909
+ if (totalSats <= 0) {
910
+ await this.wallet.resetSendTransaction();
911
+ throw new errors_1.BeignetError('SPENDING_LIMIT_EXCEEDED', 'Unable to determine the swept amount for the daily spend limit check; refusing to send');
912
+ }
913
+ try {
914
+ this._checkSpendLimit(totalSats);
915
+ }
916
+ catch (e) {
917
+ await this.wallet.resetSendTransaction();
918
+ throw e;
919
+ }
920
+ this._pendingSpendSats += totalSats;
921
+ try {
922
+ const info = await this._broadcastRawTx(result.value);
923
+ this._recordSpend(totalSats, 'onchain');
924
+ return info;
925
+ }
926
+ finally {
927
+ this._pendingSpendSats -= totalSats;
928
+ }
929
+ }
930
+ async bumpFeeOnchain(txid, satsPerVbyte) {
931
+ this._validateTxid(txid);
932
+ this._validateFeeRate(satsPerVbyte, true);
933
+ const can = this.wallet.canBoost(txid);
934
+ if (!can.rbf) {
935
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.NOT_BOOSTABLE, can.cpfp
936
+ ? `Transaction ${txid} cannot be replaced via RBF; use POST /tx/boost (CPFP) instead`
937
+ : `Transaction ${txid} is not boostable (unknown, already confirmed, or balance too low)`);
938
+ }
939
+ return this._boostRbf(txid, satsPerVbyte);
940
+ }
941
+ async boostOnchain(txid, satsPerVbyte) {
942
+ this._validateTxid(txid);
943
+ this._validateFeeRate(satsPerVbyte);
944
+ const can = this.wallet.canBoost(txid);
945
+ if (!can.canBoost) {
946
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.NOT_BOOSTABLE, `Transaction ${txid} is not boostable (unknown, already confirmed, or balance too low)`);
947
+ }
948
+ if (can.rbf) {
949
+ try {
950
+ return await this._boostRbf(txid, satsPerVbyte);
951
+ }
952
+ catch (e) {
953
+ const rbfImpossible = e instanceof errors_1.BeignetError &&
954
+ e.code === errors_1.BeignetErrorCode.NOT_BOOSTABLE;
955
+ if (!(rbfImpossible && can.cpfp))
956
+ throw e;
957
+ }
958
+ }
959
+ return this._boostCpfp(txid, satsPerVbyte);
960
+ }
961
+ async _boostRbf(txid, satsPerVbyte) {
962
+ const setup = await this.wallet.transaction.setupRbf({ txid });
963
+ if (setup.isErr()) {
964
+ await this.wallet.resetSendTransaction();
965
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.NOT_BOOSTABLE, setup.error.message);
966
+ }
967
+ try {
968
+ if (satsPerVbyte !== undefined) {
969
+ const feeRes = this.wallet.transaction.updateFee({
970
+ satsPerByte: satsPerVbyte
971
+ });
972
+ if (feeRes.isErr()) {
973
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, feeRes.error.message);
974
+ }
975
+ }
976
+ const original = this.wallet.unconfirmedTransactions[txid] ??
977
+ this.wallet.transactions[txid];
978
+ const originalFeeSats = original ? (0, conversion_1.btcToSats)(original.fee) : 0;
979
+ const newFeeSats = this.wallet.transaction.data.fee;
980
+ if (newFeeSats <= originalFeeSats) {
981
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, `Replacement fee ${newFeeSats} sats does not exceed the original fee ${originalFeeSats} sats; raise satsPerVbyte`);
982
+ }
983
+ const createRes = await this.wallet.transaction.createTransaction();
984
+ if (createRes.isErr()) {
985
+ throw new errors_1.BeignetError('SEND_FAILED', createRes.error.message);
986
+ }
987
+ const info = await this._broadcastRawTx(createRes.value.hex);
988
+ await this._recordBoost(txid, info.txid, wallet_2.EBoostType.rbf, newFeeSats);
989
+ return {
990
+ ...info,
991
+ boostType: 'rbf',
992
+ feeSats: newFeeSats,
993
+ originalTxid: txid
994
+ };
995
+ }
996
+ finally {
997
+ await this.wallet.resetSendTransaction();
998
+ }
999
+ }
1000
+ async _boostCpfp(txid, satsPerVbyte) {
1001
+ const setup = await this.wallet.transaction.setupCpfp({
1002
+ txid,
1003
+ ...(satsPerVbyte !== undefined ? { satsPerByte: satsPerVbyte } : {})
1004
+ });
1005
+ if (setup.isErr()) {
1006
+ await this.wallet.resetSendTransaction();
1007
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.NOT_BOOSTABLE, setup.error.message);
1008
+ }
1009
+ try {
1010
+ const feeSats = setup.value.fee;
1011
+ const createRes = await this.wallet.transaction.createTransaction();
1012
+ if (createRes.isErr()) {
1013
+ throw new errors_1.BeignetError('SEND_FAILED', createRes.error.message);
1014
+ }
1015
+ const info = await this._broadcastRawTx(createRes.value.hex);
1016
+ await this._recordBoost(txid, info.txid, wallet_2.EBoostType.cpfp, feeSats);
1017
+ return {
1018
+ ...info,
1019
+ boostType: 'cpfp',
1020
+ feeSats,
1021
+ originalTxid: txid
1022
+ };
1023
+ }
1024
+ finally {
1025
+ await this.wallet.resetSendTransaction();
1026
+ }
1027
+ }
1028
+ async _recordBoost(oldTxId, newTxId, type, fee) {
1029
+ const res = await this.wallet.addBoostedTransaction({
1030
+ oldTxId,
1031
+ newTxId,
1032
+ type,
1033
+ fee
1034
+ });
1035
+ if (res.isErr()) {
1036
+ this.log('warn', 'Failed to record boosted transaction', {
1037
+ oldTxId,
1038
+ newTxId,
1039
+ error: res.error.message
1040
+ });
1041
+ }
1042
+ }
1043
+ listBoostableTransactions() {
1044
+ const { rbf, cpfp } = this.wallet.getBoostableTransactions();
1045
+ return {
1046
+ rbf: rbf.map((tx) => this.toOnchainTxInfo(tx)),
1047
+ cpfp: cpfp.map((tx) => this.toOnchainTxInfo(tx))
1048
+ };
1049
+ }
1050
+ async consolidateUtxos(satsPerVbyte) {
1051
+ this._validateFeeRate(satsPerVbyte);
1052
+ const utxoCount = this.wallet
1053
+ .listUtxos()
1054
+ .filter((u) => !this.wallet.isUtxoFrozen(u.tx_hash, u.tx_pos)).length;
1055
+ if (utxoCount < 2) {
1056
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.NOTHING_TO_CONSOLIDATE, `Nothing to consolidate: wallet has ${utxoCount} spendable UTXO(s), need at least 2`);
1057
+ }
1058
+ const address = await this.getNewAddress();
1059
+ const result = await this.wallet.sendMax({
1060
+ address,
1061
+ satsPerByte: satsPerVbyte ?? this.wallet.feeEstimates.normal,
1062
+ rbf: this.wallet.rbf,
1063
+ broadcast: false
1064
+ });
1065
+ if (result.isErr()) {
1066
+ throw new errors_1.BeignetError('SEND_FAILED', result.error.message);
1067
+ }
1068
+ const feeSats = this.wallet.transaction.data.fee;
1069
+ const info = await this._broadcastRawTx(result.value);
1070
+ return { ...info, utxosConsolidated: utxoCount, address, feeSats };
1071
+ }
607
1072
  async refreshWallet() {
608
1073
  const result = await this.wallet.refreshWallet({});
609
1074
  if (result.isErr()) {
610
1075
  throw new errors_1.BeignetError('REFRESH_FAILED', result.error.message);
611
1076
  }
612
1077
  }
613
- listOnchainTransactions() {
614
- return Object.values(this.wallet.transactions)
615
- .map((tx) => ({
1078
+ toOnchainTxInfo(tx) {
1079
+ return {
616
1080
  txid: tx.txid,
617
1081
  type: tx.type === wallet_2.EPaymentType.sent
618
1082
  ? 'sent'
@@ -627,7 +1091,11 @@ class BeignetNode extends events_1.EventEmitter {
627
1091
  ...(tx.confirmTimestamp !== undefined
628
1092
  ? { confirmTimestamp: tx.confirmTimestamp }
629
1093
  : {})
630
- }))
1094
+ };
1095
+ }
1096
+ listOnchainTransactions() {
1097
+ return Object.values(this.wallet.transactions)
1098
+ .map((tx) => this.toOnchainTxInfo(tx))
631
1099
  .sort((a, b) => b.timestamp - a.timestamp);
632
1100
  }
633
1101
  listUtxos() {
@@ -636,9 +1104,43 @@ class BeignetNode extends events_1.EventEmitter {
636
1104
  vout: utxo.tx_pos,
637
1105
  address: utxo.address,
638
1106
  valueSats: utxo.value,
639
- height: utxo.height
1107
+ height: utxo.height,
1108
+ frozen: this.wallet.isUtxoFrozen(utxo.tx_hash, utxo.tx_pos)
640
1109
  }));
641
1110
  }
1111
+ async freezeUtxo(txid, index) {
1112
+ this._validateTxid(txid);
1113
+ const res = await this.wallet.freezeUtxo({ txid, index });
1114
+ if (res.isErr()) {
1115
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, res.error.message);
1116
+ }
1117
+ return { frozen: `${txid}:${index}` };
1118
+ }
1119
+ async unfreezeUtxo(txid, index) {
1120
+ this._validateTxid(txid);
1121
+ const res = await this.wallet.unfreezeUtxo({ txid, index });
1122
+ if (res.isErr()) {
1123
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, res.error.message);
1124
+ }
1125
+ return { unfrozen: `${txid}:${index}` };
1126
+ }
1127
+ async setAddressLabel(address, label) {
1128
+ const res = await this.wallet.setAddressLabel(address, label);
1129
+ if (res.isErr()) {
1130
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, res.error.message);
1131
+ }
1132
+ return { address, label };
1133
+ }
1134
+ listAddressLabels() {
1135
+ return this.wallet.listAddressLabels();
1136
+ }
1137
+ exportDescriptors() {
1138
+ const res = this.wallet.exportDescriptors();
1139
+ if (res.isErr()) {
1140
+ throw new errors_1.BeignetError('DESCRIPTOR_EXPORT_FAILED', res.error.message);
1141
+ }
1142
+ return res.value;
1143
+ }
642
1144
  async getFeeEstimates() {
643
1145
  try {
644
1146
  return await this.wallet.getFeeEstimates();
@@ -654,10 +1156,13 @@ class BeignetNode extends events_1.EventEmitter {
654
1156
  return this.wallet;
655
1157
  }
656
1158
  async connectPeer(pubkey, host, port) {
1159
+ const target = host !== undefined && port !== undefined
1160
+ ? `${host}:${port}`
1161
+ : 'resolved address (gossip graph / DNS bootstrap)';
657
1162
  let timer;
658
1163
  const connectPromise = this.node.connectPeer(pubkey, host, port);
659
1164
  const timeoutPromise = new Promise((_, reject) => {
660
- timer = setTimeout(() => reject(new errors_1.BeignetError('CONNECT_TIMEOUT', `connectPeer timed out after ${this._connectTimeoutMs}ms (is ${host}:${port} the peer's P2P address?)`)), this._connectTimeoutMs);
1165
+ timer = setTimeout(() => reject(new errors_1.BeignetError('CONNECT_TIMEOUT', `connectPeer timed out after ${this._connectTimeoutMs}ms (is ${target} the peer's P2P address?)`)), this._connectTimeoutMs);
661
1166
  });
662
1167
  try {
663
1168
  await Promise.race([connectPromise, timeoutPromise]);
@@ -665,13 +1170,19 @@ class BeignetNode extends events_1.EventEmitter {
665
1170
  catch (err) {
666
1171
  if (err instanceof errors_1.BeignetError)
667
1172
  throw err;
668
- throw new errors_1.BeignetError('CONNECT_FAILED', `Failed to connect to ${pubkey.slice(0, 16)}…@${host}:${port}: ${err.message}`);
1173
+ throw new errors_1.BeignetError('CONNECT_FAILED', `Failed to connect to ${pubkey.slice(0, 16)}…@${target}: ${err.message}`);
669
1174
  }
670
1175
  finally {
671
1176
  if (timer)
672
1177
  clearTimeout(timer);
673
1178
  }
674
- return { pubkey, host, port, state: 'connected' };
1179
+ const connected = this.node.listPeers().find((p) => p.pubkey === pubkey);
1180
+ return {
1181
+ pubkey,
1182
+ host: host ?? connected?.host ?? '',
1183
+ port: port ?? connected?.port ?? 0,
1184
+ state: 'connected'
1185
+ };
675
1186
  }
676
1187
  disconnectPeer(pubkey) {
677
1188
  this.node.disconnectPeer(pubkey);
@@ -701,6 +1212,8 @@ class BeignetNode extends events_1.EventEmitter {
701
1212
  });
702
1213
  }
703
1214
  }
1215
+ if (synced.length > 0)
1216
+ this._lastGraphSyncAt = Date.now();
704
1217
  return synced;
705
1218
  }
706
1219
  async syncRapidGossip() {
@@ -712,6 +1225,7 @@ class BeignetNode extends events_1.EventEmitter {
712
1225
  this.log('info', 'Rapid gossip sync: downloading snapshot', { url });
713
1226
  const data = await (0, rapid_sync_1.fetchRapidGossipSnapshot)(url);
714
1227
  const result = this.node.loadRapidGossipSnapshot(data);
1228
+ this._lastGraphSyncAt = Date.now();
715
1229
  this.log('info', 'Rapid gossip sync complete', {
716
1230
  channelsAdded: result.channelsAdded,
717
1231
  updatesApplied: result.updatesApplied,
@@ -733,6 +1247,7 @@ class BeignetNode extends events_1.EventEmitter {
733
1247
  const state = channel.getFullState();
734
1248
  const balances = channel.getBalances();
735
1249
  const channelId = state.channelId || state.temporaryChannelId;
1250
+ this.refreshStaticChannelBackup();
736
1251
  return {
737
1252
  channelId: channelId.toString('hex'),
738
1253
  peerPubkey: pubkey,
@@ -740,7 +1255,7 @@ class BeignetNode extends events_1.EventEmitter {
740
1255
  localBalanceSats: Number(balances.localMsat / 1000n),
741
1256
  remoteBalanceSats: Number(balances.remoteMsat / 1000n),
742
1257
  capacitySats: amountSats,
743
- isAnchor: (0, types_2.isAnchorChannel)(state.channelType)
1258
+ isAnchor: (0, types_3.isAnchorChannel)(state.channelType)
744
1259
  };
745
1260
  }
746
1261
  async openChannelAndWait(pubkey, amountSats, opts) {
@@ -882,7 +1397,7 @@ class BeignetNode extends events_1.EventEmitter {
882
1397
  localBalanceSats: Number(ch.localBalanceMsat / 1000n),
883
1398
  remoteBalanceSats: Number(ch.remoteBalanceMsat / 1000n),
884
1399
  capacitySats: Number(ch.fundingSatoshis),
885
- isAnchor: (0, types_2.isAnchorChannel)(ch.channelType ?? null)
1400
+ isAnchor: (0, types_3.isAnchorChannel)(ch.channelType ?? null)
886
1401
  };
887
1402
  if (ch.fundingTxid)
888
1403
  info.fundingTxid = ch.fundingTxid;
@@ -894,6 +1409,16 @@ class BeignetNode extends events_1.EventEmitter {
894
1409
  info.htlcCount = ch.htlcCount;
895
1410
  if (ch.isPrivate !== undefined)
896
1411
  info.isPrivate = ch.isPrivate;
1412
+ if (ch.feeBaseMsat !== undefined)
1413
+ info.feeBaseMsat = ch.feeBaseMsat;
1414
+ if (ch.feeProportionalMillionths !== undefined)
1415
+ info.feeProportionalMillionths = ch.feeProportionalMillionths;
1416
+ if (ch.cltvExpiryDelta !== undefined)
1417
+ info.cltvExpiryDelta = ch.cltvExpiryDelta;
1418
+ if (ch.htlcMinimumMsat !== undefined)
1419
+ info.htlcMinimumMsat = ch.htlcMinimumMsat.toString();
1420
+ if (ch.htlcMaximumMsat !== undefined)
1421
+ info.htlcMaximumMsat = ch.htlcMaximumMsat.toString();
897
1422
  return info;
898
1423
  }
899
1424
  createInvoice(amountSats, description, expirySecs, descriptionHash) {
@@ -916,6 +1441,76 @@ class BeignetNode extends events_1.EventEmitter {
916
1441
  info.expiry = expirySecs;
917
1442
  return info;
918
1443
  }
1444
+ createHoldInvoice(opts) {
1445
+ if (!/^[0-9a-fA-F]{64}$/.test(opts.paymentHash)) {
1446
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, 'paymentHash must be 32 bytes hex (64 hex chars)');
1447
+ }
1448
+ const amountMsat = opts.amountMsat ??
1449
+ (opts.amountSats !== undefined && opts.amountSats !== 0
1450
+ ? BigInt(opts.amountSats) * 1000n
1451
+ : undefined);
1452
+ const result = this.node.createInvoice({
1453
+ amountMsat,
1454
+ description: opts.description || '',
1455
+ expiry: opts.expiry,
1456
+ hold: true,
1457
+ paymentHash: Buffer.from(opts.paymentHash, 'hex')
1458
+ });
1459
+ const info = {
1460
+ bolt11: result.bolt11,
1461
+ paymentHash: result.paymentHash.toString('hex'),
1462
+ paymentSecret: result.paymentSecret.toString('hex')
1463
+ };
1464
+ if (amountMsat !== undefined)
1465
+ info.amountSats = Number(amountMsat / 1000n);
1466
+ if (opts.expiry !== undefined)
1467
+ info.expiry = opts.expiry;
1468
+ return info;
1469
+ }
1470
+ settleHoldInvoice(preimage) {
1471
+ if (!/^[0-9a-fA-F]{64}$/.test(preimage)) {
1472
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, 'preimage must be 32 bytes hex (64 hex chars)');
1473
+ }
1474
+ const preimageBuf = Buffer.from(preimage, 'hex');
1475
+ const paymentHash = crypto
1476
+ .createHash('sha256')
1477
+ .update(preimageBuf)
1478
+ .digest();
1479
+ const settled = this.node.settleHeldHtlc(paymentHash, preimageBuf);
1480
+ if (!settled) {
1481
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.NOT_FOUND, 'No parked HTLCs for this preimage (invoice unknown, not yet paid, or already resolved)');
1482
+ }
1483
+ return { paymentHash: paymentHash.toString('hex') };
1484
+ }
1485
+ cancelHoldInvoice(paymentHash) {
1486
+ if (!/^[0-9a-fA-F]{64}$/.test(paymentHash)) {
1487
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, 'paymentHash must be 32 bytes hex (64 hex chars)');
1488
+ }
1489
+ const result = this.node.cancelHoldInvoice(Buffer.from(paymentHash, 'hex'));
1490
+ if (!result) {
1491
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.NOT_FOUND, 'No open hold invoice for this payment hash');
1492
+ }
1493
+ return { paymentHash, htlcsFailed: result.htlcsFailed };
1494
+ }
1495
+ listHoldInvoices() {
1496
+ return this.node.listHoldInvoices().map((inv) => {
1497
+ const info = {
1498
+ paymentHash: inv.paymentHash,
1499
+ bolt11: inv.bolt11,
1500
+ state: inv.state,
1501
+ heldAmountMsat: inv.heldAmountMsat.toString(),
1502
+ htlcCount: inv.htlcCount,
1503
+ expiry: inv.expiry,
1504
+ createdAt: inv.createdAt
1505
+ };
1506
+ if (inv.amountMsat !== undefined) {
1507
+ info.amountSats = Number(inv.amountMsat / 1000n);
1508
+ }
1509
+ if (inv.description)
1510
+ info.description = inv.description;
1511
+ return info;
1512
+ });
1513
+ }
919
1514
  decodeInvoice(bolt11) {
920
1515
  const inv = (0, decode_1.decode)(bolt11);
921
1516
  const result = {
@@ -967,6 +1562,8 @@ class BeignetNode extends events_1.EventEmitter {
967
1562
  tomorrow.setUTCHours(24, 0, 0, 0);
968
1563
  this._dailySpendResetTime = tomorrow.getTime();
969
1564
  this._dailySpentSats = 0;
1565
+ this._dailySpentLightningSats = 0;
1566
+ this._dailySpentOnchainSats = 0;
970
1567
  }
971
1568
  }
972
1569
  _checkSpendLimit(amountSats) {
@@ -986,10 +1583,26 @@ class BeignetNode extends events_1.EventEmitter {
986
1583
  throw new errors_1.BeignetError('SPENDING_LIMIT_EXCEEDED', `Payment amount ${amountSats} sats exceeds per-payment limit of ${this._maxPaymentSats} sats`);
987
1584
  }
988
1585
  }
989
- _recordSpend(amountSats) {
1586
+ _recordSpend(amountSats, source = 'lightning') {
990
1587
  if (this._dailySpendLimitSats === undefined)
991
1588
  return;
992
1589
  this._dailySpentSats += amountSats;
1590
+ if (source === 'onchain') {
1591
+ this._dailySpentOnchainSats += amountSats;
1592
+ }
1593
+ else {
1594
+ this._dailySpentLightningSats += amountSats;
1595
+ }
1596
+ }
1597
+ _builtOnchainTotalSats(amountSats) {
1598
+ const feeSats = this.wallet.transaction?.data?.fee;
1599
+ if (!Number.isFinite(feeSats) || feeSats < 0) {
1600
+ if (this._dailySpendLimitSats !== undefined) {
1601
+ throw new errors_1.BeignetError('SPENDING_LIMIT_EXCEEDED', 'Unable to determine the transaction fee for the daily spend limit check; refusing to send');
1602
+ }
1603
+ return amountSats;
1604
+ }
1605
+ return amountSats + feeSats;
993
1606
  }
994
1607
  getDailySpendInfo() {
995
1608
  this._resetDailySpendIfNeeded();
@@ -998,7 +1611,10 @@ class BeignetNode extends events_1.EventEmitter {
998
1611
  limitSats: limit,
999
1612
  spentSats: this._dailySpentSats,
1000
1613
  remainingSats: limit !== null ? Math.max(0, limit - this._dailySpentSats) : Infinity,
1001
- resetsAt: this._dailySpendResetTime
1614
+ resetsAt: this._dailySpendResetTime,
1615
+ totalSats: this._dailySpentSats,
1616
+ lightningSats: this._dailySpentLightningSats,
1617
+ onchainSats: this._dailySpentOnchainSats
1002
1618
  };
1003
1619
  }
1004
1620
  setDraining(enabled) {
@@ -1543,6 +2159,36 @@ class BeignetNode extends events_1.EventEmitter {
1543
2159
  return null;
1544
2160
  return this.toPaymentInfo(p);
1545
2161
  }
2162
+ listForwards(filter) {
2163
+ return this.node.listForwards(filter).map((f) => ({
2164
+ id: f.id,
2165
+ settledAt: f.settledAt,
2166
+ inChannelId: f.inChannelId,
2167
+ outChannelId: f.outChannelId,
2168
+ inScid: f.inScid,
2169
+ outScid: f.outScid,
2170
+ amountInMsat: f.amountInMsat.toString(),
2171
+ amountOutMsat: f.amountOutMsat.toString(),
2172
+ feeMsat: f.feeMsat.toString()
2173
+ }));
2174
+ }
2175
+ getForwardingSummary(since) {
2176
+ const summary = this.node.getForwardingSummary(since !== undefined ? { since } : undefined);
2177
+ return {
2178
+ count: summary.count,
2179
+ volumeOutMsat: summary.volumeOutMsat.toString(),
2180
+ feesEarnedMsat: summary.feesEarnedMsat.toString()
2181
+ };
2182
+ }
2183
+ listWatchtowers() {
2184
+ return this.node.getWatchtowers();
2185
+ }
2186
+ addWatchtower(uri) {
2187
+ this.node.addWatchtower(uri);
2188
+ }
2189
+ removeWatchtower(uri) {
2190
+ this.node.removeWatchtower(uri);
2191
+ }
1546
2192
  getPaymentProof(paymentHash) {
1547
2193
  const proof = this.node.getPaymentProof(Buffer.from(paymentHash, 'hex'));
1548
2194
  if (!proof)
@@ -1579,6 +2225,41 @@ class BeignetNode extends events_1.EventEmitter {
1579
2225
  updateChannelFee(channelId, feeratePerKw) {
1580
2226
  return this.node.updateChannelFee(Buffer.from(channelId, 'hex'), feeratePerKw);
1581
2227
  }
2228
+ updateChannelPolicy(channelId, policy) {
2229
+ const update = {};
2230
+ if (policy.feeBaseMsat !== undefined)
2231
+ update.feeBaseMsat = policy.feeBaseMsat;
2232
+ if (policy.feeProportionalMillionths !== undefined)
2233
+ update.feeProportionalMillionths = policy.feeProportionalMillionths;
2234
+ if (policy.cltvExpiryDelta !== undefined)
2235
+ update.cltvExpiryDelta = policy.cltvExpiryDelta;
2236
+ if (policy.htlcMinimumMsat !== undefined)
2237
+ update.htlcMinimumMsat = BigInt(policy.htlcMinimumMsat);
2238
+ if (policy.htlcMaximumMsat !== undefined)
2239
+ update.htlcMaximumMsat = BigInt(policy.htlcMaximumMsat);
2240
+ const targets = channelId === 'all'
2241
+ ? this.node.listChannels().map((ch) => ch.channelId.toString('hex'))
2242
+ : [channelId];
2243
+ this.node.setChannelPolicy(channelId === 'all' ? 'all' : Buffer.from(channelId, 'hex'), update);
2244
+ const policies = targets
2245
+ .map((id) => this.getChannelPolicy(id))
2246
+ .filter((p) => p !== null);
2247
+ return { updated: policies.length, policies };
2248
+ }
2249
+ getChannelPolicy(channelId) {
2250
+ const policy = this.node.getChannelPolicy(Buffer.from(channelId, 'hex'));
2251
+ if (!policy)
2252
+ return null;
2253
+ return {
2254
+ channelId,
2255
+ feeBaseMsat: policy.feeBaseMsat,
2256
+ feeProportionalMillionths: policy.feeProportionalMillionths,
2257
+ cltvExpiryDelta: policy.cltvExpiryDelta,
2258
+ htlcMinimumMsat: policy.htlcMinimumMsat.toString(),
2259
+ htlcMaximumMsat: policy.htlcMaximumMsat.toString(),
2260
+ source: policy.source
2261
+ };
2262
+ }
1582
2263
  cancelPayment(paymentHash) {
1583
2264
  this.node.failPayment(Buffer.from(paymentHash, 'hex'));
1584
2265
  return { ok: true };
@@ -1662,6 +2343,7 @@ class BeignetNode extends events_1.EventEmitter {
1662
2343
  const state = channel.getFullState();
1663
2344
  const balances = channel.getBalances();
1664
2345
  const channelId = state.channelId || state.temporaryChannelId;
2346
+ this.refreshStaticChannelBackup();
1665
2347
  return {
1666
2348
  channelId: channelId.toString('hex'),
1667
2349
  peerPubkey,
@@ -1669,7 +2351,7 @@ class BeignetNode extends events_1.EventEmitter {
1669
2351
  localBalanceSats: Number(balances.localMsat / 1000n),
1670
2352
  remoteBalanceSats: Number(balances.remoteMsat / 1000n),
1671
2353
  capacitySats: amountSats,
1672
- isAnchor: (0, types_2.isAnchorChannel)(state.channelType)
2354
+ isAnchor: (0, types_3.isAnchorChannel)(state.channelType)
1673
2355
  };
1674
2356
  }
1675
2357
  openChannelV2(peerPubkey, params) {
@@ -1682,6 +2364,7 @@ class BeignetNode extends events_1.EventEmitter {
1682
2364
  const state = channel.getFullState();
1683
2365
  const balances = channel.getBalances();
1684
2366
  const channelId = state.channelId || state.temporaryChannelId;
2367
+ this.refreshStaticChannelBackup();
1685
2368
  return {
1686
2369
  channelId: channelId.toString('hex'),
1687
2370
  peerPubkey,
@@ -1689,16 +2372,20 @@ class BeignetNode extends events_1.EventEmitter {
1689
2372
  localBalanceSats: Number(balances.localMsat / 1000n),
1690
2373
  remoteBalanceSats: Number(balances.remoteMsat / 1000n),
1691
2374
  capacitySats: params.amountSats,
1692
- isAnchor: (0, types_2.isAnchorChannel)(state.channelType)
2375
+ isAnchor: (0, types_3.isAnchorChannel)(state.channelType)
1693
2376
  };
1694
2377
  }
1695
2378
  spliceIn(channelId, amountSats, feeratePerkw) {
1696
2379
  const idBuf = Buffer.from(channelId, 'hex');
1697
- return this.node.spliceIn(idBuf, BigInt(amountSats), feeratePerkw);
2380
+ const result = this.node.spliceIn(idBuf, BigInt(amountSats), feeratePerkw);
2381
+ this.refreshStaticChannelBackup();
2382
+ return result;
1698
2383
  }
1699
2384
  spliceOut(channelId, amountSats, feeratePerkw) {
1700
2385
  const idBuf = Buffer.from(channelId, 'hex');
1701
- return this.node.spliceOut(idBuf, BigInt(amountSats), feeratePerkw);
2386
+ const result = this.node.spliceOut(idBuf, BigInt(amountSats), feeratePerkw);
2387
+ this.refreshStaticChannelBackup();
2388
+ return result;
1702
2389
  }
1703
2390
  decodeOfferString(offerStr) {
1704
2391
  const offer = (0, decode_2.decodeOffer)(offerStr);
@@ -1915,7 +2602,7 @@ class BeignetNode extends events_1.EventEmitter {
1915
2602
  ? 'Networking and auto-reconnect enabled'
1916
2603
  : 'Networking disabled — node cannot reconnect to peers'
1917
2604
  });
1918
- const hasAnchor = channels.some((ch) => ch.channelType != null && (0, types_2.isAnchorChannel)(ch.channelType));
2605
+ const hasAnchor = channels.some((ch) => ch.channelType != null && (0, types_3.isAnchorChannel)(ch.channelType));
1919
2606
  checks.push({
1920
2607
  name: 'ANCHOR_CHANNELS_PREFERRED',
1921
2608
  status: hasAnchor || channels.length === 0 ? 'PASS' : 'WARN',
@@ -2032,6 +2719,70 @@ class BeignetNode extends events_1.EventEmitter {
2032
2719
  recommendations: snapshot.recommendations
2033
2720
  };
2034
2721
  }
2722
+ getAdvisorRecommendations() {
2723
+ const plan = this.node
2724
+ .planRebalanceRecommendations()
2725
+ .map((p) => ({
2726
+ fromChannelId: p.fromChannelId,
2727
+ toChannelId: p.toChannelId,
2728
+ amountSats: Number(p.amountSats),
2729
+ reason: p.reason
2730
+ }));
2731
+ return { ...this.getLiquiditySnapshot(), rebalancePlan: plan };
2732
+ }
2733
+ async rebalanceChannel(fromChannelId, toChannelId, amountSats, maxFeeSats) {
2734
+ if (!/^[0-9a-fA-F]{64}$/.test(fromChannelId))
2735
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, 'fromChannelId must be 64 hex chars');
2736
+ if (!/^[0-9a-fA-F]{64}$/.test(toChannelId))
2737
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, 'toChannelId must be 64 hex chars');
2738
+ if (!Number.isInteger(amountSats) || amountSats <= 0)
2739
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, 'amountSats must be a positive integer');
2740
+ if (!Number.isInteger(maxFeeSats) || maxFeeSats < 0)
2741
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, 'maxFeeSats must be a non-negative integer');
2742
+ const result = await this.node.rebalanceChannel({
2743
+ fromChannelId: Buffer.from(fromChannelId, 'hex'),
2744
+ toChannelId: Buffer.from(toChannelId, 'hex'),
2745
+ amountSats: BigInt(amountSats),
2746
+ maxFeeSats: BigInt(maxFeeSats)
2747
+ });
2748
+ this.log('info', 'Rebalance completed', {
2749
+ fromChannelId,
2750
+ toChannelId,
2751
+ amountSats,
2752
+ feeMsat: result.feeMsat.toString()
2753
+ });
2754
+ return {
2755
+ paymentHash: result.paymentHash.toString('hex'),
2756
+ amountSats,
2757
+ feeMsat: result.feeMsat.toString(),
2758
+ feeSats: Number(result.feeMsat / 1000n),
2759
+ hops: result.hops
2760
+ };
2761
+ }
2762
+ async executeRebalances(budgetSatsPerDay) {
2763
+ if (budgetSatsPerDay !== undefined &&
2764
+ (!Number.isInteger(budgetSatsPerDay) || budgetSatsPerDay < 0)) {
2765
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, 'budgetSatsPerDay must be a non-negative integer');
2766
+ }
2767
+ const summary = await this.node.executeRebalanceRecommendations({
2768
+ budgetSatsPerDay
2769
+ });
2770
+ return {
2771
+ attempts: summary.attempts.map((a) => ({
2772
+ fromChannelId: a.fromChannelId,
2773
+ toChannelId: a.toChannelId,
2774
+ amountSats: Number(a.amountSats),
2775
+ status: a.status,
2776
+ feeMsat: a.feeMsat?.toString(),
2777
+ error: a.error
2778
+ })),
2779
+ succeeded: summary.succeeded,
2780
+ failed: summary.failed,
2781
+ skippedBudget: summary.skippedBudget,
2782
+ feeSpentMsat: summary.feeSpentMsat.toString(),
2783
+ budgetRemainingMsat: summary.budgetRemainingMsat.toString()
2784
+ };
2785
+ }
2035
2786
  getFeeSnapshot() {
2036
2787
  return this.node.getFeeSnapshot();
2037
2788
  }
@@ -2047,10 +2798,160 @@ class BeignetNode extends events_1.EventEmitter {
2047
2798
  probeRoute(destination, amountSats) {
2048
2799
  return this.node.probeRoute(destination, amountSats);
2049
2800
  }
2801
+ getGraphInfo() {
2802
+ const graph = this.node.getGraph();
2803
+ const info = {
2804
+ nodeCount: graph.getNodeCount(),
2805
+ channelCount: graph.getChannelCount()
2806
+ };
2807
+ if (this._lastGraphSyncAt !== undefined) {
2808
+ info.lastSyncAt = this._lastGraphSyncAt;
2809
+ }
2810
+ return info;
2811
+ }
2812
+ getGraphNode(pubkey) {
2813
+ if (!/^[0-9a-fA-F]{66}$/.test(pubkey)) {
2814
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, 'pubkey must be a 33-byte hex string');
2815
+ }
2816
+ const graph = this.node.getGraph();
2817
+ const node = graph.getNode(Buffer.from(pubkey, 'hex'));
2818
+ if (!node)
2819
+ return null;
2820
+ const info = {
2821
+ pubkey: node.nodeId.toString('hex'),
2822
+ channelCount: node.channels.size,
2823
+ channels: graph
2824
+ .getNodeChannels(node.nodeId)
2825
+ .map((ch) => formatScid(ch.shortChannelId))
2826
+ };
2827
+ const ann = node.announcement;
2828
+ if (ann) {
2829
+ const alias = ann.alias
2830
+ .subarray(0, ann.alias.indexOf(0) === -1 ? 32 : ann.alias.indexOf(0))
2831
+ .toString('utf8');
2832
+ if (alias.length > 0)
2833
+ info.alias = alias;
2834
+ info.color = ann.rgbColor.toString('hex');
2835
+ info.addresses = ann.addresses.map((a) => ({
2836
+ type: a.type,
2837
+ host: a.host,
2838
+ port: a.port
2839
+ }));
2840
+ info.featuresHex = ann.features.toString('hex');
2841
+ info.lastUpdate = ann.timestamp;
2842
+ }
2843
+ return info;
2844
+ }
2845
+ getGraphChannel(scid) {
2846
+ const scidBuf = parseScid(scid);
2847
+ const channel = this.node.getGraph().getChannel(scidBuf);
2848
+ if (!channel)
2849
+ return null;
2850
+ return this.toGraphChannelInfo(channel);
2851
+ }
2852
+ describeGraph(limit, offset) {
2853
+ const cappedLimit = Math.min(Math.max(1, Math.floor(limit ?? GRAPH_DESCRIBE_MAX_LIMIT)), GRAPH_DESCRIBE_MAX_LIMIT);
2854
+ const safeOffset = Math.max(0, Math.floor(offset ?? 0));
2855
+ const all = this.node.getGraph().getAllChannels();
2856
+ return {
2857
+ totalChannels: all.length,
2858
+ limit: cappedLimit,
2859
+ offset: safeOffset,
2860
+ channels: all
2861
+ .slice(safeOffset, safeOffset + cappedLimit)
2862
+ .map((ch) => this.toGraphChannelInfo(ch))
2863
+ };
2864
+ }
2865
+ toGraphChannelInfo(channel) {
2866
+ const info = {
2867
+ shortChannelId: formatScid(channel.shortChannelId),
2868
+ node1Pubkey: channel.nodeId1.toString('hex'),
2869
+ node2Pubkey: channel.nodeId2.toString('hex')
2870
+ };
2871
+ const max1 = channel.update1?.htlcMaximumMsat;
2872
+ const max2 = channel.update2?.htlcMaximumMsat;
2873
+ const best = (max1 ?? 0n) > (max2 ?? 0n) ? max1 : max2;
2874
+ if (best !== undefined)
2875
+ info.capacitySats = Number(best / 1000n);
2876
+ if (channel.update1) {
2877
+ info.node1Policy = this.toGraphChannelPolicy(channel.update1);
2878
+ }
2879
+ if (channel.update2) {
2880
+ info.node2Policy = this.toGraphChannelPolicy(channel.update2);
2881
+ }
2882
+ return info;
2883
+ }
2884
+ toGraphChannelPolicy(update) {
2885
+ const policy = {
2886
+ feeBaseMsat: update.feeBaseMsat,
2887
+ feeProportionalMillionths: update.feeProportionalMillionths,
2888
+ cltvExpiryDelta: update.cltvExpiryDelta,
2889
+ htlcMinimumMsat: update.htlcMinimumMsat.toString(),
2890
+ disabled: (update.channelFlags & types_1.CHANNEL_FLAG_DISABLED) !== 0,
2891
+ lastUpdate: update.timestamp
2892
+ };
2893
+ if (update.htlcMaximumMsat !== undefined) {
2894
+ policy.htlcMaximumMsat = update.htlcMaximumMsat.toString();
2895
+ }
2896
+ return policy;
2897
+ }
2898
+ queryRoute(destination, amountSats, maxFeeSats) {
2899
+ if (!/^[0-9a-fA-F]{66}$/.test(destination)) {
2900
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, 'destination must be a 33-byte hex string');
2901
+ }
2902
+ if (!Number.isInteger(amountSats) || amountSats <= 0) {
2903
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, 'amountSats must be a positive integer');
2904
+ }
2905
+ const amountMsat = BigInt(amountSats) * 1000n;
2906
+ const route = this.node.queryRoute(Buffer.from(destination, 'hex'), amountMsat, types_2.DEFAULT_MIN_FINAL_CLTV_EXPIRY);
2907
+ if (!route) {
2908
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.NO_ROUTE, 'No route found to destination');
2909
+ }
2910
+ if (maxFeeSats !== undefined &&
2911
+ route.totalFeeMsat > BigInt(maxFeeSats) * 1000n) {
2912
+ throw new errors_1.BeignetError('FEE_EXCEEDS_MAX', `Route fee ${route.totalFeeMsat} msat exceeds maximum ${maxFeeSats} sats`);
2913
+ }
2914
+ return {
2915
+ destination,
2916
+ amountSats,
2917
+ hops: routeHopsToJson(route),
2918
+ totalAmountMsat: route.totalAmountMsat.toString(),
2919
+ totalFeeMsat: route.totalFeeMsat.toString(),
2920
+ totalCltvDelta: route.totalCltvDelta,
2921
+ finalCltvExpiry: types_2.DEFAULT_MIN_FINAL_CLTV_EXPIRY
2922
+ };
2923
+ }
2924
+ sendToRoute(paymentHash, route, paymentSecret) {
2925
+ if (!/^[0-9a-fA-F]{64}$/.test(paymentHash)) {
2926
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, 'paymentHash must be a 32-byte hex string');
2927
+ }
2928
+ if (!route || !Array.isArray(route.hops) || route.hops.length === 0) {
2929
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, 'route.hops must be a non-empty array');
2930
+ }
2931
+ if (paymentSecret !== undefined &&
2932
+ !/^[0-9a-fA-F]{64}$/.test(paymentSecret)) {
2933
+ throw new errors_1.BeignetError(errors_1.BeignetErrorCode.INVALID_PARAMS, 'paymentSecret must be a 32-byte hex string');
2934
+ }
2935
+ this._checkDraining();
2936
+ const hops = jsonToRouteHops(route.hops);
2937
+ const finalHop = hops[hops.length - 1];
2938
+ try {
2939
+ const payment = this.node.sendPaymentToRoute({ hops }, Buffer.from(paymentHash, 'hex'), finalHop.outgoingCltvValue, paymentSecret ? Buffer.from(paymentSecret, 'hex') : undefined, finalHop.amountToForwardMsat);
2940
+ return this.toPaymentInfo(payment);
2941
+ }
2942
+ catch (err) {
2943
+ if (err instanceof errors_1.BeignetError)
2944
+ throw err;
2945
+ const code = err !== null && typeof err === 'object' && 'code' in err
2946
+ ? String(err.code)
2947
+ : 'PAYMENT_FAILED';
2948
+ throw new errors_1.BeignetError(code, err instanceof Error ? err.message : String(err));
2949
+ }
2950
+ }
2050
2951
  getReadyChannels() {
2051
2952
  return this.node
2052
2953
  .listChannels()
2053
- .filter((ch) => ch.state === types_2.ChannelState.NORMAL)
2954
+ .filter((ch) => ch.state === types_3.ChannelState.NORMAL)
2054
2955
  .map((ch) => this.toChannelInfo(ch));
2055
2956
  }
2056
2957
  canSend(amountSats) {
@@ -2059,7 +2960,7 @@ class BeignetNode extends events_1.EventEmitter {
2059
2960
  let bestAvailableMsat = 0n;
2060
2961
  let totalAvailableMsat = 0n;
2061
2962
  for (const ch of this.node.listChannels()) {
2062
- if (ch.state !== types_2.ChannelState.NORMAL)
2963
+ if (ch.state !== types_3.ChannelState.NORMAL)
2063
2964
  continue;
2064
2965
  const reserveMsat = ch.localReserveMsat ?? 0n;
2065
2966
  const available = ch.localBalanceMsat > reserveMsat
@@ -2083,7 +2984,7 @@ class BeignetNode extends events_1.EventEmitter {
2083
2984
  let bestAvailableMsat = 0n;
2084
2985
  let totalAvailableMsat = 0n;
2085
2986
  for (const ch of this.node.listChannels()) {
2086
- if (ch.state !== types_2.ChannelState.NORMAL)
2987
+ if (ch.state !== types_3.ChannelState.NORMAL)
2087
2988
  continue;
2088
2989
  const reserveMsat = ch.remoteReserveMsat ?? 0n;
2089
2990
  const available = ch.remoteBalanceMsat > reserveMsat
@@ -2277,6 +3178,86 @@ class BeignetNode extends events_1.EventEmitter {
2277
3178
  triggerBackup() {
2278
3179
  this.performScheduledBackup();
2279
3180
  }
3181
+ exportStaticChannelBackup() {
3182
+ const data = this.node.buildStaticChannelBackupData();
3183
+ const backup = {
3184
+ version: 1,
3185
+ network: data.network,
3186
+ createdAt: Date.now(),
3187
+ channels: data.channels
3188
+ };
3189
+ const seed = bip39.mnemonicToSeedSync(this.mnemonic);
3190
+ const encoded = (0, scb_1.encodeScb)(backup, seed);
3191
+ const scbPath = path.join(this.dataDir, 'channels.scb');
3192
+ const tmpPath = `${scbPath}.tmp`;
3193
+ fs.writeFileSync(tmpPath, encoded);
3194
+ fs.renameSync(tmpPath, scbPath);
3195
+ return { encoded, channelCount: data.channels.length, path: scbPath };
3196
+ }
3197
+ async restoreFromScb(encoded) {
3198
+ const seed = bip39.mnemonicToSeedSync(this.mnemonic);
3199
+ const backup = (0, scb_1.decodeScb)(encoded.trim(), seed);
3200
+ const expectedNetwork = this.toLnNetwork(this.networkName);
3201
+ if (backup.network !== expectedNetwork) {
3202
+ throw new errors_1.BeignetError('INVALID_PARAMS', `SCB network "${backup.network}" does not match this node's network "${expectedNetwork}"`);
3203
+ }
3204
+ const { recovering, skipped } = await this.node.recoverFromStaticChannelBackup(backup.channels);
3205
+ this.log('info', 'SCB restore processed', {
3206
+ channelCount: backup.channels.length,
3207
+ recovering: recovering.length,
3208
+ skipped: skipped.length
3209
+ });
3210
+ return { recovering, skipped, channelCount: backup.channels.length };
3211
+ }
3212
+ refreshStaticChannelBackup() {
3213
+ if (this.destroyed)
3214
+ return;
3215
+ try {
3216
+ const { encoded } = this.exportStaticChannelBackup();
3217
+ if (this.peerStorageEnabled) {
3218
+ const sent = this.node.distributePeerStorage(Buffer.from(encoded, 'utf8'));
3219
+ if (sent > 0) {
3220
+ this.log('debug', 'Pushed SCB to peer storage', { peers: sent });
3221
+ }
3222
+ }
3223
+ }
3224
+ catch (err) {
3225
+ this.log('warn', 'Static channel backup refresh failed', {
3226
+ error: err instanceof Error ? err.message : String(err)
3227
+ });
3228
+ }
3229
+ }
3230
+ handleRetrievedPeerStorage(peerPubkey, blob) {
3231
+ let backup;
3232
+ const encoded = blob.toString('utf8');
3233
+ try {
3234
+ const seed = bip39.mnemonicToSeedSync(this.mnemonic);
3235
+ backup = (0, scb_1.decodeScb)(encoded, seed);
3236
+ }
3237
+ catch {
3238
+ this.log('debug', 'Ignoring peer storage blob that is not our SCB', {
3239
+ fromPeer: peerPubkey
3240
+ });
3241
+ return;
3242
+ }
3243
+ if (this._peerRetrievedScb &&
3244
+ this._peerRetrievedScb.createdAt >= backup.createdAt) {
3245
+ return;
3246
+ }
3247
+ this._peerRetrievedScb = {
3248
+ encoded,
3249
+ createdAt: backup.createdAt,
3250
+ fromPeer: peerPubkey
3251
+ };
3252
+ this.log('info', 'Recovered SCB from peer storage', {
3253
+ fromPeer: peerPubkey,
3254
+ createdAt: backup.createdAt,
3255
+ channelCount: backup.channels.length
3256
+ });
3257
+ }
3258
+ getPeerRetrievedBackup() {
3259
+ return this._peerRetrievedScb;
3260
+ }
2280
3261
  getNodeUri(externalHost) {
2281
3262
  if (!this._listenPort)
2282
3263
  return null;
@@ -2366,6 +3347,8 @@ class BeignetNode extends events_1.EventEmitter {
2366
3347
  return wallet_2.EAvailableNetworks.testnet;
2367
3348
  case 'regtest':
2368
3349
  return wallet_2.EAvailableNetworks.regtest;
3350
+ case 'signet':
3351
+ return wallet_2.EAvailableNetworks.signet;
2369
3352
  default:
2370
3353
  return wallet_2.EAvailableNetworks.bitcoin;
2371
3354
  }
@@ -2373,13 +3356,15 @@ class BeignetNode extends events_1.EventEmitter {
2373
3356
  toLnNetwork(network) {
2374
3357
  switch (network) {
2375
3358
  case 'mainnet':
2376
- return types_1.Network.MAINNET;
3359
+ return types_2.Network.MAINNET;
2377
3360
  case 'testnet':
2378
- return types_1.Network.TESTNET;
3361
+ return types_2.Network.TESTNET;
2379
3362
  case 'regtest':
2380
- return types_1.Network.REGTEST;
3363
+ return types_2.Network.REGTEST;
3364
+ case 'signet':
3365
+ return types_2.Network.SIGNET;
2381
3366
  default:
2382
- return types_1.Network.MAINNET;
3367
+ return types_2.Network.MAINNET;
2383
3368
  }
2384
3369
  }
2385
3370
  toCoinType(network) {
@@ -2390,22 +3375,14 @@ class BeignetNode extends events_1.EventEmitter {
2390
3375
  return wallet_keys_1.LnCoinType.TESTNET;
2391
3376
  case 'regtest':
2392
3377
  return wallet_keys_1.LnCoinType.REGTEST;
3378
+ case 'signet':
3379
+ return wallet_keys_1.LnCoinType.SIGNET;
2393
3380
  default:
2394
3381
  return wallet_keys_1.LnCoinType.BITCOIN;
2395
3382
  }
2396
3383
  }
2397
3384
  getBitcoinNetwork() {
2398
- const bitcoin = require('bitcoinjs-lib');
2399
- switch (this.networkName) {
2400
- case 'mainnet':
2401
- return bitcoin.networks.bitcoin;
2402
- case 'testnet':
2403
- return bitcoin.networks.testnet;
2404
- case 'regtest':
2405
- return bitcoin.networks.regtest;
2406
- default:
2407
- return bitcoin.networks.bitcoin;
2408
- }
3385
+ return (0, helpers_1.getBitcoinJsNetwork)(this.toBeignetNetwork(this.networkName));
2409
3386
  }
2410
3387
  }
2411
3388
  exports.BeignetNode = BeignetNode;