beignet 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/README.md +284 -1
  2. package/dist/cli/beignet-node.js +1019 -47
  3. package/dist/cli/beignet-node.js.map +1 -1
  4. package/dist/cli/cli.js +1201 -16
  5. package/dist/cli/cli.js.map +1 -1
  6. package/dist/cli/config.js +21 -1
  7. package/dist/cli/config.js.map +1 -1
  8. package/dist/cli/daemon.js +342 -16
  9. package/dist/cli/daemon.js.map +1 -1
  10. package/dist/cli/errors.js +2 -0
  11. package/dist/cli/errors.js.map +1 -1
  12. package/dist/cli/openapi.js +1207 -14
  13. package/dist/cli/openapi.js.map +1 -1
  14. package/dist/cli/restore.js +89 -0
  15. package/dist/cli/restore.js.map +1 -0
  16. package/dist/cli/wallet-storage.js +34 -0
  17. package/dist/cli/wallet-storage.js.map +1 -0
  18. package/dist/electrum/index.js +105 -10
  19. package/dist/electrum/index.js.map +1 -1
  20. package/dist/lightning/advisor/fee-tuner.js +42 -0
  21. package/dist/lightning/advisor/fee-tuner.js.map +1 -0
  22. package/dist/lightning/advisor/index.js +9 -1
  23. package/dist/lightning/advisor/index.js.map +1 -1
  24. package/dist/lightning/advisor/rebalance-planner.js +55 -0
  25. package/dist/lightning/advisor/rebalance-planner.js.map +1 -0
  26. package/dist/lightning/backup/index.js +18 -0
  27. package/dist/lightning/backup/index.js.map +1 -0
  28. package/dist/lightning/backup/scb.js +42 -0
  29. package/dist/lightning/backup/scb.js.map +1 -0
  30. package/dist/lightning/chain/chain-monitor.js +27 -0
  31. package/dist/lightning/chain/chain-monitor.js.map +1 -1
  32. package/dist/lightning/chain/output-resolver.js +55 -6
  33. package/dist/lightning/chain/output-resolver.js.map +1 -1
  34. package/dist/lightning/chain/types.js +1 -0
  35. package/dist/lightning/chain/types.js.map +1 -1
  36. package/dist/lightning/channel/channel-manager.js +64 -0
  37. package/dist/lightning/channel/channel-manager.js.map +1 -1
  38. package/dist/lightning/channel/channel-state.js.map +1 -1
  39. package/dist/lightning/channel/channel.js +124 -4
  40. package/dist/lightning/channel/channel.js.map +1 -1
  41. package/dist/lightning/channel/dual-funding.js +6 -5
  42. package/dist/lightning/channel/dual-funding.js.map +1 -1
  43. package/dist/lightning/channel/types.js +3 -1
  44. package/dist/lightning/channel/types.js.map +1 -1
  45. package/dist/lightning/channel/validation.js +3 -3
  46. package/dist/lightning/channel/validation.js.map +1 -1
  47. package/dist/lightning/crypto/index.js +1 -0
  48. package/dist/lightning/crypto/index.js.map +1 -1
  49. package/dist/lightning/crypto/message-signing.js +116 -0
  50. package/dist/lightning/crypto/message-signing.js.map +1 -0
  51. package/dist/lightning/crypto/xchacha20poly1305.js +77 -0
  52. package/dist/lightning/crypto/xchacha20poly1305.js.map +1 -0
  53. package/dist/lightning/features/flags.js +1 -0
  54. package/dist/lightning/features/flags.js.map +1 -1
  55. package/dist/lightning/gossip/messages.js +37 -1
  56. package/dist/lightning/gossip/messages.js.map +1 -1
  57. package/dist/lightning/index.js +2 -1
  58. package/dist/lightning/index.js.map +1 -1
  59. package/dist/lightning/keys/wallet-keys.js +1 -0
  60. package/dist/lightning/keys/wallet-keys.js.map +1 -1
  61. package/dist/lightning/message/index.js +1 -0
  62. package/dist/lightning/message/index.js.map +1 -1
  63. package/dist/lightning/message/peer-storage.js +43 -0
  64. package/dist/lightning/message/peer-storage.js.map +1 -0
  65. package/dist/lightning/message/types.js +2 -0
  66. package/dist/lightning/message/types.js.map +1 -1
  67. package/dist/lightning/node/lightning-node.js +1583 -235
  68. package/dist/lightning/node/lightning-node.js.map +1 -1
  69. package/dist/lightning/node/types.js +18 -1
  70. package/dist/lightning/node/types.js.map +1 -1
  71. package/dist/lightning/offer/offer-manager.js +2 -5
  72. package/dist/lightning/offer/offer-manager.js.map +1 -1
  73. package/dist/lightning/offer/tlv.js +3 -1
  74. package/dist/lightning/offer/tlv.js.map +1 -1
  75. package/dist/lightning/storage/encryption.js +67 -0
  76. package/dist/lightning/storage/encryption.js.map +1 -0
  77. package/dist/lightning/storage/index.js +1 -0
  78. package/dist/lightning/storage/index.js.map +1 -1
  79. package/dist/lightning/storage/serialization.js +6 -2
  80. package/dist/lightning/storage/serialization.js.map +1 -1
  81. package/dist/lightning/storage/sqlite-storage.js +387 -33
  82. package/dist/lightning/storage/sqlite-storage.js.map +1 -1
  83. package/dist/lightning/watchtower/blob.js +135 -0
  84. package/dist/lightning/watchtower/blob.js.map +1 -0
  85. package/dist/lightning/watchtower/index.js +55 -0
  86. package/dist/lightning/watchtower/index.js.map +1 -0
  87. package/dist/lightning/watchtower/justice.js +182 -0
  88. package/dist/lightning/watchtower/justice.js.map +1 -0
  89. package/dist/lightning/watchtower/tower-connection.js +208 -0
  90. package/dist/lightning/watchtower/tower-connection.js.map +1 -0
  91. package/dist/lightning/watchtower/types.js +3 -0
  92. package/dist/lightning/watchtower/types.js.map +1 -0
  93. package/dist/lightning/watchtower/watchtower-client.js +402 -0
  94. package/dist/lightning/watchtower/watchtower-client.js.map +1 -0
  95. package/dist/lightning/watchtower/wtwire.js +201 -0
  96. package/dist/lightning/watchtower/wtwire.js.map +1 -0
  97. package/dist/shapes/electrum.js +32 -1
  98. package/dist/shapes/electrum.js.map +1 -1
  99. package/dist/shapes/wallet.js +11 -1
  100. package/dist/shapes/wallet.js.map +1 -1
  101. package/dist/transaction/index.js +163 -15
  102. package/dist/transaction/index.js.map +1 -1
  103. package/dist/types/cli/beignet-node.d.ts +144 -3
  104. package/dist/types/cli/daemon.d.ts +2 -0
  105. package/dist/types/cli/errors.d.ts +2 -0
  106. package/dist/types/cli/restore.d.ts +10 -0
  107. package/dist/types/cli/types.d.ts +228 -1
  108. package/dist/types/cli/wallet-storage.d.ts +3 -0
  109. package/dist/types/electrum/index.d.ts +12 -0
  110. package/dist/types/electrum.js +1 -0
  111. package/dist/types/electrum.js.map +1 -1
  112. package/dist/types/lightning/advisor/fee-tuner.d.ts +20 -0
  113. package/dist/types/lightning/advisor/index.d.ts +4 -0
  114. package/dist/types/lightning/advisor/rebalance-planner.d.ts +12 -0
  115. package/dist/types/lightning/backup/index.d.ts +1 -0
  116. package/dist/types/lightning/backup/scb.d.ts +24 -0
  117. package/dist/types/lightning/chain/chain-monitor.d.ts +1 -0
  118. package/dist/types/lightning/chain/types.d.ts +1 -0
  119. package/dist/types/lightning/channel/channel-manager.d.ts +7 -0
  120. package/dist/types/lightning/channel/channel-state.d.ts +2 -0
  121. package/dist/types/lightning/channel/channel.d.ts +6 -0
  122. package/dist/types/lightning/channel/dual-funding.d.ts +2 -1
  123. package/dist/types/lightning/channel/types.d.ts +2 -0
  124. package/dist/types/lightning/channel/validation.d.ts +1 -1
  125. package/dist/types/lightning/crypto/index.d.ts +1 -0
  126. package/dist/types/lightning/crypto/message-signing.d.ts +9 -0
  127. package/dist/types/lightning/crypto/xchacha20poly1305.d.ts +3 -0
  128. package/dist/types/lightning/features/flags.d.ts +1 -0
  129. package/dist/types/lightning/gossip/messages.d.ts +4 -0
  130. package/dist/types/lightning/index.d.ts +1 -0
  131. package/dist/types/lightning/keys/wallet-keys.d.ts +2 -1
  132. package/dist/types/lightning/message/index.d.ts +1 -0
  133. package/dist/types/lightning/message/peer-storage.d.ts +9 -0
  134. package/dist/types/lightning/message/types.d.ts +2 -0
  135. package/dist/types/lightning/node/lightning-node.d.ts +116 -4
  136. package/dist/types/lightning/node/types.d.ts +62 -1
  137. package/dist/types/lightning/storage/encryption.d.ts +11 -0
  138. package/dist/types/lightning/storage/index.d.ts +1 -0
  139. package/dist/types/lightning/storage/serialization.d.ts +2 -0
  140. package/dist/types/lightning/storage/sqlite-storage.d.ts +43 -3
  141. package/dist/types/lightning/storage/types.d.ts +62 -0
  142. package/dist/types/lightning/watchtower/blob.d.ts +32 -0
  143. package/dist/types/lightning/watchtower/index.d.ts +9 -0
  144. package/dist/types/lightning/watchtower/justice.d.ts +29 -0
  145. package/dist/types/lightning/watchtower/tower-connection.d.ts +34 -0
  146. package/dist/types/lightning/watchtower/types.d.ts +44 -0
  147. package/dist/types/lightning/watchtower/watchtower-client.d.ts +67 -0
  148. package/dist/types/lightning/watchtower/wtwire.d.ts +90 -0
  149. package/dist/types/shapes/electrum.d.ts +3 -1
  150. package/dist/types/transaction/index.d.ts +6 -0
  151. package/dist/types/transaction.js.map +1 -1
  152. package/dist/types/types/electrum.d.ts +3 -2
  153. package/dist/types/types/transaction.d.ts +1 -1
  154. package/dist/types/types/wallet.d.ts +78 -4
  155. package/dist/types/utils/descriptors.d.ts +2 -0
  156. package/dist/types/utils/fees.d.ts +3 -0
  157. package/dist/types/utils/helpers.d.ts +3 -1
  158. package/dist/types/utils/index.d.ts +5 -0
  159. package/dist/types/utils/multisig.d.ts +29 -0
  160. package/dist/types/utils/transaction.d.ts +7 -0
  161. package/dist/types/utils/wallet-storage-encryption.d.ts +3 -0
  162. package/dist/types/utils/wallet.d.ts +1 -1
  163. package/dist/types/utils/watch-only.d.ts +12 -0
  164. package/dist/types/wallet/constants.d.ts +1 -0
  165. package/dist/types/wallet/index.d.ts +61 -4
  166. package/dist/types/wallet.js +3 -0
  167. package/dist/types/wallet.js.map +1 -1
  168. package/dist/utils/derivation-path.js +8 -7
  169. package/dist/utils/derivation-path.js.map +1 -1
  170. package/dist/utils/descriptors.js +56 -0
  171. package/dist/utils/descriptors.js.map +1 -0
  172. package/dist/utils/electrum.js +10 -2
  173. package/dist/utils/electrum.js.map +1 -1
  174. package/dist/utils/fees.js +19 -0
  175. package/dist/utils/fees.js.map +1 -0
  176. package/dist/utils/helpers.js +22 -5
  177. package/dist/utils/helpers.js.map +1 -1
  178. package/dist/utils/index.js +5 -0
  179. package/dist/utils/index.js.map +1 -1
  180. package/dist/utils/multisig.js +90 -0
  181. package/dist/utils/multisig.js.map +1 -0
  182. package/dist/utils/transaction.js +30 -3
  183. package/dist/utils/transaction.js.map +1 -1
  184. package/dist/utils/wallet-storage-encryption.js +45 -0
  185. package/dist/utils/wallet-storage-encryption.js.map +1 -0
  186. package/dist/utils/wallet.js +3 -1
  187. package/dist/utils/wallet.js.map +1 -1
  188. package/dist/utils/watch-only.js +102 -0
  189. package/dist/utils/watch-only.js.map +1 -0
  190. package/dist/wallet/constants.js +2 -1
  191. package/dist/wallet/constants.js.map +1 -1
  192. package/dist/wallet/index.js +787 -30
  193. package/dist/wallet/index.js.map +1 -1
  194. package/docs/ROADMAP.md +292 -0
  195. package/package.json +1 -1
@@ -30,6 +30,7 @@ exports.LightningNode = void 0;
30
30
  const events_1 = require("events");
31
31
  const crypto_1 = __importDefault(require("crypto"));
32
32
  const ecdh_1 = require("../crypto/ecdh");
33
+ const message_signing_1 = require("../crypto/message-signing");
33
34
  const blinded_path_1 = require("../onion/blinded-path");
34
35
  const channel_manager_1 = require("../channel/channel-manager");
35
36
  const channel_1 = require("../channel/channel");
@@ -40,6 +41,7 @@ const network_graph_1 = require("../gossip/network-graph");
40
41
  const pathfinding_1 = require("../gossip/pathfinding");
41
42
  const rapid_sync_1 = require("../gossip/rapid-sync");
42
43
  const mission_control_1 = require("../gossip/mission-control");
44
+ const types_2 = require("../gossip/types");
43
45
  const messages_1 = require("../gossip/messages");
44
46
  const gossip_queries_1 = require("../gossip/gossip-queries");
45
47
  const gossip_sync_1 = require("../gossip/gossip-sync");
@@ -48,20 +50,22 @@ const construct_1 = require("../onion/construct");
48
50
  const process_1 = require("../onion/process");
49
51
  const sphinx_crypto_1 = require("../onion/sphinx-crypto");
50
52
  const failures_1 = require("../onion/failures");
51
- const types_2 = require("../onion/types");
53
+ const types_3 = require("../onion/types");
52
54
  const encode_1 = require("../invoice/encode");
53
55
  const decode_1 = require("../invoice/decode");
54
- const types_3 = require("../invoice/types");
55
- const types_4 = require("../message/types");
56
- const types_5 = require("./types");
56
+ const types_4 = require("../invoice/types");
57
+ const types_5 = require("../message/types");
58
+ const peer_storage_1 = require("../message/peer-storage");
59
+ const types_6 = require("./types");
57
60
  const validation_2 = require("../validation");
58
61
  const bitcoin = __importStar(require("bitcoinjs-lib"));
59
62
  const ecc = __importStar(require("@bitcoinerlab/secp256k1"));
60
63
  const flags_1 = require("../features/flags");
61
64
  const chain_watcher_1 = require("../chain/chain-watcher");
62
65
  const sweep_1 = require("../chain/sweep");
63
- const types_6 = require("../chain/types");
66
+ const types_7 = require("../chain/types");
64
67
  const chain_monitor_1 = require("../chain/chain-monitor");
68
+ const watchtower_1 = require("../watchtower");
65
69
  const wallet_keys_1 = require("../keys/wallet-keys");
66
70
  const bip32Lib = __importStar(require("bip32"));
67
71
  const bip39 = __importStar(require("bip39"));
@@ -69,7 +73,8 @@ const shachain_1 = require("../keys/shachain");
69
73
  const derivation_1 = require("../keys/derivation");
70
74
  const funding_1 = require("../script/funding");
71
75
  const funding_taproot_1 = require("../script/funding-taproot");
72
- const types_7 = require("../channel/types");
76
+ const types_8 = require("../channel/types");
77
+ const channel_state_1 = require("../channel/channel-state");
73
78
  const commitment_builder_1 = require("../channel/commitment-builder");
74
79
  const signer_1 = require("../keys/signer");
75
80
  const bootstrap_1 = require("../bootstrap");
@@ -80,6 +85,8 @@ const rate_limiter_1 = require("./rate-limiter");
80
85
  const liquidity_advisor_1 = require("../advisor/liquidity-advisor");
81
86
  const fee_advisor_1 = require("../advisor/fee-advisor");
82
87
  const channel_suggestions_1 = require("../advisor/channel-suggestions");
88
+ const rebalance_planner_1 = require("../advisor/rebalance-planner");
89
+ const fee_tuner_1 = require("../advisor/fee-tuner");
83
90
  bitcoin.initEccLib(ecc);
84
91
  const GOSSIP_REFRESH_INTERVAL_MS = 6 * 60 * 60 * 1000;
85
92
  const HELD_HTLC_EXPIRY_MARGIN = 18;
@@ -100,6 +107,7 @@ class LightningNode extends events_1.EventEmitter {
100
107
  this.chainWatcher = null;
101
108
  this._chainWatcherEventsWired = false;
102
109
  this.currentBlockHeight = 0;
110
+ this.channelPolicies = new Map();
103
111
  this.gossipSyncManagers = new Map();
104
112
  this._ownChannelGossip = new Map();
105
113
  this.pendingMppPayments = new Map();
@@ -131,10 +139,19 @@ class LightningNode extends events_1.EventEmitter {
131
139
  this.liquidityAdvisor = new liquidity_advisor_1.LiquidityAdvisor();
132
140
  this.feeAdvisor = new fee_advisor_1.FeeAdvisor();
133
141
  this.channelSuggestions = new channel_suggestions_1.ChannelSuggestions();
142
+ this.autoRebalanceTimer = null;
143
+ this.autoTuneFeesTimer = null;
144
+ this.rebalanceBudgetDay = null;
145
+ this.rebalanceRunInFlight = false;
146
+ this.watchtowerClient = null;
147
+ this.peerStorageBlobs = new Map();
148
+ this.peerStorageLastAccepted = new Map();
149
+ this.ourPeerStorageBlob = null;
150
+ this.retrievedPeerStorage = new Map();
134
151
  this.setMaxListeners(50);
135
152
  this.nodePrivkey = config.nodePrivateKey;
136
153
  this.nodeId = (0, ecdh_1.getPublicKey)(config.nodePrivateKey).toString('hex');
137
- this.network = config.network || types_3.Network.REGTEST;
154
+ this.network = config.network || types_4.Network.REGTEST;
138
155
  this.acceptableChainHashes = config.chainHashes ?? [];
139
156
  this.storage = config.storage || null;
140
157
  this.resourceConfig = {
@@ -158,6 +175,8 @@ class LightningNode extends events_1.EventEmitter {
158
175
  this.revocationBasepointSecret = config.revocationBasepointSecret;
159
176
  this.paymentBasepointSecret = config.paymentBasepointSecret;
160
177
  this.feeEstimator = config.feeEstimator || null;
178
+ this.socks5Proxy = config.socks5Proxy ?? null;
179
+ this.initWatchtowerClient(config.watchtowers ?? []);
161
180
  this.missionControl = new mission_control_1.MissionControl();
162
181
  this.maxPaymentRetries = config.maxPaymentRetries ?? 3;
163
182
  this.maxTotalInFlightHtlcs = config.maxTotalInFlightHtlcs ?? 1000;
@@ -170,6 +189,15 @@ class LightningNode extends events_1.EventEmitter {
170
189
  !localFeatures.hasFeature(flags_1.Feature.ANCHOR_ZERO_FEE_HTLC)) {
171
190
  localFeatures.setOptional(flags_1.Feature.ANCHOR_ZERO_FEE_HTLC);
172
191
  }
192
+ this.largeChannels = config.largeChannels ?? false;
193
+ if (this.largeChannels) {
194
+ localFeatures.setOptional(flags_1.Feature.LARGE_CHANNELS);
195
+ }
196
+ this.peerStorageEnabled = config.peerStorageEnabled ?? true;
197
+ if (!this.peerStorageEnabled) {
198
+ localFeatures.clearBit(flags_1.Feature.PROVIDE_STORAGE);
199
+ localFeatures.clearBit(flags_1.Feature.PROVIDE_STORAGE + 1);
200
+ }
173
201
  this.channelManager = new channel_manager_1.ChannelManager({
174
202
  localFeatures,
175
203
  localConfig: config.channelConfig,
@@ -184,7 +212,8 @@ class LightningNode extends events_1.EventEmitter {
184
212
  preferTaproot: config.preferTaproot,
185
213
  chainHash: config.chainHashes?.[0],
186
214
  nodePrivateKey: config.nodePrivateKey,
187
- channelKeyDeriver: config.channelKeyDeriver
215
+ channelKeyDeriver: config.channelKeyDeriver,
216
+ largeChannels: this.largeChannels
188
217
  });
189
218
  this.channelManager.setFundingProvider(this.fundingProvider);
190
219
  this.graph = new network_graph_1.NetworkGraph();
@@ -211,6 +240,7 @@ class LightningNode extends events_1.EventEmitter {
211
240
  this.channelManager.attachToPeerManager(this.peerManager);
212
241
  this.registerGossipHandlers();
213
242
  this.registerOnionMessageHandler();
243
+ this.registerPeerStorageHandlers();
214
244
  this.wirePeerManagerEvents();
215
245
  }
216
246
  if (config.chainBackend) {
@@ -269,6 +299,35 @@ class LightningNode extends events_1.EventEmitter {
269
299
  this.missionControlTimer.unref();
270
300
  }
271
301
  }
302
+ this.autoRebalanceConfig = config.autoRebalance ?? {};
303
+ this.autoTuneFeesConfig = config.autoTuneFees ?? {};
304
+ if (this.autoRebalanceConfig.enabled === true) {
305
+ this.autoRebalanceTimer = setInterval(() => {
306
+ this.executeRebalanceRecommendations().catch((err) => {
307
+ this.emitStructuredLog('payment', 'auto_rebalance_failed', {
308
+ error: err instanceof Error ? err.message : String(err)
309
+ });
310
+ });
311
+ }, this.autoRebalanceConfig.intervalMs ?? 3600000);
312
+ if (this.autoRebalanceTimer.unref) {
313
+ this.autoRebalanceTimer.unref();
314
+ }
315
+ }
316
+ if (this.autoTuneFeesConfig.enabled === true) {
317
+ this.autoTuneFeesTimer = setInterval(() => {
318
+ try {
319
+ this.runFeeTuneOnce();
320
+ }
321
+ catch (err) {
322
+ this.emitStructuredLog('fee', 'auto_tune_failed', {
323
+ error: err instanceof Error ? err.message : String(err)
324
+ });
325
+ }
326
+ }, this.autoTuneFeesConfig.intervalMs ?? 21600000);
327
+ if (this.autoTuneFeesTimer.unref) {
328
+ this.autoTuneFeesTimer.unref();
329
+ }
330
+ }
272
331
  this.graphPruneTimer = setInterval(() => {
273
332
  this.pruneStaleGossipWithStorage();
274
333
  }, 3600000);
@@ -332,13 +391,35 @@ class LightningNode extends events_1.EventEmitter {
332
391
  for (const { key, secret } of this.storage.loadAllHtlcSharedSecrets()) {
333
392
  this.receivedHtlcSharedSecrets.set(key, secret);
334
393
  }
394
+ if (this.storage.loadAllChannelPolicies) {
395
+ for (const { channelId, policy } of this.storage.loadAllChannelPolicies()) {
396
+ const override = {};
397
+ if (policy.feeBaseMsat !== undefined)
398
+ override.feeBaseMsat = policy.feeBaseMsat;
399
+ if (policy.feeProportionalMillionths !== undefined)
400
+ override.feeProportionalMillionths = policy.feeProportionalMillionths;
401
+ if (policy.cltvExpiryDelta !== undefined)
402
+ override.cltvExpiryDelta = policy.cltvExpiryDelta;
403
+ if (policy.htlcMinimumMsat !== undefined)
404
+ override.htlcMinimumMsat = BigInt(policy.htlcMinimumMsat);
405
+ if (policy.htlcMaximumMsat !== undefined)
406
+ override.htlcMaximumMsat = BigInt(policy.htlcMaximumMsat);
407
+ this.channelPolicies.set(channelId, override);
408
+ }
409
+ }
335
410
  for (const { paymentHashHex, invoice } of this.storage.loadAllInvoices()) {
336
411
  if (invoice.createdAt > 10000000000) {
337
412
  invoice.createdAt = Math.floor(invoice.createdAt / 1000);
338
413
  }
339
414
  this.invoices.set(paymentHashHex, invoice);
340
415
  if (invoice.hold) {
341
- this.heldInvoiceHashes.add(paymentHashHex);
416
+ if (invoice.cancelledAt) {
417
+ this.preimages.delete(paymentHashHex);
418
+ this.paymentSecrets.delete(paymentHashHex);
419
+ }
420
+ else {
421
+ this.heldInvoiceHashes.add(paymentHashHex);
422
+ }
342
423
  }
343
424
  }
344
425
  const heldJson = this.storage.loadMetadata('held_htlcs');
@@ -408,10 +489,11 @@ class LightningNode extends events_1.EventEmitter {
408
489
  if (this.feeEstimator) {
409
490
  this.feeEstimator
410
491
  .estimateFee(6)
411
- .then((satPerVbyte) => {
492
+ .then((rawSatPerVbyte) => {
493
+ const satPerVbyte = this.clampEstimatedFeeRate(rawSatPerVbyte);
412
494
  if (satPerVbyte > 0) {
413
495
  this.feeAdvisor.recordSample(satPerVbyte);
414
- const feeratePerKw = Math.max((0, types_6.satPerVbyteToSatPerKw)(satPerVbyte), types_6.MIN_FEERATE_PER_KW);
496
+ const feeratePerKw = Math.max((0, types_7.satPerVbyteToSatPerKw)(satPerVbyte), types_7.MIN_FEERATE_PER_KW);
415
497
  for (const { channelId: monitorChannelId } of monitors) {
416
498
  const m = this.channelManager.getMonitor(Buffer.from(monitorChannelId, 'hex'));
417
499
  if (m && typeof m.updateFeeRate === 'function') {
@@ -503,6 +585,9 @@ class LightningNode extends events_1.EventEmitter {
503
585
  }
504
586
  }
505
587
  wireChannelManagerEvents() {
588
+ this.channelManager.on('watchtower:backup', (channelId, _peerPubkey, perCommitmentSecret, revokedTx) => {
589
+ this.backupRevokedStateToTowers(channelId, perCommitmentSecret, revokedTx);
590
+ });
506
591
  this.channelManager.on('channel:ready', (channelId) => {
507
592
  this.registerChannelAliases(channelId);
508
593
  this.persistChannel(channelId);
@@ -514,6 +599,27 @@ class LightningNode extends events_1.EventEmitter {
514
599
  this.emitReady();
515
600
  this.triggerPendingAnnouncementSigning(channelId);
516
601
  });
602
+ this.channelManager.on('channel:opening', (channelId, fundingTxid) => {
603
+ this.emit('channel:opening', { channelId, fundingTxid });
604
+ this.emitStructuredLog('channel', 'opening', {
605
+ channelId: channelId.toString('hex'),
606
+ fundingTxid: fundingTxid.toString('hex')
607
+ });
608
+ });
609
+ this.channelManager.on('channel:pending-close', (channelId, initiator) => {
610
+ this.emit('channel:pending-close', { channelId, initiator });
611
+ this.emitStructuredLog('channel', 'pending_close', {
612
+ channelId: channelId.toString('hex'),
613
+ initiator
614
+ });
615
+ });
616
+ this.channelManager.on('channel:force-closing', (channelId, initiator) => {
617
+ this.emit('channel:force-closing', { channelId, initiator });
618
+ this.emitStructuredLog('channel', 'force_closing', {
619
+ channelId: channelId.toString('hex'),
620
+ initiator
621
+ });
622
+ });
517
623
  this.channelManager.on('channel:closed', (channelId) => {
518
624
  this.persistChannel(channelId);
519
625
  this.emit('channel:closed', { channelId });
@@ -558,9 +664,11 @@ class LightningNode extends events_1.EventEmitter {
558
664
  });
559
665
  this.channelManager.on('htlc:fulfilled', (channelId, htlcId, preimage) => {
560
666
  this.handleHtlcFulfilled(channelId, htlcId, preimage);
667
+ this.emit('htlc:fulfilled', { channelId, htlcId });
561
668
  });
562
669
  this.channelManager.on('htlc:failed', (channelId, htlcId, reason) => {
563
670
  this.handleHtlcFailed(channelId, htlcId, reason);
671
+ this.emit('htlc:failed', { channelId, htlcId });
564
672
  });
565
673
  this.channelManager.on('error', (channelId, message) => {
566
674
  const err = {
@@ -594,13 +702,16 @@ class LightningNode extends events_1.EventEmitter {
594
702
  this.safeStorage(() => this.storage.saveChainMonitor(channelIdHex, monitor.getFullState()), 'saveChainMonitor');
595
703
  });
596
704
  this.channelManager.on('announcement:ready', (channelId, channelAnnouncement, channelUpdate) => {
597
- let signedChannelUpdate = channelUpdate;
598
- try {
599
- const sig = (0, validation_1.signChannelUpdate)(channelUpdate, this.nodePrivkey);
600
- signedChannelUpdate = Buffer.from(channelUpdate);
601
- sig.copy(signedChannelUpdate, 0);
602
- }
603
- catch {
705
+ let signedChannelUpdate = this.refreshChannelUpdate(channelUpdate, Math.floor(Date.now() / 1000), channelId);
706
+ if (!signedChannelUpdate) {
707
+ signedChannelUpdate = channelUpdate;
708
+ try {
709
+ const sig = (0, validation_1.signChannelUpdate)(channelUpdate, this.nodePrivkey);
710
+ signedChannelUpdate = Buffer.from(channelUpdate);
711
+ sig.copy(signedChannelUpdate, 0);
712
+ }
713
+ catch {
714
+ }
604
715
  }
605
716
  try {
606
717
  const annMsg = (0, messages_1.decodeChannelAnnouncementMessage)(channelAnnouncement);
@@ -697,14 +808,14 @@ class LightningNode extends events_1.EventEmitter {
697
808
  if (!this.peerManager)
698
809
  return;
699
810
  const gossipTypes = [
700
- types_4.MessageType.CHANNEL_ANNOUNCEMENT,
701
- types_4.MessageType.NODE_ANNOUNCEMENT,
702
- types_4.MessageType.CHANNEL_UPDATE,
703
- types_4.MessageType.QUERY_CHANNEL_RANGE,
704
- types_4.MessageType.REPLY_CHANNEL_RANGE,
705
- types_4.MessageType.QUERY_SHORT_CHANNEL_IDS,
706
- types_4.MessageType.REPLY_SHORT_CHANNEL_IDS_END,
707
- types_4.MessageType.GOSSIP_TIMESTAMP_FILTER
811
+ types_5.MessageType.CHANNEL_ANNOUNCEMENT,
812
+ types_5.MessageType.NODE_ANNOUNCEMENT,
813
+ types_5.MessageType.CHANNEL_UPDATE,
814
+ types_5.MessageType.QUERY_CHANNEL_RANGE,
815
+ types_5.MessageType.REPLY_CHANNEL_RANGE,
816
+ types_5.MessageType.QUERY_SHORT_CHANNEL_IDS,
817
+ types_5.MessageType.REPLY_SHORT_CHANNEL_IDS_END,
818
+ types_5.MessageType.GOSSIP_TIMESTAMP_FILTER
708
819
  ];
709
820
  for (const type of gossipTypes) {
710
821
  this.peerManager.onMessage(type, (pubkey, msgType, payload) => {
@@ -712,10 +823,149 @@ class LightningNode extends events_1.EventEmitter {
712
823
  });
713
824
  }
714
825
  }
826
+ registerPeerStorageHandlers() {
827
+ if (!this.peerManager)
828
+ return;
829
+ this.peerManager.onMessage(types_5.MessageType.PEER_STORAGE, (pubkey, _t, payload) => {
830
+ this.handlePeerStorageMessage(pubkey, payload);
831
+ });
832
+ this.peerManager.onMessage(types_5.MessageType.PEER_STORAGE_RETRIEVAL, (pubkey, _t, payload) => {
833
+ this.handlePeerStorageRetrievalMessage(pubkey, payload);
834
+ });
835
+ }
836
+ handlePeerStorageMessage(pubkey, payload) {
837
+ if (!this.peerStorageEnabled)
838
+ return;
839
+ let blob;
840
+ try {
841
+ blob = (0, peer_storage_1.decodePeerStorageMessage)(payload).blob;
842
+ }
843
+ catch (err) {
844
+ this.emitStructuredLog('peer', 'peer_storage_invalid', {
845
+ pubkey,
846
+ error: err instanceof Error ? err.message : String(err)
847
+ });
848
+ return;
849
+ }
850
+ if (!this.peerQualifiesForStorage(pubkey)) {
851
+ this.emitStructuredLog('peer', 'peer_storage_rejected', {
852
+ pubkey,
853
+ reason: 'no channel and not trusted'
854
+ });
855
+ return;
856
+ }
857
+ const now = Date.now();
858
+ const last = this.peerStorageLastAccepted.get(pubkey);
859
+ if (last !== undefined &&
860
+ now - last < LightningNode.PEER_STORAGE_MIN_INTERVAL_MS) {
861
+ return;
862
+ }
863
+ this.peerStorageLastAccepted.set(pubkey, now);
864
+ this.peerStorageBlobs.set(pubkey, { blob, receivedAt: now });
865
+ if (this.storage?.savePeerStorageBlob) {
866
+ this.safeStorage(() => this.storage.savePeerStorageBlob(pubkey, blob, now), 'savePeerStorageBlob');
867
+ }
868
+ }
869
+ handlePeerStorageRetrievalMessage(pubkey, payload) {
870
+ if (!this.peerStorageEnabled)
871
+ return;
872
+ let blob;
873
+ try {
874
+ blob = (0, peer_storage_1.decodePeerStorageRetrievalMessage)(payload).blob;
875
+ }
876
+ catch (err) {
877
+ this.emitStructuredLog('peer', 'peer_storage_retrieval_invalid', {
878
+ pubkey,
879
+ error: err instanceof Error ? err.message : String(err)
880
+ });
881
+ return;
882
+ }
883
+ this.retrievedPeerStorage.set(pubkey, { blob, receivedAt: Date.now() });
884
+ this.emit('peer_storage:retrieved', pubkey, blob);
885
+ }
886
+ peerQualifiesForStorage(pubkey) {
887
+ if (this.channelManager.isTrustedPeer(pubkey))
888
+ return true;
889
+ for (const channel of this.channelManager.listChannels()) {
890
+ const channelId = channel.getChannelId();
891
+ if (!channelId)
892
+ continue;
893
+ if (this.channelManager.getPeerForChannel(channelId) !== pubkey)
894
+ continue;
895
+ if (channel.getState() !== types_1.ChannelState.CLOSED)
896
+ return true;
897
+ }
898
+ return false;
899
+ }
900
+ sendPeerStorageOnConnect(pubkey) {
901
+ if (!this.peerStorageEnabled || !this.peerManager)
902
+ return;
903
+ let held = this.peerStorageBlobs.get(pubkey);
904
+ if (!held && this.storage?.loadPeerStorageBlob) {
905
+ try {
906
+ const loaded = this.storage.loadPeerStorageBlob(pubkey);
907
+ if (loaded) {
908
+ held = loaded;
909
+ this.peerStorageBlobs.set(pubkey, loaded);
910
+ }
911
+ }
912
+ catch (err) {
913
+ this.emitStructuredLog('peer', 'peer_storage_load_failed', {
914
+ pubkey,
915
+ error: err instanceof Error ? err.message : String(err)
916
+ });
917
+ }
918
+ }
919
+ try {
920
+ if (held) {
921
+ this.peerManager.sendToPeer(pubkey, types_5.MessageType.PEER_STORAGE_RETRIEVAL, (0, peer_storage_1.encodePeerStorageRetrievalMessage)({ blob: held.blob }));
922
+ }
923
+ if (this.ourPeerStorageBlob && this.peerAdvertisesPeerStorage(pubkey)) {
924
+ this.peerManager.sendToPeer(pubkey, types_5.MessageType.PEER_STORAGE, (0, peer_storage_1.encodePeerStorageMessage)({ blob: this.ourPeerStorageBlob }));
925
+ }
926
+ }
927
+ catch {
928
+ }
929
+ }
930
+ peerAdvertisesPeerStorage(pubkey) {
931
+ const init = this.peerManager?.getPeer(pubkey)?.getRemoteInit();
932
+ return init ? init.features.hasFeature(flags_1.Feature.PROVIDE_STORAGE) : false;
933
+ }
934
+ distributePeerStorage(blob) {
935
+ if (blob.length > peer_storage_1.PEER_STORAGE_MAX_BYTES) {
936
+ throw new Error(`peer storage blob too large: ${blob.length} > ${peer_storage_1.PEER_STORAGE_MAX_BYTES} bytes`);
937
+ }
938
+ if (!this.peerStorageEnabled)
939
+ return 0;
940
+ this.ourPeerStorageBlob = Buffer.from(blob);
941
+ if (!this.peerManager)
942
+ return 0;
943
+ const payload = (0, peer_storage_1.encodePeerStorageMessage)({ blob: this.ourPeerStorageBlob });
944
+ let sent = 0;
945
+ for (const peer of this.peerManager.listPeers()) {
946
+ if (!this.peerAdvertisesPeerStorage(peer.pubkey))
947
+ continue;
948
+ try {
949
+ this.peerManager.sendToPeer(peer.pubkey, types_5.MessageType.PEER_STORAGE, payload);
950
+ sent++;
951
+ }
952
+ catch {
953
+ }
954
+ }
955
+ return sent;
956
+ }
957
+ getRetrievedPeerStorage() {
958
+ return [...this.retrievedPeerStorage.entries()].map(([peerPubkey, { blob, receivedAt }]) => ({
959
+ peerPubkey,
960
+ blob: Buffer.from(blob),
961
+ receivedAt
962
+ }));
963
+ }
715
964
  wirePeerManagerEvents() {
716
965
  if (!this.peerManager)
717
966
  return;
718
967
  this.peerManager.on('peer:connect', (pubkey) => {
968
+ this.sendPeerStorageOnConnect(pubkey);
719
969
  this.channelManager.handlePeerReconnected(pubkey);
720
970
  this.sendOwnGossipTo(pubkey);
721
971
  if (this.peerManager) {
@@ -802,17 +1052,19 @@ class LightningNode extends events_1.EventEmitter {
802
1052
  if (!state.remoteBasepoints)
803
1053
  return;
804
1054
  const networkMap = {
805
- [types_3.Network.MAINNET]: bitcoin.networks.bitcoin,
806
- [types_3.Network.TESTNET]: bitcoin.networks.testnet,
807
- [types_3.Network.REGTEST]: bitcoin.networks.regtest,
808
- [types_3.Network.SIGNET]: bitcoin.networks.testnet
1055
+ [types_4.Network.MAINNET]: bitcoin.networks.bitcoin,
1056
+ [types_4.Network.TESTNET]: bitcoin.networks.testnet,
1057
+ [types_4.Network.REGTEST]: bitcoin.networks.regtest,
1058
+ [types_4.Network.SIGNET]: bitcoin.networks.testnet
809
1059
  };
810
1060
  const btcNetwork = networkMap[this.network] || bitcoin.networks.regtest;
811
- const { address } = (0, types_7.isTaprootChannel)(state.channelType)
1061
+ const { address } = (0, types_8.isTaprootChannel)(state.channelType)
812
1062
  ? (0, funding_taproot_1.createTaprootFundingScript)(state.localBasepoints.fundingPubkey, state.remoteBasepoints.fundingPubkey, btcNetwork)
813
1063
  : (0, funding_1.createFundingScript)(state.localBasepoints.fundingPubkey, state.remoteBasepoints.fundingPubkey, btcNetwork);
814
1064
  const feePromise = this.feeEstimator
815
- ? this.feeEstimator.estimateFee(6).then((f) => (f > 0 ? f : undefined))
1065
+ ? this.feeEstimator
1066
+ .estimateFee(6)
1067
+ .then((f) => (f > 0 ? this.clampEstimatedFeeRate(f) : undefined))
816
1068
  : Promise.resolve(undefined);
817
1069
  feePromise
818
1070
  .then((satsPerByte) => this.fundingProvider.buildFundingTransaction(address, state.fundingSatoshis, satsPerByte))
@@ -836,6 +1088,21 @@ class LightningNode extends events_1.EventEmitter {
836
1088
  getNodeId() {
837
1089
  return this.nodeId;
838
1090
  }
1091
+ signMessage(message) {
1092
+ return (0, message_signing_1.signMessageWithKey)(message, this.nodePrivkey);
1093
+ }
1094
+ verifyMessage(message, signature) {
1095
+ const result = (0, message_signing_1.verifyMessageSignature)(message, signature);
1096
+ if (!result.valid || !result.pubkey) {
1097
+ return { valid: false, pubkey: null, knownNode: false };
1098
+ }
1099
+ const knownNode = this.graph.getNode(result.pubkey) !== undefined;
1100
+ return {
1101
+ valid: true,
1102
+ pubkey: result.pubkey.toString('hex'),
1103
+ knownNode
1104
+ };
1105
+ }
839
1106
  getNodeInfo() {
840
1107
  return {
841
1108
  nodeId: this.nodeId,
@@ -846,6 +1113,148 @@ class LightningNode extends events_1.EventEmitter {
846
1113
  alias: this.alias
847
1114
  };
848
1115
  }
1116
+ buildStaticChannelBackupData() {
1117
+ const peerAddresses = new Map();
1118
+ if (this.storage) {
1119
+ for (const addr of this.storage.loadAllPeerAddresses()) {
1120
+ const list = peerAddresses.get(addr.pubkey) ?? [];
1121
+ list.push(`${addr.host}:${addr.port}`);
1122
+ peerAddresses.set(addr.pubkey, list);
1123
+ }
1124
+ }
1125
+ const channels = [];
1126
+ const seen = new Set();
1127
+ for (const channel of this.channelManager.listChannels()) {
1128
+ const state = channel.getFullState();
1129
+ if (!state.fundingTxid || !state.channelId)
1130
+ continue;
1131
+ if (state.state === types_1.ChannelState.CLOSED)
1132
+ continue;
1133
+ const idHex = state.channelId.toString('hex');
1134
+ if (seen.has(idHex))
1135
+ continue;
1136
+ const peer = this.channelManager.getPeerForChannel(state.channelId);
1137
+ if (!peer)
1138
+ continue;
1139
+ seen.add(idHex);
1140
+ channels.push({
1141
+ channelId: idHex,
1142
+ peerNodeId: peer,
1143
+ peerAddresses: peerAddresses.get(peer) ?? [],
1144
+ fundingTxid: state.fundingTxid.toString('hex'),
1145
+ fundingOutputIndex: state.fundingOutputIndex,
1146
+ fundingSatoshis: state.fundingSatoshis.toString(),
1147
+ channelKeyIndex: channel.channelKeyIndex,
1148
+ channelType: state.channelType ? state.channelType.toString('hex') : '',
1149
+ role: state.role === types_1.ChannelRole.OPENER ? 'OPENER' : 'ACCEPTOR',
1150
+ isTaproot: (0, types_8.isTaprootChannel)(state.channelType),
1151
+ isAnchor: (0, types_8.isAnchorChannel)(state.channelType)
1152
+ });
1153
+ }
1154
+ return { network: this.network, channels };
1155
+ }
1156
+ async recoverFromStaticChannelBackup(entries) {
1157
+ const recovering = [];
1158
+ const skipped = [];
1159
+ for (const entry of entries) {
1160
+ const channelId = Buffer.from(entry.channelId, 'hex');
1161
+ if (channelId.length !== 32 ||
1162
+ channelId.toString('hex') !== entry.channelId.toLowerCase()) {
1163
+ skipped.push({
1164
+ channelId: entry.channelId,
1165
+ reason: 'invalid channelId (expected 32-byte hex)'
1166
+ });
1167
+ continue;
1168
+ }
1169
+ if (this.channelManager.getChannel(channelId)) {
1170
+ skipped.push({
1171
+ channelId: entry.channelId,
1172
+ reason: 'channel already exists'
1173
+ });
1174
+ continue;
1175
+ }
1176
+ const material = this.channelManager.getRecoveryChannelMaterial(entry.channelKeyIndex);
1177
+ const stateParams = {
1178
+ temporaryChannelId: Buffer.from(channelId),
1179
+ fundingSatoshis: BigInt(entry.fundingSatoshis),
1180
+ pushMsat: 0n,
1181
+ localConfig: material.localConfig,
1182
+ localBasepoints: material.basepoints,
1183
+ localPerCommitmentSeed: material.perCommitmentSeed
1184
+ };
1185
+ const state = entry.role === 'ACCEPTOR'
1186
+ ? (0, channel_state_1.createAcceptorState)({
1187
+ ...stateParams,
1188
+ remoteBasepoints: material.basepoints,
1189
+ remoteConfig: { ...types_1.DEFAULT_CHANNEL_CONFIG }
1190
+ })
1191
+ : (0, channel_state_1.createOpenerState)(stateParams);
1192
+ state.remoteBasepoints = null;
1193
+ state.channelId = channelId;
1194
+ state.fundingTxid = Buffer.from(entry.fundingTxid, 'hex');
1195
+ state.fundingOutputIndex = entry.fundingOutputIndex;
1196
+ state.channelType = entry.channelType
1197
+ ? Buffer.from(entry.channelType, 'hex')
1198
+ : null;
1199
+ state.localCommitmentNumber = 0n;
1200
+ state.remoteCommitmentNumber = 0n;
1201
+ state.localBalanceMsat = 0n;
1202
+ state.remoteBalanceMsat = 0n;
1203
+ state.announceChannel = false;
1204
+ state.state = types_1.ChannelState.ERRORED;
1205
+ state.dataLossDetected = true;
1206
+ const channel = new channel_1.Channel(state);
1207
+ channel.channelKeyIndex = entry.channelKeyIndex;
1208
+ this.channelManager.restoreChannel(channel, entry.peerNodeId, entry.channelKeyIndex);
1209
+ this.persistChannel(channelId);
1210
+ recovering.push(entry.channelId);
1211
+ this.emitStructuredLog('channel', 'recovery_started', {
1212
+ channelId: entry.channelId,
1213
+ peerNodeId: entry.peerNodeId,
1214
+ fundingTxid: entry.fundingTxid,
1215
+ fundingOutputIndex: entry.fundingOutputIndex,
1216
+ channelKeyIndex: entry.channelKeyIndex
1217
+ });
1218
+ if (this.chainWatcher) {
1219
+ try {
1220
+ await this.watchRecoveredFundingOutput(channelId, state);
1221
+ }
1222
+ catch (err) {
1223
+ this.emit('node:error', {
1224
+ code: 'RECOVERY_WATCH_FAILED',
1225
+ channelId,
1226
+ message: `Failed to watch recovered funding output: ${err.message}`,
1227
+ timestamp: Date.now()
1228
+ });
1229
+ }
1230
+ }
1231
+ if (this.peerManager && entry.peerAddresses.length > 0) {
1232
+ void this.contactRecoveryPeer(entry.peerNodeId, entry.peerAddresses);
1233
+ }
1234
+ }
1235
+ return { recovering, skipped };
1236
+ }
1237
+ async contactRecoveryPeer(peerNodeId, addresses) {
1238
+ for (const address of addresses) {
1239
+ const sep = address.lastIndexOf(':');
1240
+ if (sep <= 0)
1241
+ continue;
1242
+ const host = address.slice(0, sep).replace(/^\[|\]$/g, '');
1243
+ const port = parseInt(address.slice(sep + 1), 10);
1244
+ if (!Number.isFinite(port) || port <= 0)
1245
+ continue;
1246
+ try {
1247
+ await this.connectPeer(peerNodeId, host, port);
1248
+ return;
1249
+ }
1250
+ catch {
1251
+ }
1252
+ }
1253
+ this.emitStructuredLog('peer', 'recovery_connect_failed', {
1254
+ peerNodeId,
1255
+ addresses
1256
+ });
1257
+ }
849
1258
  getSweepDestinationScript() {
850
1259
  if (this.sweepDestinationScript) {
851
1260
  return this.sweepDestinationScript;
@@ -857,6 +1266,92 @@ class LightningNode extends events_1.EventEmitter {
857
1266
  return Buffer.alloc(22);
858
1267
  }
859
1268
  }
1269
+ getBitcoinNetwork() {
1270
+ if (this.network === types_4.Network.MAINNET)
1271
+ return bitcoin.networks.bitcoin;
1272
+ if (this.network === types_4.Network.REGTEST)
1273
+ return bitcoin.networks.regtest;
1274
+ return bitcoin.networks.testnet;
1275
+ }
1276
+ initWatchtowerClient(towers) {
1277
+ if (towers.length === 0) {
1278
+ this.watchtowerClient = null;
1279
+ return;
1280
+ }
1281
+ const btcNetwork = this.getBitcoinNetwork();
1282
+ const store = this.storage &&
1283
+ typeof this.storage.saveWatchtowerSession ===
1284
+ 'function'
1285
+ ? this.storage
1286
+ : undefined;
1287
+ this.watchtowerClient = new watchtower_1.WatchtowerClient({
1288
+ localPrivateKey: this.nodePrivkey,
1289
+ chainHash: (0, watchtower_1.chainHashForNetwork)(btcNetwork),
1290
+ network: btcNetwork,
1291
+ towers,
1292
+ store,
1293
+ socks5Proxy: this.socks5Proxy ?? undefined
1294
+ });
1295
+ this.watchtowerClient.on('log', (entry) => {
1296
+ const event = String(entry.event ?? 'log');
1297
+ this.emitStructuredLog('watchtower', event, entry);
1298
+ });
1299
+ }
1300
+ backupRevokedStateToTowers(channelId, perCommitmentSecret, revokedTx) {
1301
+ const client = this.watchtowerClient;
1302
+ if (!client || !client.enabled)
1303
+ return;
1304
+ try {
1305
+ const channel = this.channelManager.getChannel(channelId);
1306
+ if (!channel)
1307
+ return;
1308
+ const state = channel.getFullState();
1309
+ if (!state.remoteBasepoints)
1310
+ return;
1311
+ const perCh = this.channelManager.getMonitorSigningKeys(channelId);
1312
+ const revocationBasepointSecret = perCh?.revocationBasepointSecret ?? this.revocationBasepointSecret;
1313
+ if (!revocationBasepointSecret)
1314
+ return;
1315
+ const paymentBasepointSecret = perCh?.paymentBasepointSecret ?? this.paymentBasepointSecret;
1316
+ const btcNetwork = this.getBitcoinNetwork();
1317
+ const ctx = {
1318
+ channelId: channelId.toString('hex'),
1319
+ revokedTx: bitcoin.Transaction.fromBuffer(revokedTx),
1320
+ perCommitmentSecret,
1321
+ revocationBasepoint: state.localBasepoints.revocationBasepoint,
1322
+ revocationBasepointSecret,
1323
+ remoteDelayedBasepoint: state.remoteBasepoints.delayedPaymentBasepoint,
1324
+ toSelfDelay: state.localConfig.toSelfDelay,
1325
+ isAnchor: (0, types_8.isAnchorChannel)(state.channelType),
1326
+ localPaymentPubkey: state.localBasepoints.paymentBasepoint,
1327
+ paymentBasepointSecret,
1328
+ sweepScript: this.getSweepDestinationScript(),
1329
+ network: btcNetwork
1330
+ };
1331
+ client.backupRevokedState(ctx);
1332
+ }
1333
+ catch (err) {
1334
+ this.emitStructuredLog('watchtower', 'backup_context_failed', {
1335
+ channelId: channelId.toString('hex'),
1336
+ error: err instanceof Error ? err.message : String(err)
1337
+ });
1338
+ }
1339
+ }
1340
+ addWatchtower(uri) {
1341
+ if (!this.watchtowerClient) {
1342
+ this.initWatchtowerClient([uri]);
1343
+ const client = this.watchtowerClient;
1344
+ void client?.start();
1345
+ return;
1346
+ }
1347
+ this.watchtowerClient.addTower(uri);
1348
+ }
1349
+ removeWatchtower(uri) {
1350
+ this.watchtowerClient?.removeTower(uri);
1351
+ }
1352
+ getWatchtowers() {
1353
+ return this.watchtowerClient ? this.watchtowerClient.getHealth() : [];
1354
+ }
860
1355
  setSweepDestinationScript(destinationScript) {
861
1356
  this.sweepDestinationScript = destinationScript;
862
1357
  this.chainWatcher?.setDestinationScript(destinationScript);
@@ -884,7 +1379,7 @@ class LightningNode extends events_1.EventEmitter {
884
1379
  let feeRatePerVbyte = opts?.feeRatePerVbyte ?? 0;
885
1380
  if (feeRatePerVbyte <= 0 && this.feeEstimator) {
886
1381
  try {
887
- feeRatePerVbyte = await this.feeEstimator.estimateFee(6);
1382
+ feeRatePerVbyte = this.clampEstimatedFeeRate(await this.feeEstimator.estimateFee(6));
888
1383
  }
889
1384
  catch {
890
1385
  }
@@ -918,10 +1413,10 @@ class LightningNode extends events_1.EventEmitter {
918
1413
  }
919
1414
  getFundingAddress() {
920
1415
  const networkMap = {
921
- [types_3.Network.MAINNET]: bitcoin.networks.bitcoin,
922
- [types_3.Network.TESTNET]: bitcoin.networks.testnet,
923
- [types_3.Network.REGTEST]: bitcoin.networks.regtest,
924
- [types_3.Network.SIGNET]: bitcoin.networks.testnet
1416
+ [types_4.Network.MAINNET]: bitcoin.networks.bitcoin,
1417
+ [types_4.Network.TESTNET]: bitcoin.networks.testnet,
1418
+ [types_4.Network.REGTEST]: bitcoin.networks.regtest,
1419
+ [types_4.Network.SIGNET]: bitcoin.networks.testnet
925
1420
  };
926
1421
  const btcNetwork = networkMap[this.network] || bitcoin.networks.regtest;
927
1422
  const { address } = bitcoin.payments.p2wpkh({
@@ -949,6 +1444,13 @@ class LightningNode extends events_1.EventEmitter {
949
1444
  const pubkeyErr = (0, validation_2.validateHexPubkey)(pubkey, 'pubkey');
950
1445
  if (pubkeyErr)
951
1446
  throw new Error(pubkeyErr);
1447
+ if (host === undefined && port === undefined) {
1448
+ await this.connectPeerById(pubkey);
1449
+ return;
1450
+ }
1451
+ if (host === undefined || port === undefined) {
1452
+ throw new Error('host and port must be provided together (omit both to resolve from gossip/DNS)');
1453
+ }
952
1454
  const hostErr = (0, validation_2.validateHost)(host);
953
1455
  if (hostErr)
954
1456
  throw new Error(hostErr);
@@ -957,6 +1459,58 @@ class LightningNode extends events_1.EventEmitter {
957
1459
  throw new Error(portErr);
958
1460
  await this.peerManager.connectPeer(pubkey, host, port);
959
1461
  }
1462
+ async connectPeerById(pubkey) {
1463
+ const attempts = [];
1464
+ const isTor = (a) => a.type === types_2.ADDRESS_TYPE_TORV2 || a.type === types_2.ADDRESS_TYPE_TORV3;
1465
+ const announced = this.graph.getNode(Buffer.from(pubkey, 'hex'))?.announcement?.addresses ??
1466
+ [];
1467
+ let skippedTor = 0;
1468
+ const candidates = [];
1469
+ for (const addr of announced) {
1470
+ if (isTor(addr) && !this.socks5Proxy) {
1471
+ skippedTor++;
1472
+ continue;
1473
+ }
1474
+ const dialable = (0, messages_1.nodeAddressToHostPort)(addr);
1475
+ if (dialable)
1476
+ candidates.push(dialable);
1477
+ }
1478
+ for (const { host, port } of candidates) {
1479
+ try {
1480
+ await this.peerManager.connectPeer(pubkey, host, port);
1481
+ return;
1482
+ }
1483
+ catch (err) {
1484
+ attempts.push(`graph ${host}:${port} (${err instanceof Error ? err.message : String(err)})`);
1485
+ }
1486
+ }
1487
+ if (skippedTor > 0) {
1488
+ attempts.push(`skipped ${skippedTor} Tor address(es): no socks5Proxy configured`);
1489
+ }
1490
+ if (candidates.length === 0) {
1491
+ let seedPeers = [];
1492
+ try {
1493
+ seedPeers = await this.bootstrapPeers();
1494
+ }
1495
+ catch (err) {
1496
+ attempts.push(`DNS bootstrap failed (${err instanceof Error ? err.message : String(err)})`);
1497
+ }
1498
+ const matches = seedPeers.filter((p) => p.pubkey.toString('hex') === pubkey);
1499
+ if (matches.length === 0) {
1500
+ attempts.push('DNS bootstrap returned no address for this node id');
1501
+ }
1502
+ for (const peer of matches) {
1503
+ try {
1504
+ await this.peerManager.connectPeer(pubkey, peer.host, peer.port);
1505
+ return;
1506
+ }
1507
+ catch (err) {
1508
+ attempts.push(`dns ${peer.host}:${peer.port} (${err instanceof Error ? err.message : String(err)})`);
1509
+ }
1510
+ }
1511
+ }
1512
+ throw new Error(`Unable to resolve a connection to ${pubkey}: ${attempts.join('; ')}`);
1513
+ }
960
1514
  disconnectPeer(pubkey) {
961
1515
  if (!this.peerManager) {
962
1516
  throw new Error('Networking is not enabled');
@@ -1033,6 +1587,13 @@ class LightningNode extends events_1.EventEmitter {
1033
1587
  });
1034
1588
  }
1035
1589
  async startChainWatcher() {
1590
+ if (this.watchtowerClient) {
1591
+ this.watchtowerClient.start().catch((err) => {
1592
+ this.emitStructuredLog('watchtower', 'start_failed', {
1593
+ error: err instanceof Error ? err.message : String(err)
1594
+ });
1595
+ });
1596
+ }
1036
1597
  if (this.chainWatcher) {
1037
1598
  this.wireChainWatcherEvents();
1038
1599
  await this.chainWatcher.start();
@@ -1050,10 +1611,10 @@ class LightningNode extends events_1.EventEmitter {
1050
1611
  if (!this.chainWatcher)
1051
1612
  return;
1052
1613
  const networkMap = {
1053
- [types_3.Network.MAINNET]: bitcoin.networks.bitcoin,
1054
- [types_3.Network.TESTNET]: bitcoin.networks.testnet,
1055
- [types_3.Network.REGTEST]: bitcoin.networks.regtest,
1056
- [types_3.Network.SIGNET]: bitcoin.networks.testnet
1614
+ [types_4.Network.MAINNET]: bitcoin.networks.bitcoin,
1615
+ [types_4.Network.TESTNET]: bitcoin.networks.testnet,
1616
+ [types_4.Network.REGTEST]: bitcoin.networks.regtest,
1617
+ [types_4.Network.SIGNET]: bitcoin.networks.testnet
1057
1618
  };
1058
1619
  const btcNetwork = networkMap[this.network] || bitcoin.networks.regtest;
1059
1620
  for (const channel of this.channelManager.listChannels()) {
@@ -1066,10 +1627,10 @@ class LightningNode extends events_1.EventEmitter {
1066
1627
  continue;
1067
1628
  if (monitor) {
1068
1629
  for (const output of monitor.getTrackedOutputs()) {
1069
- if (output.status === types_6.OutputStatus.IRREVOCABLY_RESOLVED)
1630
+ if (output.status === types_7.OutputStatus.IRREVOCABLY_RESOLVED)
1070
1631
  continue;
1071
1632
  try {
1072
- const seedTxid = output.status === types_6.OutputStatus.SPEND_CONFIRMED
1633
+ const seedTxid = output.status === types_7.OutputStatus.SPEND_CONFIRMED
1073
1634
  ? output.resolutionTxid
1074
1635
  : undefined;
1075
1636
  const seedHeight = seedTxid !== undefined ? output.confirmationHeight : undefined;
@@ -1093,10 +1654,10 @@ class LightningNode extends events_1.EventEmitter {
1093
1654
  continue;
1094
1655
  if (monitor) {
1095
1656
  for (const output of monitor.getTrackedOutputs()) {
1096
- if (output.status === types_6.OutputStatus.IRREVOCABLY_RESOLVED)
1657
+ if (output.status === types_7.OutputStatus.IRREVOCABLY_RESOLVED)
1097
1658
  continue;
1098
1659
  try {
1099
- const seedTxid = output.status === types_6.OutputStatus.SPEND_CONFIRMED
1660
+ const seedTxid = output.status === types_7.OutputStatus.SPEND_CONFIRMED
1100
1661
  ? output.resolutionTxid
1101
1662
  : undefined;
1102
1663
  const seedHeight = seedTxid !== undefined ? output.confirmationHeight : undefined;
@@ -1107,8 +1668,22 @@ class LightningNode extends events_1.EventEmitter {
1107
1668
  }
1108
1669
  }
1109
1670
  }
1110
- if (!state.remoteBasepoints)
1671
+ if (!state.remoteBasepoints) {
1672
+ if (state.dataLossDetected && state.fundingTxid) {
1673
+ try {
1674
+ await this.watchRecoveredFundingOutput(state.channelId || state.temporaryChannelId, state);
1675
+ }
1676
+ catch (err) {
1677
+ this.emit('node:error', {
1678
+ code: 'RECOVERY_WATCH_FAILED',
1679
+ channelId: state.channelId || state.temporaryChannelId,
1680
+ message: `Failed to watch recovered funding output: ${err.message}`,
1681
+ timestamp: Date.now()
1682
+ });
1683
+ }
1684
+ }
1111
1685
  continue;
1686
+ }
1112
1687
  const { p2wshOutput } = (0, funding_1.createFundingScript)(state.localBasepoints.fundingPubkey, state.remoteBasepoints.fundingPubkey, btcNetwork);
1113
1688
  const inflight = state.spliceInFlight;
1114
1689
  if (inflight) {
@@ -1130,6 +1705,21 @@ class LightningNode extends events_1.EventEmitter {
1130
1705
  await this.chainWatcher.watchFundingOutput(state.channelId || state.temporaryChannelId, txidHex, state.fundingOutputIndex, state.minimumDepth ?? 3, p2wshOutput);
1131
1706
  }
1132
1707
  }
1708
+ async watchRecoveredFundingOutput(channelId, state) {
1709
+ if (!this.chainWatcher || !this._chainBackend) {
1710
+ throw new Error('Chain backend is not available');
1711
+ }
1712
+ if (!state.fundingTxid) {
1713
+ throw new Error('Recovered channel has no funding txid');
1714
+ }
1715
+ const txidHex = Buffer.from(state.fundingTxid).reverse().toString('hex');
1716
+ const rawTx = await this._chainBackend.getTransaction(txidHex);
1717
+ const fundingTx = bitcoin.Transaction.fromBuffer(rawTx);
1718
+ if (state.fundingOutputIndex >= fundingTx.outs.length) {
1719
+ throw new Error(`Funding output index ${state.fundingOutputIndex} out of range for tx ${txidHex}`);
1720
+ }
1721
+ await this.chainWatcher.watchFundingOutput(channelId, txidHex, state.fundingOutputIndex, state.minimumDepth || 1, Buffer.from(fundingTx.outs[state.fundingOutputIndex].script));
1722
+ }
1133
1723
  async bootstrapPeers(config) {
1134
1724
  return (0, bootstrap_1.bootstrapPeers)(config);
1135
1725
  }
@@ -1198,6 +1788,14 @@ class LightningNode extends events_1.EventEmitter {
1198
1788
  clearInterval(this._gossipRefreshTimer);
1199
1789
  this._gossipRefreshTimer = undefined;
1200
1790
  }
1791
+ if (this.autoRebalanceTimer) {
1792
+ clearInterval(this.autoRebalanceTimer);
1793
+ this.autoRebalanceTimer = null;
1794
+ }
1795
+ if (this.autoTuneFeesTimer) {
1796
+ clearInterval(this.autoTuneFeesTimer);
1797
+ this.autoTuneFeesTimer = null;
1798
+ }
1201
1799
  for (const t of this._reconnectTimers) {
1202
1800
  clearTimeout(t);
1203
1801
  }
@@ -1214,6 +1812,9 @@ class LightningNode extends events_1.EventEmitter {
1214
1812
  if (this.chainWatcher) {
1215
1813
  this.chainWatcher.stop();
1216
1814
  }
1815
+ if (this.watchtowerClient) {
1816
+ this.watchtowerClient.stop();
1817
+ }
1217
1818
  if (this.peerManager) {
1218
1819
  this.peerManager.destroy();
1219
1820
  }
@@ -1315,8 +1916,8 @@ class LightningNode extends events_1.EventEmitter {
1315
1916
  const max = this.resourceConfig.maxCompletedPayments;
1316
1917
  let pruned = 0;
1317
1918
  for (const [hash, payment] of this.payments) {
1318
- if (payment.status === types_5.PaymentStatus.COMPLETED ||
1319
- payment.status === types_5.PaymentStatus.FAILED) {
1919
+ if (payment.status === types_6.PaymentStatus.COMPLETED ||
1920
+ payment.status === types_6.PaymentStatus.FAILED) {
1320
1921
  const age = now - (payment.completedAt || payment.createdAt);
1321
1922
  if (age > ttl) {
1322
1923
  this.payments.delete(hash);
@@ -1327,8 +1928,8 @@ class LightningNode extends events_1.EventEmitter {
1327
1928
  }
1328
1929
  const completed = [];
1329
1930
  for (const entry of this.payments) {
1330
- if (entry[1].status === types_5.PaymentStatus.COMPLETED ||
1331
- entry[1].status === types_5.PaymentStatus.FAILED) {
1931
+ if (entry[1].status === types_6.PaymentStatus.COMPLETED ||
1932
+ entry[1].status === types_6.PaymentStatus.FAILED) {
1332
1933
  completed.push(entry);
1333
1934
  }
1334
1935
  }
@@ -1444,6 +2045,217 @@ class LightningNode extends events_1.EventEmitter {
1444
2045
  }
1445
2046
  return { ok: true };
1446
2047
  }
2048
+ setChannelPolicy(channelId, policy) {
2049
+ this.validateChannelPolicyFields(policy);
2050
+ let targets;
2051
+ if (channelId === 'all') {
2052
+ targets = this.channelManager
2053
+ .listChannels()
2054
+ .map((ch) => ch.getChannelId())
2055
+ .filter((id) => id !== null);
2056
+ }
2057
+ else {
2058
+ const cidErr = (0, validation_2.validateBuffer)(channelId, 32, 'channelId');
2059
+ if (cidErr)
2060
+ throw new Error(cidErr);
2061
+ if (!this.channelManager.getChannel(channelId)) {
2062
+ throw new Error(`Channel not found: ${channelId.toString('hex')}`);
2063
+ }
2064
+ targets = [channelId];
2065
+ }
2066
+ for (const target of targets) {
2067
+ const hex = target.toString('hex');
2068
+ const merged = {
2069
+ ...this.channelPolicies.get(hex),
2070
+ ...policy
2071
+ };
2072
+ if (merged.htlcMinimumMsat !== undefined &&
2073
+ merged.htlcMaximumMsat !== undefined &&
2074
+ merged.htlcMinimumMsat > merged.htlcMaximumMsat) {
2075
+ throw new Error(`htlcMinimumMsat (${merged.htlcMinimumMsat}) exceeds htlcMaximumMsat (${merged.htlcMaximumMsat})`);
2076
+ }
2077
+ this.channelPolicies.set(hex, merged);
2078
+ this.safeStorage(() => this.storage.saveChannelPolicy?.(hex, LightningNode.serializeChannelPolicy(merged)), 'saveChannelPolicy');
2079
+ this.regenerateChannelUpdateForPolicy(target);
2080
+ this.emitStructuredLog('channel', 'policy_updated', {
2081
+ channelId: hex,
2082
+ ...LightningNode.serializeChannelPolicy(merged)
2083
+ });
2084
+ }
2085
+ }
2086
+ getChannelPolicy(channelId) {
2087
+ const channel = this.channelManager.getChannel(channelId);
2088
+ if (!channel)
2089
+ return null;
2090
+ const state = channel.getFullState();
2091
+ const override = this.channelPolicies.get(channelId.toString('hex'));
2092
+ const capacityMsat = state.fundingSatoshis * 1000n;
2093
+ const defaultHtlcMax = state.localConfig.maxHtlcValueInFlightMsat > capacityMsat
2094
+ ? capacityMsat
2095
+ : state.localConfig.maxHtlcValueInFlightMsat;
2096
+ return {
2097
+ feeBaseMsat: override?.feeBaseMsat ?? this.forwardingFeeBaseMsat,
2098
+ feeProportionalMillionths: override?.feeProportionalMillionths ?? this.forwardingFeePropMillionths,
2099
+ cltvExpiryDelta: override?.cltvExpiryDelta ?? this.forwardingCltvDelta,
2100
+ htlcMinimumMsat: override?.htlcMinimumMsat ?? state.localConfig.htlcMinimumMsat,
2101
+ htlcMaximumMsat: override?.htlcMaximumMsat ?? defaultHtlcMax,
2102
+ source: override && Object.keys(override).length > 0 ? 'override' : 'default'
2103
+ };
2104
+ }
2105
+ getForwardingPolicyForChannel(channelId) {
2106
+ const override = channelId
2107
+ ? this.channelPolicies.get(channelId.toString('hex'))
2108
+ : undefined;
2109
+ return {
2110
+ feeBaseMsat: override?.feeBaseMsat ?? this.forwardingFeeBaseMsat,
2111
+ feeProportionalMillionths: override?.feeProportionalMillionths ?? this.forwardingFeePropMillionths,
2112
+ cltvExpiryDelta: override?.cltvExpiryDelta ?? this.forwardingCltvDelta
2113
+ };
2114
+ }
2115
+ validateChannelPolicyFields(policy) {
2116
+ if (policy.feeBaseMsat === undefined &&
2117
+ policy.feeProportionalMillionths === undefined &&
2118
+ policy.cltvExpiryDelta === undefined &&
2119
+ policy.htlcMinimumMsat === undefined &&
2120
+ policy.htlcMaximumMsat === undefined) {
2121
+ throw new Error('policy must set at least one field');
2122
+ }
2123
+ if (policy.feeBaseMsat !== undefined) {
2124
+ if (!Number.isInteger(policy.feeBaseMsat) ||
2125
+ policy.feeBaseMsat < 0 ||
2126
+ policy.feeBaseMsat > 0xffffffff) {
2127
+ throw new Error(`feeBaseMsat must be an integer in [0, 4294967295], got ${policy.feeBaseMsat}`);
2128
+ }
2129
+ }
2130
+ if (policy.feeProportionalMillionths !== undefined) {
2131
+ if (!Number.isInteger(policy.feeProportionalMillionths) ||
2132
+ policy.feeProportionalMillionths < 0 ||
2133
+ policy.feeProportionalMillionths > 0xffffffff) {
2134
+ throw new Error(`feeProportionalMillionths must be an integer in [0, 4294967295], got ${policy.feeProportionalMillionths}`);
2135
+ }
2136
+ }
2137
+ if (policy.cltvExpiryDelta !== undefined) {
2138
+ if (!Number.isInteger(policy.cltvExpiryDelta) ||
2139
+ policy.cltvExpiryDelta < 1 ||
2140
+ policy.cltvExpiryDelta > 0xffff) {
2141
+ throw new Error(`cltvExpiryDelta must be an integer in [1, 65535] (>= 18 recommended), got ${policy.cltvExpiryDelta}`);
2142
+ }
2143
+ }
2144
+ if (policy.htlcMinimumMsat !== undefined &&
2145
+ (typeof policy.htlcMinimumMsat !== 'bigint' ||
2146
+ policy.htlcMinimumMsat < 0n)) {
2147
+ throw new Error(`htlcMinimumMsat must be a non-negative bigint, got ${policy.htlcMinimumMsat}`);
2148
+ }
2149
+ if (policy.htlcMaximumMsat !== undefined &&
2150
+ (typeof policy.htlcMaximumMsat !== 'bigint' ||
2151
+ policy.htlcMaximumMsat < 0n)) {
2152
+ throw new Error(`htlcMaximumMsat must be a non-negative bigint, got ${policy.htlcMaximumMsat}`);
2153
+ }
2154
+ if (policy.htlcMinimumMsat !== undefined &&
2155
+ policy.htlcMaximumMsat !== undefined &&
2156
+ policy.htlcMinimumMsat > policy.htlcMaximumMsat) {
2157
+ throw new Error(`htlcMinimumMsat (${policy.htlcMinimumMsat}) exceeds htlcMaximumMsat (${policy.htlcMaximumMsat})`);
2158
+ }
2159
+ }
2160
+ static serializeChannelPolicy(policy) {
2161
+ const out = {};
2162
+ if (policy.feeBaseMsat !== undefined)
2163
+ out.feeBaseMsat = policy.feeBaseMsat;
2164
+ if (policy.feeProportionalMillionths !== undefined)
2165
+ out.feeProportionalMillionths = policy.feeProportionalMillionths;
2166
+ if (policy.cltvExpiryDelta !== undefined)
2167
+ out.cltvExpiryDelta = policy.cltvExpiryDelta;
2168
+ if (policy.htlcMinimumMsat !== undefined)
2169
+ out.htlcMinimumMsat = policy.htlcMinimumMsat.toString();
2170
+ if (policy.htlcMaximumMsat !== undefined)
2171
+ out.htlcMaximumMsat = policy.htlcMaximumMsat.toString();
2172
+ return out;
2173
+ }
2174
+ regenerateChannelUpdateForPolicy(channelId) {
2175
+ const hex = channelId.toString('hex');
2176
+ const gossip = this._ownChannelGossip.get(hex);
2177
+ if (gossip) {
2178
+ let timestamp = Math.floor(Date.now() / 1000);
2179
+ try {
2180
+ timestamp = Math.max(timestamp, (0, messages_1.decodeChannelUpdateMessage)(gossip.update).timestamp + 1);
2181
+ }
2182
+ catch {
2183
+ }
2184
+ const refreshed = this.refreshChannelUpdate(gossip.update, timestamp, channelId);
2185
+ if (!refreshed)
2186
+ return;
2187
+ this._ownChannelGossip.set(hex, {
2188
+ announcement: gossip.announcement,
2189
+ update: refreshed
2190
+ });
2191
+ try {
2192
+ this.graph.applyChannelUpdate((0, messages_1.decodeChannelUpdateMessage)(refreshed));
2193
+ }
2194
+ catch {
2195
+ }
2196
+ this.broadcastOwnGossip();
2197
+ return;
2198
+ }
2199
+ const payload = this.buildDirectChannelUpdate(channelId);
2200
+ if (!payload)
2201
+ return;
2202
+ const peer = this.channelManager.getPeerForChannel(channelId);
2203
+ if (!peer)
2204
+ return;
2205
+ if (this.peerManager) {
2206
+ try {
2207
+ this.peerManager.sendToPeer(peer, types_5.MessageType.CHANNEL_UPDATE, payload);
2208
+ }
2209
+ catch {
2210
+ }
2211
+ }
2212
+ else {
2213
+ this.emit('message:outbound', peer, types_5.MessageType.CHANNEL_UPDATE, payload);
2214
+ }
2215
+ }
2216
+ buildDirectChannelUpdate(channelId) {
2217
+ const channel = this.channelManager.getChannel(channelId);
2218
+ if (!channel)
2219
+ return null;
2220
+ const state = channel.getFullState();
2221
+ const scid = state.shortChannelId ?? state.scidAlias;
2222
+ if (!scid)
2223
+ return null;
2224
+ const peerHex = this.channelManager.getPeerForChannel(channelId);
2225
+ if (!peerHex)
2226
+ return null;
2227
+ const policy = this.getChannelPolicy(channelId);
2228
+ if (!policy)
2229
+ return null;
2230
+ try {
2231
+ const { encodeChannelUpdateMessage } = require('../gossip/messages');
2232
+ const ourNodeId = (0, ecdh_1.getPublicKey)(this.nodePrivkey);
2233
+ const peerNodeId = Buffer.from(peerHex, 'hex');
2234
+ const capacityMsat = state.fundingSatoshis * 1000n;
2235
+ const htlcMaxMsat = policy.htlcMaximumMsat > capacityMsat
2236
+ ? capacityMsat
2237
+ : policy.htlcMaximumMsat;
2238
+ const payload = encodeChannelUpdateMessage({
2239
+ signature: Buffer.alloc(64),
2240
+ chainHash: this.acceptableChainHashes[0] ?? types_1.BITCOIN_CHAIN_HASH,
2241
+ shortChannelId: scid,
2242
+ timestamp: Math.floor(Date.now() / 1000),
2243
+ messageFlags: 0x01,
2244
+ channelFlags: Buffer.compare(ourNodeId, peerNodeId) < 0 ? 0 : 1,
2245
+ cltvExpiryDelta: policy.cltvExpiryDelta,
2246
+ htlcMinimumMsat: policy.htlcMinimumMsat,
2247
+ feeBaseMsat: policy.feeBaseMsat,
2248
+ feeProportionalMillionths: policy.feeProportionalMillionths,
2249
+ htlcMaximumMsat: htlcMaxMsat
2250
+ });
2251
+ const sig = (0, validation_1.signChannelUpdate)(payload, this.nodePrivkey);
2252
+ sig.copy(payload, 0);
2253
+ return payload;
2254
+ }
2255
+ catch {
2256
+ return null;
2257
+ }
2258
+ }
1447
2259
  forceCloseChannel(channelId, destinationScript) {
1448
2260
  const result = this.channelManager.forceClose(channelId, destinationScript, this.resolveForceCloseFeeRatePerVbyte());
1449
2261
  if (!result.ok) {
@@ -1661,6 +2473,316 @@ class LightningNode extends events_1.EventEmitter {
1661
2473
  });
1662
2474
  return this.liquidityAdvisor.analyze(snapshots);
1663
2475
  }
2476
+ planRebalanceRecommendations(minImbalancePct) {
2477
+ const snapshots = this.listChannels().map((ch) => ({
2478
+ channelId: ch.channelId.toString('hex'),
2479
+ state: ch.state,
2480
+ localBalanceMsat: ch.localBalanceMsat,
2481
+ remoteBalanceMsat: ch.remoteBalanceMsat,
2482
+ capacitySats: Number(ch.fundingSatoshis),
2483
+ peerPubkey: ch.peerPubkey
2484
+ }));
2485
+ const plans = (0, rebalance_planner_1.planRebalances)(snapshots, {
2486
+ minImbalancePct: minImbalancePct ?? this.autoRebalanceConfig.minImbalancePct
2487
+ });
2488
+ const clamped = [];
2489
+ for (const plan of plans) {
2490
+ const fromCh = this.channelManager.getChannel(Buffer.from(plan.fromChannelId, 'hex'));
2491
+ const toCh = this.channelManager.getChannel(Buffer.from(plan.toChannelId, 'hex'));
2492
+ if (!fromCh || !toCh)
2493
+ continue;
2494
+ const outCapMsat = fromCh.getFullState().remoteConfig.maxHtlcValueInFlightMsat;
2495
+ const inCapMsat = toCh.getFullState().localConfig.maxHtlcValueInFlightMsat;
2496
+ const capMsat = outCapMsat < inCapMsat ? outCapMsat : inCapMsat;
2497
+ const maxAmountSats = (capMsat * 99n) / 100n / 1000n;
2498
+ const amountSats = plan.amountSats < maxAmountSats ? plan.amountSats : maxAmountSats;
2499
+ if (amountSats < rebalance_planner_1.MIN_REBALANCE_SATS)
2500
+ continue;
2501
+ clamped.push({ ...plan, amountSats });
2502
+ }
2503
+ return clamped;
2504
+ }
2505
+ async rebalanceChannel(options) {
2506
+ const { fromChannelId, toChannelId, amountSats, maxFeeSats } = options;
2507
+ const cidErr = (0, validation_2.validateBuffer)(fromChannelId, 32, 'fromChannelId') ||
2508
+ (0, validation_2.validateBuffer)(toChannelId, 32, 'toChannelId');
2509
+ if (cidErr)
2510
+ throw new Error(cidErr);
2511
+ if (fromChannelId.equals(toChannelId)) {
2512
+ throw new Error('fromChannelId and toChannelId must differ');
2513
+ }
2514
+ if (amountSats <= 0n)
2515
+ throw new Error('amountSats must be positive');
2516
+ if (maxFeeSats < 0n)
2517
+ throw new Error('maxFeeSats must be non-negative');
2518
+ const fromChannel = this.channelManager.getChannel(fromChannelId);
2519
+ if (!fromChannel || fromChannel.getState() !== types_1.ChannelState.NORMAL) {
2520
+ throw new Error(`from channel not found or not NORMAL: ${fromChannelId.toString('hex')}`);
2521
+ }
2522
+ const toChannel = this.channelManager.getChannel(toChannelId);
2523
+ if (!toChannel || toChannel.getState() !== types_1.ChannelState.NORMAL) {
2524
+ throw new Error(`to channel not found or not NORMAL: ${toChannelId.toString('hex')}`);
2525
+ }
2526
+ const amountMsat = amountSats * 1000n;
2527
+ const maxFeeMsat = maxFeeSats * 1000n;
2528
+ const fromState = fromChannel.getFullState();
2529
+ const toState = toChannel.getFullState();
2530
+ if (fromState.localBalanceMsat < amountMsat) {
2531
+ throw new Error('insufficient local balance on from channel');
2532
+ }
2533
+ if (toState.remoteBalanceMsat < amountMsat) {
2534
+ throw new Error('insufficient inbound capacity on to channel');
2535
+ }
2536
+ if (amountMsat > toState.localConfig.maxHtlcValueInFlightMsat) {
2537
+ throw new Error('amount exceeds our max_htlc_value_in_flight on the to channel');
2538
+ }
2539
+ const fromScid = fromState.shortChannelId ?? fromState.scidAlias;
2540
+ if (!fromScid) {
2541
+ throw new Error('from channel has no SCID or alias yet');
2542
+ }
2543
+ const fromPeerHex = this.channelManager.getPeerForChannel(fromChannelId);
2544
+ if (!fromPeerHex)
2545
+ throw new Error('from channel has no known peer');
2546
+ const toHint = this.buildRoutingHintForChannel(toChannel);
2547
+ if (!toHint) {
2548
+ throw new Error('to channel has no usable routing hint (SCID/alias)');
2549
+ }
2550
+ const excluded = new Set();
2551
+ for (const channel of this.channelManager.listChannels()) {
2552
+ const id = channel.getChannelId();
2553
+ if (id && id.equals(fromChannelId))
2554
+ continue;
2555
+ const st = channel.getFullState();
2556
+ if (st.shortChannelId)
2557
+ excluded.add(st.shortChannelId.toString('hex'));
2558
+ if (st.scidAlias)
2559
+ excluded.add(st.scidAlias.toString('hex'));
2560
+ if (st.remoteScidAlias) {
2561
+ excluded.add(st.remoteScidAlias.toString('hex'));
2562
+ }
2563
+ }
2564
+ const ourNodeId = (0, ecdh_1.getPublicKey)(this.nodePrivkey);
2565
+ const finalCltvExpiry = types_4.DEFAULT_MIN_FINAL_CLTV_EXPIRY;
2566
+ const toPeerFeeMsat = (0, pathfinding_1.calculateFee)(amountMsat, toHint.feeBaseMsat, toHint.feeProportionalMillionths);
2567
+ const subRoute = (0, pathfinding_1.findRoute)(this.graph, ourNodeId, toHint.pubkey, amountMsat + toPeerFeeMsat, finalCltvExpiry + toHint.cltvExpiryDelta, undefined, excluded, this.missionControl, undefined, undefined, undefined, [
2568
+ {
2569
+ shortChannelId: fromScid,
2570
+ peer: Buffer.from(fromPeerHex, 'hex'),
2571
+ outboundMsat: fromState.localBalanceMsat
2572
+ }
2573
+ ]);
2574
+ if (!subRoute) {
2575
+ throw new types_6.LightningPaymentError(types_6.LightningErrorCode.NO_ROUTE, 'No circular route from fromChannel back to toChannel');
2576
+ }
2577
+ if (!subRoute.hops[0].shortChannelId.equals(fromScid)) {
2578
+ throw new types_6.LightningPaymentError(types_6.LightningErrorCode.NO_ROUTE, 'Route does not leave over the requested from channel');
2579
+ }
2580
+ const totalFeeMsat = subRoute.totalAmountMsat - amountMsat;
2581
+ if (totalFeeMsat > maxFeeMsat) {
2582
+ throw new types_6.LightningPaymentError(types_6.LightningErrorCode.FEE_EXCEEDS_MAX, `Rebalance fee ${totalFeeMsat} msat exceeds cap ${maxFeeMsat} msat`);
2583
+ }
2584
+ if (subRoute.totalAmountMsat > fromState.remoteConfig.maxHtlcValueInFlightMsat) {
2585
+ throw new Error('amount plus fees exceeds the peer max_htlc_value_in_flight on the from channel');
2586
+ }
2587
+ const invoice = this.createInvoice({
2588
+ amountMsat,
2589
+ description: 'beignet circular rebalance',
2590
+ minFinalCltvExpiry: finalCltvExpiry
2591
+ });
2592
+ const hops = [
2593
+ ...subRoute.hops,
2594
+ {
2595
+ pubkey: ourNodeId,
2596
+ shortChannelId: toHint.shortChannelId,
2597
+ amountToForwardMsat: amountMsat,
2598
+ outgoingCltvValue: finalCltvExpiry,
2599
+ cltvExpiryDelta: toHint.cltvExpiryDelta,
2600
+ feeBaseMsat: 0,
2601
+ feeProportionalMillionths: 0
2602
+ }
2603
+ ];
2604
+ this.emitStructuredLog('payment', 'rebalance_started', {
2605
+ fromChannelId: fromChannelId.toString('hex'),
2606
+ toChannelId: toChannelId.toString('hex'),
2607
+ amountMsat: amountMsat.toString(),
2608
+ feeMsat: totalFeeMsat.toString(),
2609
+ hops: hops.length
2610
+ });
2611
+ this.sendPaymentToRoute({ hops }, invoice.paymentHash, finalCltvExpiry, invoice.paymentSecret, amountMsat);
2612
+ await this.waitForPayment(invoice.paymentHash, options.timeoutMs ?? 60000);
2613
+ this.emitStructuredLog('payment', 'rebalance_succeeded', {
2614
+ fromChannelId: fromChannelId.toString('hex'),
2615
+ toChannelId: toChannelId.toString('hex'),
2616
+ amountMsat: amountMsat.toString(),
2617
+ feeMsat: totalFeeMsat.toString()
2618
+ });
2619
+ return {
2620
+ paymentHash: invoice.paymentHash,
2621
+ amountMsat,
2622
+ feeMsat: totalFeeMsat,
2623
+ hops: hops.length
2624
+ };
2625
+ }
2626
+ static currentUtcDay() {
2627
+ return new Date().toISOString().slice(0, 10);
2628
+ }
2629
+ loadRebalanceSpentMsat() {
2630
+ const day = LightningNode.currentUtcDay();
2631
+ if (!this.rebalanceBudgetDay || this.rebalanceBudgetDay.day !== day) {
2632
+ let spent = 0n;
2633
+ if (this.storage) {
2634
+ try {
2635
+ const raw = this.storage.loadMetadata(LightningNode.REBALANCE_BUDGET_KEY);
2636
+ if (raw) {
2637
+ const parsed = JSON.parse(raw);
2638
+ if (parsed.day === day && parsed.spentFeeMsat) {
2639
+ spent = BigInt(parsed.spentFeeMsat);
2640
+ }
2641
+ }
2642
+ }
2643
+ catch {
2644
+ }
2645
+ }
2646
+ this.rebalanceBudgetDay = { day, spentFeeMsat: spent };
2647
+ }
2648
+ return this.rebalanceBudgetDay.spentFeeMsat;
2649
+ }
2650
+ recordRebalanceSpend(feeMsat) {
2651
+ const spent = this.loadRebalanceSpentMsat() + feeMsat;
2652
+ this.rebalanceBudgetDay = {
2653
+ day: LightningNode.currentUtcDay(),
2654
+ spentFeeMsat: spent
2655
+ };
2656
+ this.safeStorage(() => this.storage.saveMetadata(LightningNode.REBALANCE_BUDGET_KEY, JSON.stringify({
2657
+ day: this.rebalanceBudgetDay.day,
2658
+ spentFeeMsat: spent.toString()
2659
+ })), 'saveRebalanceBudget');
2660
+ }
2661
+ async executeRebalanceRecommendations(options) {
2662
+ if (this.rebalanceRunInFlight) {
2663
+ throw new Error('a rebalance execution run is already in progress');
2664
+ }
2665
+ this.rebalanceRunInFlight = true;
2666
+ try {
2667
+ const budgetSats = options?.budgetSatsPerDay ??
2668
+ this.autoRebalanceConfig.budgetSatsPerDay ??
2669
+ 1000;
2670
+ if (budgetSats < 0)
2671
+ throw new Error('budgetSatsPerDay must be >= 0');
2672
+ const budgetMsat = BigInt(budgetSats) * 1000n;
2673
+ const plans = this.planRebalanceRecommendations(options?.minImbalancePct);
2674
+ const attempts = [];
2675
+ let feeSpentThisRunMsat = 0n;
2676
+ for (const plan of plans) {
2677
+ const remainingMsat = budgetMsat - this.loadRebalanceSpentMsat();
2678
+ const proportionalCapMsat = (plan.amountSats * 1000n * 5000n) / 1000000n;
2679
+ const perPairCapMsat = proportionalCapMsat > 1000n ? proportionalCapMsat : 1000n;
2680
+ const feeCapMsat = remainingMsat < perPairCapMsat ? remainingMsat : perPairCapMsat;
2681
+ if (feeCapMsat < 1000n) {
2682
+ attempts.push({
2683
+ fromChannelId: plan.fromChannelId,
2684
+ toChannelId: plan.toChannelId,
2685
+ amountSats: plan.amountSats,
2686
+ status: 'SKIPPED_BUDGET'
2687
+ });
2688
+ this.emitStructuredLog('payment', 'rebalance_budget_exhausted', {
2689
+ remainingMsat: remainingMsat.toString(),
2690
+ budgetMsat: budgetMsat.toString()
2691
+ });
2692
+ continue;
2693
+ }
2694
+ try {
2695
+ const result = await this.rebalanceChannel({
2696
+ fromChannelId: Buffer.from(plan.fromChannelId, 'hex'),
2697
+ toChannelId: Buffer.from(plan.toChannelId, 'hex'),
2698
+ amountSats: plan.amountSats,
2699
+ maxFeeSats: feeCapMsat / 1000n
2700
+ });
2701
+ this.recordRebalanceSpend(result.feeMsat);
2702
+ feeSpentThisRunMsat += result.feeMsat;
2703
+ attempts.push({
2704
+ fromChannelId: plan.fromChannelId,
2705
+ toChannelId: plan.toChannelId,
2706
+ amountSats: plan.amountSats,
2707
+ status: 'SUCCEEDED',
2708
+ feeMsat: result.feeMsat
2709
+ });
2710
+ }
2711
+ catch (err) {
2712
+ attempts.push({
2713
+ fromChannelId: plan.fromChannelId,
2714
+ toChannelId: plan.toChannelId,
2715
+ amountSats: plan.amountSats,
2716
+ status: 'FAILED',
2717
+ error: err instanceof Error ? err.message : String(err)
2718
+ });
2719
+ }
2720
+ }
2721
+ const spent = this.loadRebalanceSpentMsat();
2722
+ return {
2723
+ attempts,
2724
+ succeeded: attempts.filter((a) => a.status === 'SUCCEEDED').length,
2725
+ failed: attempts.filter((a) => a.status === 'FAILED').length,
2726
+ skippedBudget: attempts.filter((a) => a.status === 'SKIPPED_BUDGET')
2727
+ .length,
2728
+ feeSpentMsat: feeSpentThisRunMsat,
2729
+ budgetRemainingMsat: budgetMsat > spent ? budgetMsat - spent : 0n
2730
+ };
2731
+ }
2732
+ finally {
2733
+ this.rebalanceRunInFlight = false;
2734
+ }
2735
+ }
2736
+ runFeeTuneOnce(now = Date.now()) {
2737
+ const intervalMs = this.autoTuneFeesConfig.intervalMs ?? 21600000;
2738
+ const floorPpm = this.autoTuneFeesConfig.floorPpm ?? fee_tuner_1.DEFAULT_FEE_TUNE_FLOOR_PPM;
2739
+ const ceilPpm = this.autoTuneFeesConfig.ceilPpm ?? fee_tuner_1.DEFAULT_FEE_TUNE_CEIL_PPM;
2740
+ if (floorPpm < 0 || ceilPpm < floorPpm) {
2741
+ throw new Error('autoTuneFees requires 0 <= floorPpm <= ceilPpm');
2742
+ }
2743
+ const since = now - intervalMs;
2744
+ const inputs = [];
2745
+ for (const channel of this.channelManager.listChannels()) {
2746
+ if (channel.getState() !== types_1.ChannelState.NORMAL)
2747
+ continue;
2748
+ const channelId = channel.getChannelId();
2749
+ if (!channelId)
2750
+ continue;
2751
+ const hex = channelId.toString('hex');
2752
+ const policy = this.getChannelPolicy(channelId);
2753
+ if (!policy)
2754
+ continue;
2755
+ const st = channel.getFullState();
2756
+ const capacityMsat = st.fundingSatoshis * 1000n;
2757
+ if (capacityMsat <= 0n)
2758
+ continue;
2759
+ const forwards = this.listForwards({ since, until: now, channelId: hex });
2760
+ inputs.push({
2761
+ channelId: hex,
2762
+ currentPpm: policy.feeProportionalMillionths,
2763
+ localBalanceFraction: Number(st.localBalanceMsat) / Number(capacityMsat),
2764
+ outboundForwards: forwards.filter((f) => f.outChannelId === hex).length,
2765
+ totalForwards: forwards.length
2766
+ });
2767
+ }
2768
+ const adjustments = (0, fee_tuner_1.computeFeeTuneAdjustments)(inputs, {
2769
+ floorPpm,
2770
+ ceilPpm
2771
+ });
2772
+ for (const adj of adjustments) {
2773
+ this.setChannelPolicy(Buffer.from(adj.channelId, 'hex'), {
2774
+ feeProportionalMillionths: adj.newPpm
2775
+ });
2776
+ this.emitStructuredLog('fee', 'auto_tune_adjusted', {
2777
+ channelId: adj.channelId,
2778
+ oldPpm: adj.oldPpm,
2779
+ newPpm: adj.newPpm,
2780
+ reason: adj.reason,
2781
+ windowMs: intervalMs
2782
+ });
2783
+ }
2784
+ return adjustments;
2785
+ }
1664
2786
  getFeeSnapshot() {
1665
2787
  return this.feeAdvisor.getSnapshot();
1666
2788
  }
@@ -1717,6 +2839,14 @@ class LightningNode extends events_1.EventEmitter {
1717
2839
  info.localReserveMsat = state.remoteConfig.channelReserveSatoshis * 1000n;
1718
2840
  info.remoteReserveMsat = state.localConfig.channelReserveSatoshis * 1000n;
1719
2841
  info.isPrivate = !state.announceChannel;
2842
+ const policy = this.getChannelPolicy(channelId);
2843
+ if (policy) {
2844
+ info.feeBaseMsat = policy.feeBaseMsat;
2845
+ info.feeProportionalMillionths = policy.feeProportionalMillionths;
2846
+ info.cltvExpiryDelta = policy.cltvExpiryDelta;
2847
+ info.htlcMinimumMsat = policy.htlcMinimumMsat;
2848
+ info.htlcMaximumMsat = policy.htlcMaximumMsat;
2849
+ }
1720
2850
  return info;
1721
2851
  }
1722
2852
  registerChannelScid(channelId, scid) {
@@ -1744,9 +2874,13 @@ class LightningNode extends events_1.EventEmitter {
1744
2874
  if (this.alias) {
1745
2875
  Buffer.from(this.alias, 'utf8').copy(aliasBuffer, 0, 0, Math.min(32, Buffer.byteLength(this.alias, 'utf8')));
1746
2876
  }
2877
+ const announcedFeatures = flags_1.FeatureFlags.empty();
2878
+ if (this.largeChannels) {
2879
+ announcedFeatures.setOptional(flags_1.Feature.LARGE_CHANNELS);
2880
+ }
1747
2881
  const payload = encodeNodeAnnouncementMessage({
1748
2882
  signature: Buffer.alloc(64),
1749
- features: Buffer.alloc(0),
2883
+ features: announcedFeatures.toBuffer(),
1750
2884
  timestamp,
1751
2885
  nodeId,
1752
2886
  rgbColor: Buffer.from([0, 0, 0]),
@@ -1761,11 +2895,24 @@ class LightningNode extends events_1.EventEmitter {
1761
2895
  return null;
1762
2896
  }
1763
2897
  }
1764
- refreshChannelUpdate(cachedUpdate, timestamp) {
2898
+ refreshChannelUpdate(cachedUpdate, timestamp, channelId) {
1765
2899
  try {
1766
2900
  const { encodeChannelUpdateMessage } = require('../gossip/messages');
1767
2901
  const msg = (0, messages_1.decodeChannelUpdateMessage)(cachedUpdate);
1768
2902
  msg.timestamp = timestamp;
2903
+ const policy = channelId ? this.getChannelPolicy(channelId) : null;
2904
+ if (policy && channelId) {
2905
+ msg.cltvExpiryDelta = policy.cltvExpiryDelta;
2906
+ msg.feeBaseMsat = policy.feeBaseMsat;
2907
+ msg.feeProportionalMillionths = policy.feeProportionalMillionths;
2908
+ msg.htlcMinimumMsat = policy.htlcMinimumMsat;
2909
+ const capacityMsat = this.channelManager.getChannel(channelId).getFullState()
2910
+ .fundingSatoshis * 1000n;
2911
+ msg.htlcMaximumMsat =
2912
+ policy.htlcMaximumMsat > capacityMsat
2913
+ ? capacityMsat
2914
+ : policy.htlcMaximumMsat;
2915
+ }
1769
2916
  const payload = encodeChannelUpdateMessage(msg);
1770
2917
  const sig = (0, validation_1.signChannelUpdate)(payload, this.nodePrivkey);
1771
2918
  sig.copy(payload, 0);
@@ -1780,11 +2927,11 @@ class LightningNode extends events_1.EventEmitter {
1780
2927
  return;
1781
2928
  try {
1782
2929
  for (const { announcement, update } of this._ownChannelGossip.values()) {
1783
- this.peerManager.sendToPeer(pubkey, types_4.MessageType.CHANNEL_ANNOUNCEMENT, announcement);
1784
- this.peerManager.sendToPeer(pubkey, types_4.MessageType.CHANNEL_UPDATE, update);
2930
+ this.peerManager.sendToPeer(pubkey, types_5.MessageType.CHANNEL_ANNOUNCEMENT, announcement);
2931
+ this.peerManager.sendToPeer(pubkey, types_5.MessageType.CHANNEL_UPDATE, update);
1785
2932
  }
1786
2933
  if (this._ownNodeAnnouncement) {
1787
- this.peerManager.sendToPeer(pubkey, types_4.MessageType.NODE_ANNOUNCEMENT, this._ownNodeAnnouncement);
2934
+ this.peerManager.sendToPeer(pubkey, types_5.MessageType.NODE_ANNOUNCEMENT, this._ownNodeAnnouncement);
1788
2935
  }
1789
2936
  }
1790
2937
  catch {
@@ -1807,7 +2954,7 @@ class LightningNode extends events_1.EventEmitter {
1807
2954
  this._ownNodeAnnouncement = refreshed;
1808
2955
  }
1809
2956
  for (const [channelIdHex, gossip] of this._ownChannelGossip) {
1810
- const refreshedUpdate = this.refreshChannelUpdate(gossip.update, now);
2957
+ const refreshedUpdate = this.refreshChannelUpdate(gossip.update, now, Buffer.from(channelIdHex, 'hex'));
1811
2958
  if (refreshedUpdate) {
1812
2959
  this._ownChannelGossip.set(channelIdHex, {
1813
2960
  announcement: gossip.announcement,
@@ -1822,56 +2969,59 @@ class LightningNode extends events_1.EventEmitter {
1822
2969
  }
1823
2970
  getPrivateChannelRoutingHints() {
1824
2971
  const hints = [];
1825
- const channels = this.channelManager.listChannels();
1826
- for (const channel of channels) {
1827
- const state = channel.getFullState();
1828
- const effectiveState = state.preReestablishState ?? channel.getState();
1829
- if (effectiveState !== types_1.ChannelState.NORMAL)
1830
- continue;
1831
- const channelId = channel.getChannelId();
1832
- if (!channelId)
1833
- continue;
1834
- const peerPubkeyHex = this.channelManager.getPeerForChannel(channelId);
1835
- if (!peerPubkeyHex)
1836
- continue;
1837
- const scid = state.shortChannelId || state.scidAlias;
1838
- if (!scid)
1839
- continue;
1840
- const peerPubkey = Buffer.from(peerPubkeyHex, 'hex');
1841
- let feeBaseMsat = this.forwardingFeeBaseMsat;
1842
- let feeProportionalMillionths = this.forwardingFeePropMillionths;
1843
- let cltvExpiryDelta = this.forwardingCltvDelta;
1844
- const directPolicy = state.remoteForwardingPolicy;
1845
- if (directPolicy) {
1846
- feeBaseMsat = directPolicy.feeBaseMsat;
1847
- feeProportionalMillionths = directPolicy.feeProportionalMillionths;
1848
- cltvExpiryDelta = directPolicy.cltvExpiryDelta;
1849
- }
1850
- if (state.shortChannelId) {
1851
- const graphChannel = this.graph.getChannel(state.shortChannelId);
1852
- const peerUpdate = graphChannel?.nodeId1.equals(peerPubkey)
1853
- ? graphChannel.update1
1854
- : graphChannel?.nodeId2.equals(peerPubkey)
1855
- ? graphChannel.update2
1856
- : undefined;
1857
- if (peerUpdate &&
1858
- (!directPolicy || peerUpdate.timestamp >= directPolicy.timestamp)) {
1859
- feeBaseMsat = peerUpdate.feeBaseMsat;
1860
- feeProportionalMillionths = peerUpdate.feeProportionalMillionths;
1861
- cltvExpiryDelta = peerUpdate.cltvExpiryDelta;
1862
- }
1863
- }
1864
- const hop = {
1865
- pubkey: peerPubkey,
1866
- shortChannelId: scid,
1867
- feeBaseMsat,
1868
- feeProportionalMillionths,
1869
- cltvExpiryDelta
1870
- };
1871
- hints.push([hop]);
2972
+ for (const channel of this.channelManager.listChannels()) {
2973
+ const hop = this.buildRoutingHintForChannel(channel);
2974
+ if (hop)
2975
+ hints.push([hop]);
1872
2976
  }
1873
2977
  return hints;
1874
2978
  }
2979
+ buildRoutingHintForChannel(channel) {
2980
+ const state = channel.getFullState();
2981
+ const effectiveState = state.preReestablishState ?? channel.getState();
2982
+ if (effectiveState !== types_1.ChannelState.NORMAL)
2983
+ return null;
2984
+ const channelId = channel.getChannelId();
2985
+ if (!channelId)
2986
+ return null;
2987
+ const peerPubkeyHex = this.channelManager.getPeerForChannel(channelId);
2988
+ if (!peerPubkeyHex)
2989
+ return null;
2990
+ const scid = state.shortChannelId || state.scidAlias;
2991
+ if (!scid)
2992
+ return null;
2993
+ const peerPubkey = Buffer.from(peerPubkeyHex, 'hex');
2994
+ let feeBaseMsat = this.forwardingFeeBaseMsat;
2995
+ let feeProportionalMillionths = this.forwardingFeePropMillionths;
2996
+ let cltvExpiryDelta = this.forwardingCltvDelta;
2997
+ const directPolicy = state.remoteForwardingPolicy;
2998
+ if (directPolicy) {
2999
+ feeBaseMsat = directPolicy.feeBaseMsat;
3000
+ feeProportionalMillionths = directPolicy.feeProportionalMillionths;
3001
+ cltvExpiryDelta = directPolicy.cltvExpiryDelta;
3002
+ }
3003
+ if (state.shortChannelId) {
3004
+ const graphChannel = this.graph.getChannel(state.shortChannelId);
3005
+ const peerUpdate = graphChannel?.nodeId1.equals(peerPubkey)
3006
+ ? graphChannel.update1
3007
+ : graphChannel?.nodeId2.equals(peerPubkey)
3008
+ ? graphChannel.update2
3009
+ : undefined;
3010
+ if (peerUpdate &&
3011
+ (!directPolicy || peerUpdate.timestamp >= directPolicy.timestamp)) {
3012
+ feeBaseMsat = peerUpdate.feeBaseMsat;
3013
+ feeProportionalMillionths = peerUpdate.feeProportionalMillionths;
3014
+ cltvExpiryDelta = peerUpdate.cltvExpiryDelta;
3015
+ }
3016
+ }
3017
+ return {
3018
+ pubkey: peerPubkey,
3019
+ shortChannelId: scid,
3020
+ feeBaseMsat,
3021
+ feeProportionalMillionths,
3022
+ cltvExpiryDelta
3023
+ };
3024
+ }
1875
3025
  findBlindedIntroExtension(peerPubkey) {
1876
3026
  const ourNodeId = (0, ecdh_1.getPublicKey)(this.nodePrivkey);
1877
3027
  for (const edge of this.graph.getNodeChannels(peerPubkey)) {
@@ -2006,16 +3156,16 @@ class LightningNode extends events_1.EventEmitter {
2006
3156
  }
2007
3157
  handleGossipMessage(pubkey, type, payload) {
2008
3158
  switch (type) {
2009
- case types_4.MessageType.CHANNEL_ANNOUNCEMENT:
3159
+ case types_5.MessageType.CHANNEL_ANNOUNCEMENT:
2010
3160
  this.handleChannelAnnouncement(payload);
2011
3161
  break;
2012
- case types_4.MessageType.NODE_ANNOUNCEMENT:
3162
+ case types_5.MessageType.NODE_ANNOUNCEMENT:
2013
3163
  this.handleNodeAnnouncement(payload);
2014
3164
  break;
2015
- case types_4.MessageType.CHANNEL_UPDATE:
3165
+ case types_5.MessageType.CHANNEL_UPDATE:
2016
3166
  this.handleChannelUpdate(payload);
2017
3167
  break;
2018
- case types_4.MessageType.REPLY_CHANNEL_RANGE: {
3168
+ case types_5.MessageType.REPLY_CHANNEL_RANGE: {
2019
3169
  const syncMgr = this.gossipSyncManagers.get(pubkey);
2020
3170
  if (syncMgr) {
2021
3171
  const msg = (0, gossip_queries_1.decodeReplyChannelRangeMessage)(payload);
@@ -2026,7 +3176,7 @@ class LightningNode extends events_1.EventEmitter {
2026
3176
  }
2027
3177
  break;
2028
3178
  }
2029
- case types_4.MessageType.REPLY_SHORT_CHANNEL_IDS_END: {
3179
+ case types_5.MessageType.REPLY_SHORT_CHANNEL_IDS_END: {
2030
3180
  const syncMgr = this.gossipSyncManagers.get(pubkey);
2031
3181
  if (syncMgr) {
2032
3182
  const msg = (0, gossip_queries_1.decodeReplyShortChannelIdsEndMessage)(payload);
@@ -2037,7 +3187,7 @@ class LightningNode extends events_1.EventEmitter {
2037
3187
  }
2038
3188
  break;
2039
3189
  }
2040
- case types_4.MessageType.QUERY_CHANNEL_RANGE: {
3190
+ case types_5.MessageType.QUERY_CHANNEL_RANGE: {
2041
3191
  const syncMgr = this.getOrCreateSyncManager(pubkey);
2042
3192
  const msg = (0, gossip_queries_1.decodeQueryChannelRangeMessage)(payload);
2043
3193
  const responses = syncMgr.handleQueryChannelRange(msg);
@@ -2046,7 +3196,7 @@ class LightningNode extends events_1.EventEmitter {
2046
3196
  }
2047
3197
  break;
2048
3198
  }
2049
- case types_4.MessageType.QUERY_SHORT_CHANNEL_IDS: {
3199
+ case types_5.MessageType.QUERY_SHORT_CHANNEL_IDS: {
2050
3200
  const syncMgr = this.getOrCreateSyncManager(pubkey);
2051
3201
  const msg = (0, gossip_queries_1.decodeQueryShortChannelIdsMessage)(payload);
2052
3202
  const responses = syncMgr.handleQueryShortChannelIds(msg);
@@ -2055,7 +3205,7 @@ class LightningNode extends events_1.EventEmitter {
2055
3205
  }
2056
3206
  break;
2057
3207
  }
2058
- case types_4.MessageType.GOSSIP_TIMESTAMP_FILTER:
3208
+ case types_5.MessageType.GOSSIP_TIMESTAMP_FILTER:
2059
3209
  (0, gossip_queries_1.decodeGossipTimestampFilterMessage)(payload);
2060
3210
  this.sendOwnGossipTo(pubkey);
2061
3211
  break;
@@ -2237,8 +3387,8 @@ class LightningNode extends events_1.EventEmitter {
2237
3387
  descriptionHash: options.descriptionHash,
2238
3388
  paymentHash,
2239
3389
  paymentSecret,
2240
- expiry: options.expiry ?? types_3.DEFAULT_EXPIRY,
2241
- minFinalCltvExpiry: options.minFinalCltvExpiry ?? types_3.DEFAULT_MIN_FINAL_CLTV_EXPIRY,
3390
+ expiry: options.expiry ?? types_4.DEFAULT_EXPIRY,
3391
+ minFinalCltvExpiry: options.minFinalCltvExpiry ?? types_4.DEFAULT_MIN_FINAL_CLTV_EXPIRY,
2242
3392
  privateKey: this.nodePrivkey,
2243
3393
  payeeNodeKey: (0, ecdh_1.getPublicKey)(this.nodePrivkey),
2244
3394
  routingHints: !useBlinded && routingHints.length > 0 ? routingHints : undefined,
@@ -2249,8 +3399,8 @@ class LightningNode extends events_1.EventEmitter {
2249
3399
  paymentHash,
2250
3400
  preimage,
2251
3401
  amountMsat: options.amountMsat || 0n,
2252
- status: types_5.PaymentStatus.PENDING,
2253
- direction: types_5.PaymentDirection.INCOMING,
3402
+ status: types_6.PaymentStatus.PENDING,
3403
+ direction: types_6.PaymentDirection.INCOMING,
2254
3404
  createdAt: Date.now()
2255
3405
  };
2256
3406
  this.payments.set(paymentHash.toString('hex'), payment);
@@ -2265,7 +3415,7 @@ class LightningNode extends events_1.EventEmitter {
2265
3415
  bolt11: invoiceStr,
2266
3416
  amountMsat: options.amountMsat,
2267
3417
  description: options.description,
2268
- expiry: options.expiry ?? types_3.DEFAULT_EXPIRY,
3418
+ expiry: options.expiry ?? types_4.DEFAULT_EXPIRY,
2269
3419
  createdAt: createdAtSecs,
2270
3420
  hold: options.hold
2271
3421
  });
@@ -2276,7 +3426,7 @@ class LightningNode extends events_1.EventEmitter {
2276
3426
  bolt11: invoiceStr,
2277
3427
  amountMsat: options.amountMsat,
2278
3428
  description: options.description,
2279
- expiry: options.expiry ?? types_3.DEFAULT_EXPIRY,
3429
+ expiry: options.expiry ?? types_4.DEFAULT_EXPIRY,
2280
3430
  createdAt: createdAtSecs,
2281
3431
  hold: options.hold
2282
3432
  });
@@ -2311,27 +3461,27 @@ class LightningNode extends events_1.EventEmitter {
2311
3461
  const invoice = (0, decode_1.decode)(invoiceStr);
2312
3462
  const dedupHashHex = invoice.paymentHash.toString('hex');
2313
3463
  const existingPayment = this.payments.get(dedupHashHex);
2314
- if (existingPayment && existingPayment.status === types_5.PaymentStatus.PENDING) {
2315
- throw new types_5.LightningPaymentError(types_5.LightningErrorCode.DUPLICATE_PAYMENT, 'Payment already in flight for this invoice');
3464
+ if (existingPayment && existingPayment.status === types_6.PaymentStatus.PENDING) {
3465
+ throw new types_6.LightningPaymentError(types_6.LightningErrorCode.DUPLICATE_PAYMENT, 'Payment already in flight for this invoice');
2316
3466
  }
2317
3467
  const destination = invoice.payeeNodeKey || invoice.recoveredPubkey;
2318
3468
  if (!destination) {
2319
- throw new types_5.LightningPaymentError(types_5.LightningErrorCode.INVALID_INVOICE, 'Cannot determine payee from invoice');
3469
+ throw new types_6.LightningPaymentError(types_6.LightningErrorCode.INVALID_INVOICE, 'Cannot determine payee from invoice');
2320
3470
  }
2321
3471
  let paymentAmountMsat = invoice.amountMsat;
2322
3472
  if (paymentAmountMsat === undefined) {
2323
3473
  if (amountMsat === undefined) {
2324
- throw new types_5.LightningPaymentError(types_5.LightningErrorCode.MISSING_AMOUNT, 'Invoice has no amount and no amountMsat provided');
3474
+ throw new types_6.LightningPaymentError(types_6.LightningErrorCode.MISSING_AMOUNT, 'Invoice has no amount and no amountMsat provided');
2325
3475
  }
2326
3476
  paymentAmountMsat = amountMsat;
2327
3477
  }
2328
- const expiryTimestamp = invoice.timestamp + (invoice.expiry ?? types_3.DEFAULT_EXPIRY);
3478
+ const expiryTimestamp = invoice.timestamp + (invoice.expiry ?? types_4.DEFAULT_EXPIRY);
2329
3479
  if (Math.floor(Date.now() / 1000) > expiryTimestamp) {
2330
3480
  const payment = {
2331
3481
  paymentHash: invoice.paymentHash,
2332
3482
  amountMsat: paymentAmountMsat,
2333
- status: types_5.PaymentStatus.FAILED,
2334
- direction: types_5.PaymentDirection.OUTGOING,
3483
+ status: types_6.PaymentStatus.FAILED,
3484
+ direction: types_6.PaymentDirection.OUTGOING,
2335
3485
  createdAt: Date.now(),
2336
3486
  completedAt: Date.now()
2337
3487
  };
@@ -2339,16 +3489,16 @@ class LightningNode extends events_1.EventEmitter {
2339
3489
  this.emit('payment:failed', payment);
2340
3490
  return payment;
2341
3491
  }
2342
- const finalCltvExpiry = invoice.minFinalCltvExpiry ?? types_3.DEFAULT_MIN_FINAL_CLTV_EXPIRY;
3492
+ const finalCltvExpiry = invoice.minFinalCltvExpiry ?? types_4.DEFAULT_MIN_FINAL_CLTV_EXPIRY;
2343
3493
  const sourceNodeId = (0, ecdh_1.getPublicKey)(this.nodePrivkey);
2344
3494
  if (invoice.blindedPaths && invoice.blindedPaths.length > 0) {
2345
3495
  const blinded = invoice.blindedPaths[0];
2346
3496
  const blindedRoute = (0, pathfinding_1.findRouteToBlindedPath)(this.graph, sourceNodeId, blinded.path, blinded.payInfo, paymentAmountMsat, finalCltvExpiry, undefined, excludedChannels, this.missionControl, this.getLocalChannelEdges());
2347
3497
  if (!blindedRoute) {
2348
- throw new types_5.LightningPaymentError(types_5.LightningErrorCode.NO_ROUTE, 'No route to blinded path introduction node');
3498
+ throw new types_6.LightningPaymentError(types_6.LightningErrorCode.NO_ROUTE, 'No route to blinded path introduction node');
2349
3499
  }
2350
3500
  if (maxFeeMsat !== undefined && blindedRoute.totalFeeMsat > maxFeeMsat) {
2351
- throw new types_5.LightningPaymentError(types_5.LightningErrorCode.FEE_EXCEEDS_MAX, 'Route fee exceeds maximum');
3501
+ throw new types_6.LightningPaymentError(types_6.LightningErrorCode.FEE_EXCEEDS_MAX, 'Route fee exceeds maximum');
2352
3502
  }
2353
3503
  const bHashHex = invoice.paymentHash.toString('hex');
2354
3504
  if (!this.paymentRetryContexts.has(bHashHex)) {
@@ -2369,16 +3519,16 @@ class LightningNode extends events_1.EventEmitter {
2369
3519
  const multiRoute = (0, pathfinding_1.findMultiPathRoute)(this.graph, sourceNodeId, destination, paymentAmountMsat, finalCltvExpiry, undefined, undefined, this.missionControl, invoice.routingHints, undefined, localChannels);
2370
3520
  if (multiRoute) {
2371
3521
  if (maxFeeMsat !== undefined && multiRoute.totalFeeMsat > maxFeeMsat) {
2372
- throw new types_5.LightningPaymentError(types_5.LightningErrorCode.FEE_EXCEEDS_MAX, 'Route fee exceeds maximum');
3522
+ throw new types_6.LightningPaymentError(types_6.LightningErrorCode.FEE_EXCEEDS_MAX, 'Route fee exceeds maximum');
2373
3523
  }
2374
3524
  return this.sendPaymentMpp(invoiceStr, invoice, multiRoute, finalCltvExpiry);
2375
3525
  }
2376
3526
  }
2377
3527
  if (!route) {
2378
- throw new types_5.LightningPaymentError(types_5.LightningErrorCode.NO_ROUTE, 'No route found to destination');
3528
+ throw new types_6.LightningPaymentError(types_6.LightningErrorCode.NO_ROUTE, 'No route found to destination');
2379
3529
  }
2380
3530
  if (maxFeeMsat !== undefined && route.totalFeeMsat > maxFeeMsat) {
2381
- throw new types_5.LightningPaymentError(types_5.LightningErrorCode.FEE_EXCEEDS_MAX, 'Route fee exceeds maximum');
3531
+ throw new types_6.LightningPaymentError(types_6.LightningErrorCode.FEE_EXCEEDS_MAX, 'Route fee exceeds maximum');
2382
3532
  }
2383
3533
  const hashHex = invoice.paymentHash.toString('hex');
2384
3534
  if (!this.paymentRetryContexts.has(hashHex)) {
@@ -2433,9 +3583,10 @@ class LightningNode extends events_1.EventEmitter {
2433
3583
  const onionPacket = (0, construct_1.constructOnionPacket)(sessionKey, onionHops, paymentHash);
2434
3584
  const onionBuf = (0, construct_1.encodeOnionPacket)(onionPacket);
2435
3585
  const firstHopPubkey = hops[0].pubkey.toString('hex');
2436
- const outChannel = this.findChannelForPeer(firstHopPubkey, hops[0].amountToForwardMsat);
3586
+ const outChannel = this.findLocalChannelByScid(hops[0].shortChannelId, firstHopPubkey) ??
3587
+ this.findChannelForPeer(firstHopPubkey, hops[0].amountToForwardMsat);
2437
3588
  if (!outChannel) {
2438
- throw new types_5.LightningPaymentError(types_5.LightningErrorCode.NO_CHANNEL_TO_HOP, `No channel to first hop ${firstHopPubkey}`);
3589
+ throw new types_6.LightningPaymentError(types_6.LightningErrorCode.NO_CHANNEL_TO_HOP, `No channel to first hop ${firstHopPubkey}`);
2439
3590
  }
2440
3591
  const channelId = outChannel.getChannelId();
2441
3592
  const cltvExpiry = hops[0].outgoingCltvValue + baseHeight;
@@ -2443,8 +3594,8 @@ class LightningNode extends events_1.EventEmitter {
2443
3594
  const payment = {
2444
3595
  paymentHash,
2445
3596
  amountMsat: amount,
2446
- status: types_5.PaymentStatus.PENDING,
2447
- direction: types_5.PaymentDirection.OUTGOING,
3597
+ status: types_6.PaymentStatus.PENDING,
3598
+ direction: types_6.PaymentDirection.OUTGOING,
2448
3599
  route: route,
2449
3600
  sharedSecrets,
2450
3601
  createdAt: Date.now()
@@ -2461,7 +3612,7 @@ class LightningNode extends events_1.EventEmitter {
2461
3612
  }
2462
3613
  const result = this.channelManager.addHtlc(channelId, amount, paymentHash, cltvExpiry, onionBuf);
2463
3614
  if (!result.ok) {
2464
- payment.status = types_5.PaymentStatus.FAILED;
3615
+ payment.status = types_6.PaymentStatus.FAILED;
2465
3616
  payment.completedAt = Date.now();
2466
3617
  this.emit('payment:failed', payment);
2467
3618
  }
@@ -2470,31 +3621,31 @@ class LightningNode extends events_1.EventEmitter {
2470
3621
  sendKeysend(options) {
2471
3622
  const { destination, amountMsat, maxFeeMsat, customRecords: extraRecords, metadata } = options;
2472
3623
  if (!destination || destination.length !== 33) {
2473
- throw new types_5.LightningPaymentError(types_5.LightningErrorCode.INVALID_KEYSEND, 'destination must be a 33-byte compressed public key');
3624
+ throw new types_6.LightningPaymentError(types_6.LightningErrorCode.INVALID_KEYSEND, 'destination must be a 33-byte compressed public key');
2474
3625
  }
2475
3626
  if (amountMsat <= 0n) {
2476
- throw new types_5.LightningPaymentError(types_5.LightningErrorCode.INVALID_KEYSEND, 'amountMsat must be positive');
3627
+ throw new types_6.LightningPaymentError(types_6.LightningErrorCode.INVALID_KEYSEND, 'amountMsat must be positive');
2477
3628
  }
2478
3629
  const preimage = crypto_1.default.randomBytes(32);
2479
3630
  const paymentHash = crypto_1.default.createHash('sha256').update(preimage).digest();
2480
3631
  const hashHex = paymentHash.toString('hex');
2481
3632
  const existingPayment = this.payments.get(hashHex);
2482
- if (existingPayment && existingPayment.status === types_5.PaymentStatus.PENDING) {
2483
- throw new types_5.LightningPaymentError(types_5.LightningErrorCode.DUPLICATE_PAYMENT, 'Payment already in flight');
3633
+ if (existingPayment && existingPayment.status === types_6.PaymentStatus.PENDING) {
3634
+ throw new types_6.LightningPaymentError(types_6.LightningErrorCode.DUPLICATE_PAYMENT, 'Payment already in flight');
2484
3635
  }
2485
- const finalCltvExpiry = types_3.DEFAULT_MIN_FINAL_CLTV_EXPIRY;
3636
+ const finalCltvExpiry = types_4.DEFAULT_MIN_FINAL_CLTV_EXPIRY;
2486
3637
  const sourceNodeId = (0, ecdh_1.getPublicKey)(this.nodePrivkey);
2487
3638
  const route = (0, pathfinding_1.findRoute)(this.graph, sourceNodeId, destination, amountMsat, finalCltvExpiry, undefined, undefined, this.missionControl, undefined, undefined, undefined, this.getLocalChannelEdges());
2488
3639
  if (!route) {
2489
- throw new types_5.LightningPaymentError(types_5.LightningErrorCode.NO_ROUTE, 'No route found to destination');
3640
+ throw new types_6.LightningPaymentError(types_6.LightningErrorCode.NO_ROUTE, 'No route found to destination');
2490
3641
  }
2491
3642
  if (maxFeeMsat !== undefined && route.totalFeeMsat > maxFeeMsat) {
2492
- throw new types_5.LightningPaymentError(types_5.LightningErrorCode.FEE_EXCEEDS_MAX, 'Route fee exceeds maximum');
3643
+ throw new types_6.LightningPaymentError(types_6.LightningErrorCode.FEE_EXCEEDS_MAX, 'Route fee exceeds maximum');
2493
3644
  }
2494
3645
  const hops = route.hops;
2495
3646
  const baseHeight = this.currentBlockHeight;
2496
3647
  const keysendRecords = new Map();
2497
- keysendRecords.set(types_2.KEYSEND_TLV_TYPE, preimage);
3648
+ keysendRecords.set(types_3.KEYSEND_TLV_TYPE, preimage);
2498
3649
  if (extraRecords) {
2499
3650
  for (const [type, value] of extraRecords) {
2500
3651
  keysendRecords.set(type, value);
@@ -2523,7 +3674,7 @@ class LightningNode extends events_1.EventEmitter {
2523
3674
  const firstHopPubkey = hops[0].pubkey.toString('hex');
2524
3675
  const outChannel = this.findChannelForPeer(firstHopPubkey, hops[0].amountToForwardMsat);
2525
3676
  if (!outChannel) {
2526
- throw new types_5.LightningPaymentError(types_5.LightningErrorCode.NO_CHANNEL_TO_HOP, `No channel to first hop ${firstHopPubkey}`);
3677
+ throw new types_6.LightningPaymentError(types_6.LightningErrorCode.NO_CHANNEL_TO_HOP, `No channel to first hop ${firstHopPubkey}`);
2527
3678
  }
2528
3679
  const channelId = outChannel.getChannelId();
2529
3680
  const cltvExpiry = hops[0].outgoingCltvValue + baseHeight;
@@ -2532,8 +3683,8 @@ class LightningNode extends events_1.EventEmitter {
2532
3683
  paymentHash,
2533
3684
  preimage,
2534
3685
  amountMsat: amount,
2535
- status: types_5.PaymentStatus.PENDING,
2536
- direction: types_5.PaymentDirection.OUTGOING,
3686
+ status: types_6.PaymentStatus.PENDING,
3687
+ direction: types_6.PaymentDirection.OUTGOING,
2537
3688
  route: route,
2538
3689
  sharedSecrets,
2539
3690
  createdAt: Date.now(),
@@ -2551,7 +3702,7 @@ class LightningNode extends events_1.EventEmitter {
2551
3702
  }
2552
3703
  const result = this.channelManager.addHtlc(channelId, amount, paymentHash, cltvExpiry, onionBuf);
2553
3704
  if (!result.ok) {
2554
- payment.status = types_5.PaymentStatus.FAILED;
3705
+ payment.status = types_6.PaymentStatus.FAILED;
2555
3706
  payment.completedAt = Date.now();
2556
3707
  this.emit('payment:failed', payment);
2557
3708
  }
@@ -2564,8 +3715,8 @@ class LightningNode extends events_1.EventEmitter {
2564
3715
  const payment = {
2565
3716
  paymentHash,
2566
3717
  amountMsat: totalMsat,
2567
- status: types_5.PaymentStatus.PENDING,
2568
- direction: types_5.PaymentDirection.OUTGOING,
3718
+ status: types_6.PaymentStatus.PENDING,
3719
+ direction: types_6.PaymentDirection.OUTGOING,
2569
3720
  createdAt: Date.now()
2570
3721
  };
2571
3722
  this.payments.set(hashHex, payment);
@@ -2632,16 +3783,16 @@ class LightningNode extends events_1.EventEmitter {
2632
3783
  channelId,
2633
3784
  htlcId,
2634
3785
  amountMsat: amount,
2635
- status: types_5.PaymentStatus.PENDING
3786
+ status: types_6.PaymentStatus.PENDING
2636
3787
  });
2637
3788
  const result = this.channelManager.addHtlc(channelId, amount, paymentHash, cltvExpiry, onionBuf);
2638
3789
  if (!result.ok) {
2639
3790
  for (const dispatched of mppState.parts) {
2640
- if (dispatched.status === types_5.PaymentStatus.PENDING) {
2641
- this.channelManager.failHtlc(dispatched.channelId, dispatched.htlcId, (0, failures_1.createFailureMessage)(Buffer.alloc(32), types_2.TEMPORARY_CHANNEL_FAILURE));
3791
+ if (dispatched.status === types_6.PaymentStatus.PENDING) {
3792
+ this.channelManager.failHtlc(dispatched.channelId, dispatched.htlcId, (0, failures_1.createFailureMessage)(Buffer.alloc(32), types_3.TEMPORARY_CHANNEL_FAILURE));
2642
3793
  }
2643
3794
  }
2644
- payment.status = types_5.PaymentStatus.FAILED;
3795
+ payment.status = types_6.PaymentStatus.FAILED;
2645
3796
  payment.completedAt = Date.now();
2646
3797
  this.outboundMppPayments.delete(hashHex);
2647
3798
  this.emit('payment:failed', payment);
@@ -2661,12 +3812,12 @@ class LightningNode extends events_1.EventEmitter {
2661
3812
  if (!channel)
2662
3813
  return;
2663
3814
  if (this.getTotalInFlightHtlcCount() > this.maxTotalInFlightHtlcs) {
2664
- this.channelManager.failHtlc(channelId, htlcId, (0, failures_1.createFailureMessage)(Buffer.alloc(32), types_2.TEMPORARY_NODE_FAILURE));
3815
+ this.channelManager.failHtlc(channelId, htlcId, (0, failures_1.createFailureMessage)(Buffer.alloc(32), types_3.TEMPORARY_NODE_FAILURE));
2665
3816
  return;
2666
3817
  }
2667
3818
  const peerPubkey = this.channelManager.getPeerForChannel(channelId);
2668
3819
  if (peerPubkey && !this.rateLimiter.tryConsume(peerPubkey)) {
2669
- this.channelManager.failHtlc(channelId, htlcId, (0, failures_1.createFailureMessage)(Buffer.alloc(32), types_2.TEMPORARY_NODE_FAILURE));
3820
+ this.channelManager.failHtlc(channelId, htlcId, (0, failures_1.createFailureMessage)(Buffer.alloc(32), types_3.TEMPORARY_NODE_FAILURE));
2670
3821
  return;
2671
3822
  }
2672
3823
  const htlcEntry = channel.getFullState().htlcs.get(`received-${htlcId}`);
@@ -2689,7 +3840,7 @@ class LightningNode extends events_1.EventEmitter {
2689
3840
  message: `Onion processing failed for HTLC ${htlcId} on channel ${channelId.toString('hex')}: ${err.message || 'unknown'}`,
2690
3841
  timestamp: Date.now()
2691
3842
  });
2692
- this.channelManager.failHtlc(channelId, htlcId, (0, failures_1.createFailureMessage)(Buffer.alloc(32), types_2.INVALID_ONION_HMAC));
3843
+ this.channelManager.failHtlc(channelId, htlcId, (0, failures_1.createFailureMessage)(Buffer.alloc(32), types_3.INVALID_ONION_HMAC));
2693
3844
  return;
2694
3845
  }
2695
3846
  const htlcSecretKey = `${channelId.toString('hex')}:${htlcId}`;
@@ -2712,11 +3863,11 @@ class LightningNode extends events_1.EventEmitter {
2712
3863
  const hashHex = paymentHash.toString('hex');
2713
3864
  const htlcSecretKey = `${channelId.toString('hex')}:${htlcId}`;
2714
3865
  const sharedSecret = this.receivedHtlcSharedSecrets.get(htlcSecretKey);
2715
- const keysendPreimage = hopPayload?.customRecords?.get(types_2.KEYSEND_TLV_TYPE);
3866
+ const keysendPreimage = hopPayload?.customRecords?.get(types_3.KEYSEND_TLV_TYPE);
2716
3867
  if (keysendPreimage) {
2717
3868
  if (keysendPreimage.length !== 32) {
2718
3869
  const reason = sharedSecret
2719
- ? (0, failures_1.createFailureMessage)(sharedSecret, types_2.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS)
3870
+ ? (0, failures_1.createFailureMessage)(sharedSecret, types_3.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS)
2720
3871
  : Buffer.alloc(290);
2721
3872
  this.cleanupHtlcSharedSecret(htlcSecretKey);
2722
3873
  this.channelManager.failHtlc(channelId, htlcId, reason);
@@ -2728,7 +3879,7 @@ class LightningNode extends events_1.EventEmitter {
2728
3879
  .digest();
2729
3880
  if (!expectedHash.equals(paymentHash)) {
2730
3881
  const reason = sharedSecret
2731
- ? (0, failures_1.createFailureMessage)(sharedSecret, types_2.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS)
3882
+ ? (0, failures_1.createFailureMessage)(sharedSecret, types_3.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS)
2732
3883
  : Buffer.alloc(290);
2733
3884
  this.cleanupHtlcSharedSecret(htlcSecretKey);
2734
3885
  this.channelManager.failHtlc(channelId, htlcId, reason);
@@ -2739,8 +3890,8 @@ class LightningNode extends events_1.EventEmitter {
2739
3890
  paymentHash,
2740
3891
  preimage: keysendPreimage,
2741
3892
  amountMsat,
2742
- status: types_5.PaymentStatus.PENDING,
2743
- direction: types_5.PaymentDirection.INCOMING,
3893
+ status: types_6.PaymentStatus.PENDING,
3894
+ direction: types_6.PaymentDirection.INCOMING,
2744
3895
  createdAt: Date.now(),
2745
3896
  metadata: { _keysend: 'true' }
2746
3897
  };
@@ -2765,7 +3916,7 @@ class LightningNode extends events_1.EventEmitter {
2765
3916
  paymentHash: hashHex
2766
3917
  });
2767
3918
  const reason = sharedSecret
2768
- ? (0, failures_1.createFailureMessage)(sharedSecret, types_2.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS)
3919
+ ? (0, failures_1.createFailureMessage)(sharedSecret, types_3.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS)
2769
3920
  : Buffer.alloc(290);
2770
3921
  this.cleanupHtlcSharedSecret(htlcSecretKey);
2771
3922
  this.channelManager.failHtlc(channelId, htlcId, reason);
@@ -2779,7 +3930,7 @@ class LightningNode extends events_1.EventEmitter {
2779
3930
  paymentHash: hashHex
2780
3931
  });
2781
3932
  const reason = sharedSecret
2782
- ? (0, failures_1.createFailureMessage)(sharedSecret, types_2.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS)
3933
+ ? (0, failures_1.createFailureMessage)(sharedSecret, types_3.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS)
2783
3934
  : Buffer.alloc(290);
2784
3935
  this.cleanupHtlcSharedSecret(htlcSecretKey);
2785
3936
  this.channelManager.failHtlc(channelId, htlcId, reason);
@@ -2795,7 +3946,7 @@ class LightningNode extends events_1.EventEmitter {
2795
3946
  onionCltv: hopPayload.outgoingCltvValue
2796
3947
  });
2797
3948
  const reason = sharedSecret
2798
- ? (0, failures_1.createFailureMessage)(sharedSecret, types_2.FINAL_INCORRECT_CLTV_EXPIRY)
3949
+ ? (0, failures_1.createFailureMessage)(sharedSecret, types_3.FINAL_INCORRECT_CLTV_EXPIRY)
2799
3950
  : Buffer.alloc(290);
2800
3951
  this.cleanupHtlcSharedSecret(htlcSecretKey);
2801
3952
  this.channelManager.failHtlc(channelId, htlcId, reason);
@@ -2803,14 +3954,14 @@ class LightningNode extends events_1.EventEmitter {
2803
3954
  }
2804
3955
  if (this.currentBlockHeight > 0 &&
2805
3956
  incomingCltvExpiry <
2806
- this.currentBlockHeight + types_3.DEFAULT_MIN_FINAL_CLTV_EXPIRY) {
3957
+ this.currentBlockHeight + types_4.DEFAULT_MIN_FINAL_CLTV_EXPIRY) {
2807
3958
  this.emitStructuredLog('htlc', 'final_expiry_too_soon', {
2808
3959
  paymentHash: hashHex,
2809
3960
  htlcCltv: incomingCltvExpiry,
2810
3961
  height: this.currentBlockHeight
2811
3962
  });
2812
3963
  const reason = sharedSecret
2813
- ? (0, failures_1.createFailureMessage)(sharedSecret, types_2.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS)
3964
+ ? (0, failures_1.createFailureMessage)(sharedSecret, types_3.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS)
2814
3965
  : Buffer.alloc(290);
2815
3966
  this.cleanupHtlcSharedSecret(htlcSecretKey);
2816
3967
  this.channelManager.failHtlc(channelId, htlcId, reason);
@@ -2831,7 +3982,7 @@ class LightningNode extends events_1.EventEmitter {
2831
3982
  invoiced: finalInvoice.amountMsat.toString()
2832
3983
  });
2833
3984
  const reason = sharedSecret
2834
- ? (0, failures_1.createFailureMessage)(sharedSecret, types_2.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS)
3985
+ ? (0, failures_1.createFailureMessage)(sharedSecret, types_3.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS)
2835
3986
  : Buffer.alloc(290);
2836
3987
  this.cleanupHtlcSharedSecret(htlcSecretKey);
2837
3988
  this.channelManager.failHtlc(channelId, htlcId, reason);
@@ -2891,11 +4042,12 @@ class LightningNode extends events_1.EventEmitter {
2891
4042
  this.persistHeldHtlcs();
2892
4043
  const payment = this.payments.get(hashHex);
2893
4044
  if (payment) {
2894
- payment.status = types_5.PaymentStatus.COMPLETED;
4045
+ payment.status = types_6.PaymentStatus.COMPLETED;
2895
4046
  payment.preimage = pre;
2896
4047
  payment.completedAt = Date.now();
2897
4048
  this.safeStorage(() => this.persistPayment(paymentHash), 'persistPayment');
2898
4049
  this.emit('payment:received', payment);
4050
+ this.emitInvoiceSettled(paymentHash, payment);
2899
4051
  }
2900
4052
  this.emitStructuredLog('payment', 'received', {
2901
4053
  paymentHash: hashHex,
@@ -2903,7 +4055,21 @@ class LightningNode extends events_1.EventEmitter {
2903
4055
  });
2904
4056
  return true;
2905
4057
  }
2906
- cancelHeldHtlc(paymentHash, failureCode = types_2.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS) {
4058
+ emitInvoiceSettled(paymentHash, payment) {
4059
+ const invoice = this.invoices.get(paymentHash.toString('hex'));
4060
+ if (!invoice)
4061
+ return;
4062
+ this.emit('invoice:settled', {
4063
+ paymentHash,
4064
+ bolt11: invoice.bolt11,
4065
+ amountMsat: payment.amountMsat
4066
+ });
4067
+ this.emitStructuredLog('payment', 'invoice_settled', {
4068
+ paymentHash: paymentHash.toString('hex'),
4069
+ amountMsat: payment.amountMsat.toString()
4070
+ });
4071
+ }
4072
+ cancelHeldHtlc(paymentHash, failureCode = types_3.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS) {
2907
4073
  const hashHex = paymentHash.toString('hex');
2908
4074
  const held = this.heldHtlcs.get(hashHex);
2909
4075
  if (!held || held.length === 0)
@@ -2920,9 +4086,80 @@ class LightningNode extends events_1.EventEmitter {
2920
4086
  this.heldHtlcs.delete(hashHex);
2921
4087
  this.heldInvoiceHashes.delete(hashHex);
2922
4088
  this.persistHeldHtlcs();
4089
+ this.markHoldInvoiceCancelled(hashHex);
2923
4090
  this.emitStructuredLog('htlc', 'held_cancelled', { paymentHash: hashHex });
2924
4091
  return true;
2925
4092
  }
4093
+ markHoldInvoiceCancelled(hashHex) {
4094
+ this.preimages.delete(hashHex);
4095
+ this.paymentSecrets.delete(hashHex);
4096
+ this.safeStorage(() => this.storage.deletePaymentSecret(hashHex), 'deletePaymentSecret');
4097
+ const invoice = this.invoices.get(hashHex);
4098
+ if (invoice && !invoice.cancelledAt) {
4099
+ invoice.cancelledAt = Date.now();
4100
+ this.safeStorage(() => this.storage.saveInvoice(hashHex, invoice), 'saveInvoice');
4101
+ }
4102
+ const payment = this.payments.get(hashHex);
4103
+ if (payment &&
4104
+ payment.direction === types_6.PaymentDirection.INCOMING &&
4105
+ payment.status !== types_6.PaymentStatus.COMPLETED) {
4106
+ payment.status = types_6.PaymentStatus.FAILED;
4107
+ this.safeStorage(() => this.persistPayment(Buffer.from(hashHex, 'hex')), 'persistPayment');
4108
+ }
4109
+ }
4110
+ cancelHoldInvoice(paymentHash) {
4111
+ const hashHex = paymentHash.toString('hex');
4112
+ const held = this.heldHtlcs.get(hashHex);
4113
+ if (held && held.length > 0) {
4114
+ const count = held.length;
4115
+ this.cancelHeldHtlc(paymentHash);
4116
+ return { htlcsFailed: count };
4117
+ }
4118
+ if (!this.heldInvoiceHashes.has(hashHex))
4119
+ return null;
4120
+ this.heldInvoiceHashes.delete(hashHex);
4121
+ this.markHoldInvoiceCancelled(hashHex);
4122
+ this.emitStructuredLog('htlc', 'held_cancelled', { paymentHash: hashHex });
4123
+ return { htlcsFailed: 0 };
4124
+ }
4125
+ listHoldInvoices() {
4126
+ const out = [];
4127
+ for (const [hashHex, invoice] of this.invoices) {
4128
+ if (!invoice.hold)
4129
+ continue;
4130
+ const held = this.heldHtlcs.get(hashHex) ?? [];
4131
+ let heldAmountMsat = 0n;
4132
+ for (const h of held)
4133
+ heldAmountMsat += h.amountMsat;
4134
+ const payment = this.payments.get(hashHex);
4135
+ let state;
4136
+ if (held.length > 0) {
4137
+ state = 'ACCEPTED';
4138
+ }
4139
+ else if (payment?.status === types_6.PaymentStatus.COMPLETED &&
4140
+ payment.direction === types_6.PaymentDirection.INCOMING) {
4141
+ state = 'SETTLED';
4142
+ }
4143
+ else if (invoice.cancelledAt || !this.heldInvoiceHashes.has(hashHex)) {
4144
+ state = 'CANCELLED';
4145
+ }
4146
+ else {
4147
+ state = 'OPEN';
4148
+ }
4149
+ out.push({
4150
+ paymentHash: hashHex,
4151
+ bolt11: invoice.bolt11,
4152
+ amountMsat: invoice.amountMsat,
4153
+ description: invoice.description,
4154
+ expiry: invoice.expiry,
4155
+ createdAt: invoice.createdAt,
4156
+ state,
4157
+ heldAmountMsat,
4158
+ htlcCount: held.length
4159
+ });
4160
+ }
4161
+ return out;
4162
+ }
2926
4163
  scanExpiringHeldHtlcs(height) {
2927
4164
  if (height <= 0)
2928
4165
  return;
@@ -3006,7 +4243,7 @@ class LightningNode extends events_1.EventEmitter {
3006
4243
  channelId,
3007
4244
  htlcId,
3008
4245
  amountMsat,
3009
- status: types_5.PaymentStatus.PENDING
4246
+ status: types_6.PaymentStatus.PENDING
3010
4247
  };
3011
4248
  pending.receivedParts.push(part);
3012
4249
  let totalReceived = 0n;
@@ -3016,16 +4253,17 @@ class LightningNode extends events_1.EventEmitter {
3016
4253
  if (totalReceived >= pending.totalMsat) {
3017
4254
  this.channelManager.recordPreimage(paymentHash, preimage);
3018
4255
  for (const p of pending.receivedParts) {
3019
- p.status = types_5.PaymentStatus.COMPLETED;
4256
+ p.status = types_6.PaymentStatus.COMPLETED;
3020
4257
  this.channelManager.fulfillHtlc(p.channelId, p.htlcId, preimage);
3021
4258
  }
3022
4259
  this.pendingMppPayments.delete(hashHex);
3023
4260
  const payment = this.payments.get(hashHex);
3024
4261
  if (payment) {
3025
- payment.status = types_5.PaymentStatus.COMPLETED;
4262
+ payment.status = types_6.PaymentStatus.COMPLETED;
3026
4263
  payment.completedAt = Date.now();
3027
4264
  this.persistPayment(paymentHash);
3028
4265
  this.emit('payment:received', payment);
4266
+ this.emitInvoiceSettled(paymentHash, payment);
3029
4267
  }
3030
4268
  }
3031
4269
  }
@@ -3034,12 +4272,12 @@ class LightningNode extends events_1.EventEmitter {
3034
4272
  for (const [hashHex, pending] of this.pendingMppPayments) {
3035
4273
  if (now - pending.createdAt > this.mppTimeoutMs) {
3036
4274
  for (const part of pending.receivedParts) {
3037
- if (part.status === types_5.PaymentStatus.PENDING) {
3038
- part.status = types_5.PaymentStatus.FAILED;
4275
+ if (part.status === types_6.PaymentStatus.PENDING) {
4276
+ part.status = types_6.PaymentStatus.FAILED;
3039
4277
  const htlcSecretKey = `${part.channelId.toString('hex')}:${part.htlcId}`;
3040
4278
  const sharedSecret = this.receivedHtlcSharedSecrets.get(htlcSecretKey);
3041
4279
  const reason = sharedSecret
3042
- ? (0, failures_1.createFailureMessage)(sharedSecret, types_2.MPP_TIMEOUT)
4280
+ ? (0, failures_1.createFailureMessage)(sharedSecret, types_3.MPP_TIMEOUT)
3043
4281
  : Buffer.alloc(290);
3044
4282
  this.cleanupHtlcSharedSecret(htlcSecretKey);
3045
4283
  this.channelManager.failHtlc(part.channelId, part.htlcId, reason);
@@ -3056,7 +4294,7 @@ class LightningNode extends events_1.EventEmitter {
3056
4294
  this.paymentSecrets.delete(hashHex);
3057
4295
  const payment = this.payments.get(hashHex);
3058
4296
  if (payment) {
3059
- payment.status = types_5.PaymentStatus.COMPLETED;
4297
+ payment.status = types_6.PaymentStatus.COMPLETED;
3060
4298
  payment.completedAt = Date.now();
3061
4299
  }
3062
4300
  if (this.storage) {
@@ -3075,6 +4313,7 @@ class LightningNode extends events_1.EventEmitter {
3075
4313
  }
3076
4314
  if (payment) {
3077
4315
  this.emit('payment:received', payment);
4316
+ this.emitInvoiceSettled(paymentHash, payment);
3078
4317
  this.emitStructuredLog('payment', 'received', {
3079
4318
  paymentHash: hashHex,
3080
4319
  amountMsat: Number(payment.amountMsat),
@@ -3117,39 +4356,41 @@ class LightningNode extends events_1.EventEmitter {
3117
4356
  const forwardCltv = blindedOutCltv ?? hopPayload.outgoingCltvValue;
3118
4357
  if (!outgoingScid) {
3119
4358
  this.cleanupHtlcSharedSecret(inHtlcSecretKey);
3120
- this.channelManager.failHtlc(inChannelId, inHtlcId, (0, failures_1.createFailureMessage)(sharedSecret, types_2.UNKNOWN_NEXT_PEER));
4359
+ this.channelManager.failHtlc(inChannelId, inHtlcId, (0, failures_1.createFailureMessage)(sharedSecret, types_3.UNKNOWN_NEXT_PEER));
4360
+ return;
4361
+ }
4362
+ const scidHex = outgoingScid.toString('hex');
4363
+ const outChannelId = this.scidToChannelId.get(scidHex);
4364
+ if (!outChannelId) {
4365
+ this.cleanupHtlcSharedSecret(inHtlcSecretKey);
4366
+ this.channelManager.failHtlc(inChannelId, inHtlcId, (0, failures_1.createFailureMessage)(sharedSecret, types_3.UNKNOWN_NEXT_PEER));
3121
4367
  return;
3122
4368
  }
4369
+ const outPolicy = this.getForwardingPolicyForChannel(outChannelId);
3123
4370
  if (isBlindedForward) {
3124
4371
  if (forwardAmount <= 0n ||
3125
- incomingCltvExpiry - forwardCltv < this.forwardingCltvDelta ||
4372
+ incomingCltvExpiry - forwardCltv < outPolicy.cltvExpiryDelta ||
3126
4373
  (blindedMaxCltv !== undefined && incomingCltvExpiry > blindedMaxCltv)) {
3127
4374
  this.cleanupHtlcSharedSecret(inHtlcSecretKey);
3128
- this.channelManager.failHtlc(inChannelId, inHtlcId, (0, failures_1.createFailureMessage)(sharedSecret, types_2.INCORRECT_CLTV_EXPIRY));
4375
+ this.channelManager.failHtlc(inChannelId, inHtlcId, (0, failures_1.createFailureMessage)(sharedSecret, types_3.INCORRECT_CLTV_EXPIRY));
3129
4376
  return;
3130
4377
  }
3131
4378
  }
3132
4379
  else {
3133
- if (incomingCltvExpiry < forwardCltv + this.forwardingCltvDelta) {
4380
+ if (incomingCltvExpiry < forwardCltv + outPolicy.cltvExpiryDelta) {
3134
4381
  this.cleanupHtlcSharedSecret(inHtlcSecretKey);
3135
- this.channelManager.failHtlc(inChannelId, inHtlcId, (0, failures_1.createFailureMessage)(sharedSecret, types_2.INCORRECT_CLTV_EXPIRY));
4382
+ this.channelManager.failHtlc(inChannelId, inHtlcId, (0, failures_1.createFailureMessage)(sharedSecret, types_3.INCORRECT_CLTV_EXPIRY));
3136
4383
  return;
3137
4384
  }
3138
- const requiredFee = BigInt(this.forwardingFeeBaseMsat) +
3139
- (forwardAmount * BigInt(this.forwardingFeePropMillionths)) / 1000000n;
4385
+ const requiredFee = BigInt(outPolicy.feeBaseMsat) +
4386
+ (forwardAmount * BigInt(outPolicy.feeProportionalMillionths)) /
4387
+ 1000000n;
3140
4388
  if (incomingAmountMsat < forwardAmount + requiredFee) {
3141
4389
  this.cleanupHtlcSharedSecret(inHtlcSecretKey);
3142
- this.channelManager.failHtlc(inChannelId, inHtlcId, (0, failures_1.createFailureMessage)(sharedSecret, types_2.FEE_INSUFFICIENT));
4390
+ this.channelManager.failHtlc(inChannelId, inHtlcId, (0, failures_1.createFailureMessage)(sharedSecret, types_3.FEE_INSUFFICIENT));
3143
4391
  return;
3144
4392
  }
3145
4393
  }
3146
- const scidHex = outgoingScid.toString('hex');
3147
- const outChannelId = this.scidToChannelId.get(scidHex);
3148
- if (!outChannelId) {
3149
- this.cleanupHtlcSharedSecret(inHtlcSecretKey);
3150
- this.channelManager.failHtlc(inChannelId, inHtlcId, (0, failures_1.createFailureMessage)(sharedSecret, types_2.UNKNOWN_NEXT_PEER));
3151
- return;
3152
- }
3153
4394
  const performForward = () => {
3154
4395
  const nextOnionBuf = (0, construct_1.encodeOnionPacket)(nextPacket);
3155
4396
  const outChannel = this.channelManager.getChannel(outChannelId);
@@ -3163,7 +4404,7 @@ class LightningNode extends events_1.EventEmitter {
3163
4404
  if (!result.ok) {
3164
4405
  this.forwardedHtlcs.delete(outKey);
3165
4406
  this.cleanupHtlcSharedSecret(inHtlcSecretKey);
3166
- this.channelManager.failHtlc(inChannelId, inHtlcId, (0, failures_1.createFailureMessage)(sharedSecret, types_2.TEMPORARY_CHANNEL_FAILURE));
4407
+ this.channelManager.failHtlc(inChannelId, inHtlcId, (0, failures_1.createFailureMessage)(sharedSecret, types_3.TEMPORARY_CHANNEL_FAILURE));
3167
4408
  return;
3168
4409
  }
3169
4410
  this.emit('htlc:forward', inChannelId, outChannelId, forwardAmount, paymentHash);
@@ -3184,7 +4425,7 @@ class LightningNode extends events_1.EventEmitter {
3184
4425
  fail: () => {
3185
4426
  this.heldForwards.delete(hashHex);
3186
4427
  this.cleanupHtlcSharedSecret(inHtlcSecretKey);
3187
- this.channelManager.failHtlc(inChannelId, inHtlcId, (0, failures_1.createFailureMessage)(sharedSecret, types_2.UNKNOWN_NEXT_PEER));
4428
+ this.channelManager.failHtlc(inChannelId, inHtlcId, (0, failures_1.createFailureMessage)(sharedSecret, types_3.UNKNOWN_NEXT_PEER));
3188
4429
  }
3189
4430
  });
3190
4431
  this.emit('htlc:held-forward', {
@@ -3235,6 +4476,7 @@ class LightningNode extends events_1.EventEmitter {
3235
4476
  if (forward) {
3236
4477
  this.cleanupHtlcSharedSecret(`${forward.inChannelId.toString('hex')}:${forward.inHtlcId}`);
3237
4478
  this.channelManager.recordPreimage(preimageHash, preimage);
4479
+ this.recordForwardingEvent(channelId, htlcId, forward);
3238
4480
  this.safeStorage(() => this.storage.deleteForwardedHtlc(outKey), 'deleteForwardedHtlc');
3239
4481
  this.channelManager.fulfillHtlc(forward.inChannelId, forward.inHtlcId, preimage);
3240
4482
  this.forwardedHtlcs.delete(outKey);
@@ -3245,10 +4487,10 @@ class LightningNode extends events_1.EventEmitter {
3245
4487
  const hashHex = paymentHash.toString('hex');
3246
4488
  const payment = this.payments.get(hashHex);
3247
4489
  if (payment &&
3248
- payment.direction === types_5.PaymentDirection.OUTGOING &&
3249
- (payment.status === types_5.PaymentStatus.PENDING ||
3250
- payment.status === types_5.PaymentStatus.FAILED)) {
3251
- payment.status = types_5.PaymentStatus.COMPLETED;
4490
+ payment.direction === types_6.PaymentDirection.OUTGOING &&
4491
+ (payment.status === types_6.PaymentStatus.PENDING ||
4492
+ payment.status === types_6.PaymentStatus.FAILED)) {
4493
+ payment.status = types_6.PaymentStatus.COMPLETED;
3252
4494
  payment.preimage = preimage;
3253
4495
  payment.completedAt = Date.now();
3254
4496
  const retryCtx = this.paymentRetryContexts.get(hashHex);
@@ -3285,6 +4527,41 @@ class LightningNode extends events_1.EventEmitter {
3285
4527
  });
3286
4528
  }
3287
4529
  }
4530
+ recordForwardingEvent(outChannelId, outHtlcId, forward) {
4531
+ const outState = this.channelManager
4532
+ .getChannel(outChannelId)
4533
+ ?.getFullState();
4534
+ const inState = this.channelManager
4535
+ .getChannel(forward.inChannelId)
4536
+ ?.getFullState();
4537
+ const outHtlc = outState?.htlcs.get(`offered-${outHtlcId}`);
4538
+ const inHtlc = inState?.htlcs.get(`received-${forward.inHtlcId}`);
4539
+ if (!outHtlc || !inHtlc)
4540
+ return;
4541
+ const amountInMsat = inHtlc.amountMsat;
4542
+ const amountOutMsat = outHtlc.amountMsat;
4543
+ this.emit('htlc:forwarded', {
4544
+ inChannelId: forward.inChannelId,
4545
+ outChannelId,
4546
+ amountInMsat,
4547
+ amountOutMsat,
4548
+ feeMsat: amountInMsat - amountOutMsat
4549
+ });
4550
+ if (!this.storage ||
4551
+ typeof this.storage.saveForwardingEvent !== 'function') {
4552
+ return;
4553
+ }
4554
+ this.safeStorage(() => this.storage.saveForwardingEvent({
4555
+ settledAt: Date.now(),
4556
+ inChannelId: forward.inChannelId.toString('hex'),
4557
+ outChannelId: outChannelId.toString('hex'),
4558
+ inScid: inState?.shortChannelId?.toString('hex'),
4559
+ outScid: outState?.shortChannelId?.toString('hex'),
4560
+ amountInMsat,
4561
+ amountOutMsat,
4562
+ feeMsat: amountInMsat - amountOutMsat
4563
+ }), 'saveForwardingEvent');
4564
+ }
3288
4565
  handleHtlcFailed(channelId, htlcId, reason) {
3289
4566
  const outKey = `${channelId.toString('hex')}:offered-${htlcId}`;
3290
4567
  const forward = this.forwardedHtlcs.get(outKey);
@@ -3306,7 +4583,7 @@ class LightningNode extends events_1.EventEmitter {
3306
4583
  if (!hashHex)
3307
4584
  return;
3308
4585
  const payment = this.payments.get(hashHex);
3309
- if (!payment || payment.direction !== types_5.PaymentDirection.OUTGOING)
4586
+ if (!payment || payment.direction !== types_6.PaymentDirection.OUTGOING)
3310
4587
  return;
3311
4588
  let failureData;
3312
4589
  if (payment.sharedSecrets && reason.length > 0) {
@@ -3356,7 +4633,7 @@ class LightningNode extends events_1.EventEmitter {
3356
4633
  }
3357
4634
  retryCtx.retryCount++;
3358
4635
  payment.retryCount = retryCtx.retryCount;
3359
- payment.status = types_5.PaymentStatus.PENDING;
4636
+ payment.status = types_6.PaymentStatus.PENDING;
3360
4637
  payment.failureCode = undefined;
3361
4638
  payment.failureSourceIndex = undefined;
3362
4639
  payment.completedAt = undefined;
@@ -3368,7 +4645,7 @@ class LightningNode extends events_1.EventEmitter {
3368
4645
  }
3369
4646
  }
3370
4647
  this.paymentRetryContexts.delete(hashHex);
3371
- payment.status = types_5.PaymentStatus.FAILED;
4648
+ payment.status = types_6.PaymentStatus.FAILED;
3372
4649
  payment.completedAt = Date.now();
3373
4650
  this.htlcPaymentMap.delete(key);
3374
4651
  if (this.storage) {
@@ -3391,7 +4668,7 @@ class LightningNode extends events_1.EventEmitter {
3391
4668
  return true;
3392
4669
  if (failureCode & 0x8000)
3393
4670
  return true;
3394
- if (failureCode === types_2.EXPIRY_TOO_FAR)
4671
+ if (failureCode === types_3.EXPIRY_TOO_FAR)
3395
4672
  return true;
3396
4673
  return false;
3397
4674
  }
@@ -3401,12 +4678,36 @@ class LightningNode extends events_1.EventEmitter {
3401
4678
  listPayments() {
3402
4679
  return [...this.payments.values()];
3403
4680
  }
4681
+ listForwards(filter) {
4682
+ if (!this.storage ||
4683
+ typeof this.storage.listForwardingEvents !== 'function') {
4684
+ return [];
4685
+ }
4686
+ try {
4687
+ return this.storage.listForwardingEvents(filter);
4688
+ }
4689
+ catch {
4690
+ return [];
4691
+ }
4692
+ }
4693
+ getForwardingSummary(options) {
4694
+ if (!this.storage ||
4695
+ typeof this.storage.getForwardingSummary !== 'function') {
4696
+ return { count: 0, volumeOutMsat: 0n, feesEarnedMsat: 0n };
4697
+ }
4698
+ try {
4699
+ return this.storage.getForwardingSummary(options);
4700
+ }
4701
+ catch {
4702
+ return { count: 0, volumeOutMsat: 0n, feesEarnedMsat: 0n };
4703
+ }
4704
+ }
3404
4705
  getPaymentProof(paymentHash) {
3405
4706
  const hashHex = paymentHash.toString('hex');
3406
4707
  const payment = this.payments.get(hashHex);
3407
4708
  if (!payment)
3408
4709
  return null;
3409
- if (payment.status !== types_5.PaymentStatus.COMPLETED)
4710
+ if (payment.status !== types_6.PaymentStatus.COMPLETED)
3410
4711
  return null;
3411
4712
  if (!payment.preimage)
3412
4713
  return null;
@@ -3444,7 +4745,7 @@ class LightningNode extends events_1.EventEmitter {
3444
4745
  if (!destination)
3445
4746
  return null;
3446
4747
  const sourceBuf = Buffer.from(this.nodeId, 'hex');
3447
- const route = (0, pathfinding_1.findRoute)(this.graph, sourceBuf, destination, amountMsat, decoded.minFinalCltvExpiry || types_3.DEFAULT_MIN_FINAL_CLTV_EXPIRY, 20, undefined, this.missionControl, undefined, decoded.routingHints, undefined, this.getLocalChannelEdges());
4748
+ const route = (0, pathfinding_1.findRoute)(this.graph, sourceBuf, destination, amountMsat, decoded.minFinalCltvExpiry || types_4.DEFAULT_MIN_FINAL_CLTV_EXPIRY, 20, undefined, this.missionControl, undefined, decoded.routingHints, undefined, this.getLocalChannelEdges());
3448
4749
  if (!route)
3449
4750
  return null;
3450
4751
  return {
@@ -3469,7 +4770,7 @@ class LightningNode extends events_1.EventEmitter {
3469
4770
  if (!destination)
3470
4771
  return null;
3471
4772
  const sourceBuf = Buffer.from(this.nodeId, 'hex');
3472
- const route = (0, pathfinding_1.findRoute)(this.graph, sourceBuf, destination, amountMsat, decoded.minFinalCltvExpiry || types_3.DEFAULT_MIN_FINAL_CLTV_EXPIRY, 20, undefined, this.missionControl, undefined, decoded.routingHints, undefined, this.getLocalChannelEdges());
4773
+ const route = (0, pathfinding_1.findRoute)(this.graph, sourceBuf, destination, amountMsat, decoded.minFinalCltvExpiry || types_4.DEFAULT_MIN_FINAL_CLTV_EXPIRY, 20, undefined, this.missionControl, undefined, decoded.routingHints, undefined, this.getLocalChannelEdges());
3473
4774
  if (!route)
3474
4775
  return null;
3475
4776
  let successProbability = 1.0;
@@ -3492,7 +4793,7 @@ class LightningNode extends events_1.EventEmitter {
3492
4793
  const estimatedTimeMs = hopCount * 2000;
3493
4794
  let alternativeAvailable = false;
3494
4795
  try {
3495
- const altRoute = (0, pathfinding_1.findMultiPathRoute)(this.graph, sourceBuf, destination, amountMsat, decoded.minFinalCltvExpiry || types_3.DEFAULT_MIN_FINAL_CLTV_EXPIRY, undefined, undefined, this.missionControl, decoded.routingHints, undefined, this.getLocalChannelEdges());
4796
+ const altRoute = (0, pathfinding_1.findMultiPathRoute)(this.graph, sourceBuf, destination, amountMsat, decoded.minFinalCltvExpiry || types_4.DEFAULT_MIN_FINAL_CLTV_EXPIRY, undefined, undefined, this.missionControl, decoded.routingHints, undefined, this.getLocalChannelEdges());
3496
4797
  alternativeAvailable = altRoute !== null && altRoute.parts.length > 1;
3497
4798
  }
3498
4799
  catch {
@@ -3526,7 +4827,7 @@ class LightningNode extends events_1.EventEmitter {
3526
4827
  const amountMsat = BigInt(amountSats) * 1000n;
3527
4828
  const destBuf = Buffer.from(destination, 'hex');
3528
4829
  const sourceBuf = Buffer.from(this.nodeId, 'hex');
3529
- const route = (0, pathfinding_1.findRoute)(this.graph, sourceBuf, destBuf, amountMsat, types_3.DEFAULT_MIN_FINAL_CLTV_EXPIRY, 20, undefined, this.missionControl, undefined, undefined, undefined, this.getLocalChannelEdges());
4830
+ const route = (0, pathfinding_1.findRoute)(this.graph, sourceBuf, destBuf, amountMsat, types_4.DEFAULT_MIN_FINAL_CLTV_EXPIRY, 20, undefined, this.missionControl, undefined, undefined, undefined, this.getLocalChannelEdges());
3530
4831
  if (!route)
3531
4832
  return { success: false };
3532
4833
  if (route.hops.length > 0) {
@@ -3542,6 +4843,10 @@ class LightningNode extends events_1.EventEmitter {
3542
4843
  return { success: false };
3543
4844
  }
3544
4845
  }
4846
+ queryRoute(destination, amountMsat, finalCltvExpiry = types_4.DEFAULT_MIN_FINAL_CLTV_EXPIRY) {
4847
+ const sourceBuf = Buffer.from(this.nodeId, 'hex');
4848
+ return (0, pathfinding_1.findRoute)(this.graph, sourceBuf, destination, amountMsat, finalCltvExpiry, undefined, undefined, this.missionControl, undefined, undefined, undefined, this.getLocalChannelEdges());
4849
+ }
3545
4850
  handlePeerMessage(pubkey, type, payload) {
3546
4851
  if (Buffer.isBuffer(payload) && payload.length > validation_2.MAX_MESSAGE_SIZE) {
3547
4852
  this.emit('node:error', {
@@ -3551,17 +4856,17 @@ class LightningNode extends events_1.EventEmitter {
3551
4856
  });
3552
4857
  return;
3553
4858
  }
3554
- if (type === types_4.MessageType.CHANNEL_ANNOUNCEMENT ||
3555
- type === types_4.MessageType.NODE_ANNOUNCEMENT ||
3556
- type === types_4.MessageType.CHANNEL_UPDATE ||
3557
- type === types_4.MessageType.QUERY_CHANNEL_RANGE ||
3558
- type === types_4.MessageType.REPLY_CHANNEL_RANGE ||
3559
- type === types_4.MessageType.QUERY_SHORT_CHANNEL_IDS ||
3560
- type === types_4.MessageType.REPLY_SHORT_CHANNEL_IDS_END ||
3561
- type === types_4.MessageType.GOSSIP_TIMESTAMP_FILTER) {
4859
+ if (type === types_5.MessageType.CHANNEL_ANNOUNCEMENT ||
4860
+ type === types_5.MessageType.NODE_ANNOUNCEMENT ||
4861
+ type === types_5.MessageType.CHANNEL_UPDATE ||
4862
+ type === types_5.MessageType.QUERY_CHANNEL_RANGE ||
4863
+ type === types_5.MessageType.REPLY_CHANNEL_RANGE ||
4864
+ type === types_5.MessageType.QUERY_SHORT_CHANNEL_IDS ||
4865
+ type === types_5.MessageType.REPLY_SHORT_CHANNEL_IDS_END ||
4866
+ type === types_5.MessageType.GOSSIP_TIMESTAMP_FILTER) {
3562
4867
  this.handleGossipMessage(pubkey, type, payload);
3563
4868
  }
3564
- if (type === types_4.MessageType.ONION_MESSAGE) {
4869
+ if (type === types_5.MessageType.ONION_MESSAGE) {
3565
4870
  this.onionMessageManager.handleMessage(pubkey, payload);
3566
4871
  }
3567
4872
  this.channelManager.handleMessage(pubkey, type, payload);
@@ -3593,7 +4898,8 @@ class LightningNode extends events_1.EventEmitter {
3593
4898
  if (this.feeEstimator) {
3594
4899
  this.feeEstimator
3595
4900
  .estimateFee(6)
3596
- .then((satPerVbyte) => {
4901
+ .then((rawSatPerVbyte) => {
4902
+ const satPerVbyte = this.clampEstimatedFeeRate(rawSatPerVbyte);
3597
4903
  if (satPerVbyte > 0) {
3598
4904
  this.feeAdvisor.recordSample(satPerVbyte);
3599
4905
  for (const monitor of this.channelManager.getMonitors().values()) {
@@ -3614,6 +4920,14 @@ class LightningNode extends events_1.EventEmitter {
3614
4920
  return FORCE_CLOSE_DEFAULT_SAT_PER_VBYTE;
3615
4921
  return Math.max(Math.ceil(live * FORCE_CLOSE_FEE_MULTIPLIER), FORCE_CLOSE_DEFAULT_SAT_PER_VBYTE);
3616
4922
  }
4923
+ clampEstimatedFeeRate(satPerVbyte) {
4924
+ return (0, types_6.clampFeeRateSatPerVbyte)(satPerVbyte, (original, clamped) => {
4925
+ this.emitStructuredLog('fee', 'estimate_clamped', {
4926
+ original,
4927
+ clamped
4928
+ });
4929
+ });
4930
+ }
3617
4931
  scanExpiringHtlcs(blockHeight) {
3618
4932
  const claimBuffer = Math.max(LightningNode.INBOUND_HTLC_CLAIM_FORCE_CLOSE_BUFFER, this.htlcSafetyMargin);
3619
4933
  const channels = this.channelManager.listChannels();
@@ -3626,8 +4940,13 @@ class LightningNode extends events_1.EventEmitter {
3626
4940
  if (!key.startsWith('received-'))
3627
4941
  continue;
3628
4942
  const paymentHashHex = htlc.paymentHash?.toString('hex');
3629
- const haveClaim = htlc.state === types_1.HtlcState.FULFILLED ||
3630
- (paymentHashHex !== undefined && this.preimages.has(paymentHashHex));
4943
+ const parkedHold = htlc.state !== types_1.HtlcState.FULFILLED &&
4944
+ paymentHashHex !== undefined &&
4945
+ this.heldInvoiceHashes.has(paymentHashHex);
4946
+ const haveClaim = !parkedHold &&
4947
+ (htlc.state === types_1.HtlcState.FULFILLED ||
4948
+ (paymentHashHex !== undefined &&
4949
+ this.preimages.has(paymentHashHex)));
3631
4950
  if (haveClaim && htlc.cltvExpiry - blockHeight <= claimBuffer) {
3632
4951
  const channelId = state.channelId || state.temporaryChannelId;
3633
4952
  this.emit('node:error', {
@@ -3647,7 +4966,7 @@ class LightningNode extends events_1.EventEmitter {
3647
4966
  const htlcSecretKey = `${channelId.toString('hex')}:${htlc.id}`;
3648
4967
  const htlcSharedSecret = this.receivedHtlcSharedSecrets.get(htlcSecretKey);
3649
4968
  const reason = htlcSharedSecret
3650
- ? (0, failures_1.createFailureMessage)(htlcSharedSecret, types_2.EXPIRY_TOO_SOON)
4969
+ ? (0, failures_1.createFailureMessage)(htlcSharedSecret, types_3.EXPIRY_TOO_SOON)
3651
4970
  : Buffer.alloc(290);
3652
4971
  this.cleanupHtlcSharedSecret(htlcSecretKey);
3653
4972
  this.channelManager.failHtlc(channelId, htlc.id, reason);
@@ -3689,7 +5008,7 @@ class LightningNode extends events_1.EventEmitter {
3689
5008
  const htlcSecretKey = `${channelId.toString('hex')}:${htlc.id}`;
3690
5009
  const sharedSecret = this.receivedHtlcSharedSecrets.get(htlcSecretKey);
3691
5010
  const reason = sharedSecret
3692
- ? (0, failures_1.createFailureMessage)(sharedSecret, types_2.EXPIRY_TOO_SOON)
5011
+ ? (0, failures_1.createFailureMessage)(sharedSecret, types_3.EXPIRY_TOO_SOON)
3693
5012
  : Buffer.alloc(290);
3694
5013
  this.cleanupHtlcSharedSecret(htlcSecretKey);
3695
5014
  this.channelManager.failHtlc(channelId, htlc.id, reason);
@@ -3775,8 +5094,13 @@ class LightningNode extends events_1.EventEmitter {
3775
5094
  feeEstimator: options?.feeEstimator,
3776
5095
  socks5Proxy: options?.socks5Proxy,
3777
5096
  preferAnchors: options?.preferAnchors,
5097
+ largeChannels: options?.largeChannels,
3778
5098
  chainBackend: options?.chainBackend,
3779
5099
  sweepDestinationScript: options?.sweepDestinationScript,
5100
+ peerStorageEnabled: options?.peerStorageEnabled,
5101
+ autoRebalance: options?.autoRebalance,
5102
+ autoTuneFees: options?.autoTuneFees,
5103
+ watchtowers: options?.watchtowers,
3780
5104
  channelKeyDeriver
3781
5105
  });
3782
5106
  }
@@ -3797,6 +5121,9 @@ class LightningNode extends events_1.EventEmitter {
3797
5121
  flags.setOptional(flags_1.Feature.ANCHOR_ZERO_FEE_HTLC);
3798
5122
  flags.setOptional(flags_1.Feature.SHUTDOWN_ANY_SEGWIT);
3799
5123
  flags.setOptional(flags_1.Feature.SIMPLE_CLOSE);
5124
+ flags.setOptional(flags_1.Feature.DUAL_FUND);
5125
+ flags.setOptional(flags_1.Feature.ZERO_CONF);
5126
+ flags.setOptional(flags_1.Feature.PROVIDE_STORAGE);
3800
5127
  return flags;
3801
5128
  }
3802
5129
  sendOnionMessage(destination, messageData, options) {
@@ -3829,7 +5156,7 @@ class LightningNode extends events_1.EventEmitter {
3829
5156
  }
3830
5157
  }
3831
5158
  });
3832
- this.peerManager.onMessage(types_4.MessageType.ONION_MESSAGE, (pubkey, _type, payload) => {
5159
+ this.peerManager.onMessage(types_5.MessageType.ONION_MESSAGE, (pubkey, _type, payload) => {
3833
5160
  this.onionMessageManager.handleMessage(pubkey, payload);
3834
5161
  });
3835
5162
  }
@@ -3859,7 +5186,7 @@ class LightningNode extends events_1.EventEmitter {
3859
5186
  }
3860
5187
  const destination = invoice.nodeId;
3861
5188
  const amountMsat = invoice.amount;
3862
- const finalCltvExpiry = types_3.DEFAULT_MIN_FINAL_CLTV_EXPIRY;
5189
+ const finalCltvExpiry = types_4.DEFAULT_MIN_FINAL_CLTV_EXPIRY;
3863
5190
  const sourceNodeId = (0, ecdh_1.getPublicKey)(this.nodePrivkey);
3864
5191
  if (invoice.paths && invoice.paths.length > 0) {
3865
5192
  const payInfo = invoice.blindedPayInfo?.[0] ?? {
@@ -3902,7 +5229,7 @@ class LightningNode extends events_1.EventEmitter {
3902
5229
  bolt11: '',
3903
5230
  amountMsat: invoice.amount,
3904
5231
  description: invoice.description,
3905
- expiry: invoice.relativeExpiry ?? types_3.DEFAULT_EXPIRY,
5232
+ expiry: invoice.relativeExpiry ?? types_4.DEFAULT_EXPIRY,
3906
5233
  createdAt: Number(invoice.createdAt)
3907
5234
  };
3908
5235
  this.invoices.set(hashHex, invoiceInfo);
@@ -3911,8 +5238,8 @@ class LightningNode extends events_1.EventEmitter {
3911
5238
  paymentHash: invoice.paymentHash,
3912
5239
  preimage,
3913
5240
  amountMsat: invoice.amount,
3914
- status: types_5.PaymentStatus.PENDING,
3915
- direction: types_5.PaymentDirection.INCOMING,
5241
+ status: types_6.PaymentStatus.PENDING,
5242
+ direction: types_6.PaymentDirection.INCOMING,
3916
5243
  createdAt: Date.now()
3917
5244
  });
3918
5245
  }
@@ -3972,9 +5299,9 @@ class LightningNode extends events_1.EventEmitter {
3972
5299
  failPayment(paymentHash) {
3973
5300
  const hashHex = paymentHash.toString('hex');
3974
5301
  const payment = this.payments.get(hashHex);
3975
- if (!payment || payment.status !== types_5.PaymentStatus.PENDING)
5302
+ if (!payment || payment.status !== types_6.PaymentStatus.PENDING)
3976
5303
  return;
3977
- payment.status = types_5.PaymentStatus.FAILED;
5304
+ payment.status = types_6.PaymentStatus.FAILED;
3978
5305
  payment.completedAt = Date.now();
3979
5306
  this.paymentRetryContexts.delete(hashHex);
3980
5307
  this.outboundMppPayments.delete(hashHex);
@@ -4008,9 +5335,9 @@ class LightningNode extends events_1.EventEmitter {
4008
5335
  }
4009
5336
  }
4010
5337
  for (const [hashHex, payment] of this.payments) {
4011
- if (payment.status !== types_5.PaymentStatus.PENDING)
5338
+ if (payment.status !== types_6.PaymentStatus.PENDING)
4012
5339
  continue;
4013
- if (payment.direction !== types_5.PaymentDirection.OUTGOING)
5340
+ if (payment.direction !== types_6.PaymentDirection.OUTGOING)
4014
5341
  continue;
4015
5342
  if (now - payment.createdAt < TEN_MINUTES)
4016
5343
  continue;
@@ -4022,9 +5349,9 @@ class LightningNode extends events_1.EventEmitter {
4022
5349
  scanExpiredPendingPayments() {
4023
5350
  const now = Math.floor(Date.now() / 1000);
4024
5351
  for (const [hashHex, payment] of this.payments) {
4025
- if (payment.status !== types_5.PaymentStatus.PENDING)
5352
+ if (payment.status !== types_6.PaymentStatus.PENDING)
4026
5353
  continue;
4027
- if (payment.direction !== types_5.PaymentDirection.OUTGOING)
5354
+ if (payment.direction !== types_6.PaymentDirection.OUTGOING)
4028
5355
  continue;
4029
5356
  const retryCtx = this.paymentRetryContexts.get(hashHex);
4030
5357
  if (!retryCtx)
@@ -4162,10 +5489,10 @@ class LightningNode extends events_1.EventEmitter {
4162
5489
  const hashHex = paymentHash.toString('hex');
4163
5490
  const existing = this.payments.get(hashHex);
4164
5491
  if (existing) {
4165
- if (existing.status === types_5.PaymentStatus.COMPLETED) {
5492
+ if (existing.status === types_6.PaymentStatus.COMPLETED) {
4166
5493
  return Promise.resolve(existing);
4167
5494
  }
4168
- if (existing.status === types_5.PaymentStatus.FAILED) {
5495
+ if (existing.status === types_6.PaymentStatus.FAILED) {
4169
5496
  return Promise.reject(new Error(`Payment already failed${existing.failureCode !== undefined
4170
5497
  ? ` (code ${existing.failureCode})`
4171
5498
  : ''}`));
@@ -4232,6 +5559,9 @@ class LightningNode extends events_1.EventEmitter {
4232
5559
  for (const channel of channels) {
4233
5560
  const state = channel.getFullState();
4234
5561
  const channelId = state.channelId || state.temporaryChannelId;
5562
+ if (state.dataLossDetected) {
5563
+ continue;
5564
+ }
4235
5565
  const effectiveState = state.state === types_1.ChannelState.AWAITING_REESTABLISH
4236
5566
  ? state.preReestablishState || state.state
4237
5567
  : state.state;
@@ -4307,11 +5637,11 @@ class LightningNode extends events_1.EventEmitter {
4307
5637
  async checkAndUpdateFees() {
4308
5638
  if (!this.feeEstimator)
4309
5639
  return;
4310
- const satPerVbyte = await this.feeEstimator.estimateFee(6);
5640
+ const satPerVbyte = this.clampEstimatedFeeRate(await this.feeEstimator.estimateFee(6));
4311
5641
  if (satPerVbyte <= 0)
4312
5642
  return;
4313
5643
  this.feeAdvisor.recordSample(satPerVbyte);
4314
- const newFeeratePerKw = Math.max((0, types_6.satPerVbyteToSatPerKw)(satPerVbyte), types_6.MIN_FEERATE_PER_KW);
5644
+ const newFeeratePerKw = Math.max((0, types_7.satPerVbyteToSatPerKw)(satPerVbyte), types_7.MIN_FEERATE_PER_KW);
4315
5645
  if (this.lastKnownFeeratePerKw > 0) {
4316
5646
  const ratio = newFeeratePerKw / this.lastKnownFeeratePerKw;
4317
5647
  if (ratio > 0.8 && ratio < 1.2)
@@ -4405,6 +5735,22 @@ class LightningNode extends events_1.EventEmitter {
4405
5735
  }
4406
5736
  }
4407
5737
  }
5738
+ findLocalChannelByScid(scid, peerPubkeyHex) {
5739
+ if (!scid || scid.length === 0 || scid.equals(Buffer.alloc(scid.length))) {
5740
+ return undefined;
5741
+ }
5742
+ for (const channel of this.channelManager.getChannelsByPeer(peerPubkeyHex)) {
5743
+ if (channel.getState() !== types_1.ChannelState.NORMAL)
5744
+ continue;
5745
+ const st = channel.getFullState();
5746
+ if ((st.shortChannelId && st.shortChannelId.equals(scid)) ||
5747
+ (st.scidAlias && st.scidAlias.equals(scid)) ||
5748
+ (st.remoteScidAlias && st.remoteScidAlias.equals(scid))) {
5749
+ return channel;
5750
+ }
5751
+ }
5752
+ return undefined;
5753
+ }
4408
5754
  findChannelForPeer(peerPubkeyHex, amountMsat) {
4409
5755
  const channels = this.channelManager.getChannelsByPeer(peerPubkeyHex);
4410
5756
  const normalChannels = channels.filter((ch) => ch.getState() === types_1.ChannelState.NORMAL);
@@ -4430,7 +5776,9 @@ class LightningNode extends events_1.EventEmitter {
4430
5776
  }
4431
5777
  }
4432
5778
  exports.LightningNode = LightningNode;
5779
+ LightningNode.PEER_STORAGE_MIN_INTERVAL_MS = 60000;
4433
5780
  LightningNode.SPLICE_MIN_AMOUNT_SATS = 546n;
5781
+ LightningNode.REBALANCE_BUDGET_KEY = 'advisor:rebalance-budget';
4434
5782
  LightningNode.INBOUND_HTLC_CLAIM_FORCE_CLOSE_BUFFER = 18;
4435
5783
  LightningNode.OFFERED_HTLC_FORCE_CLOSE_GRACE_BLOCKS = 6;
4436
5784
  //# sourceMappingURL=lightning-node.js.map