beignet 0.15.1 → 0.17.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 (53) hide show
  1. package/README.md +25 -13
  2. package/dist/cli/beignet-node.js +90 -9
  3. package/dist/cli/beignet-node.js.map +1 -1
  4. package/dist/cli/cli.js +6 -3
  5. package/dist/cli/cli.js.map +1 -1
  6. package/dist/cli/config.js +17 -2
  7. package/dist/cli/config.js.map +1 -1
  8. package/dist/cli/daemon.js +21 -9
  9. package/dist/cli/daemon.js.map +1 -1
  10. package/dist/cli/errors.js +5 -1
  11. package/dist/cli/errors.js.map +1 -1
  12. package/dist/cli/openapi.js +59 -11
  13. package/dist/cli/openapi.js.map +1 -1
  14. package/dist/lightning/channel/channel-state.js +2 -0
  15. package/dist/lightning/channel/channel-state.js.map +1 -1
  16. package/dist/lightning/channel/channel.js +46 -4
  17. package/dist/lightning/channel/channel.js.map +1 -1
  18. package/dist/lightning/node/lightning-node.js +184 -37
  19. package/dist/lightning/node/lightning-node.js.map +1 -1
  20. package/dist/lightning/node/types.js.map +1 -1
  21. package/dist/lightning/storage/serialization.js +12 -0
  22. package/dist/lightning/storage/serialization.js.map +1 -1
  23. package/dist/lightning/swaps/client.js +129 -1
  24. package/dist/lightning/swaps/client.js.map +1 -1
  25. package/dist/lightning/swaps/engine-common.js +97 -0
  26. package/dist/lightning/swaps/engine-common.js.map +1 -0
  27. package/dist/lightning/swaps/index.js +2 -0
  28. package/dist/lightning/swaps/index.js.map +1 -1
  29. package/dist/lightning/swaps/ledger.js +15 -15
  30. package/dist/lightning/swaps/ledger.js.map +1 -1
  31. package/dist/lightning/swaps/messages.js +148 -3
  32. package/dist/lightning/swaps/messages.js.map +1 -1
  33. package/dist/lightning/swaps/reverse-engine.js +25 -80
  34. package/dist/lightning/swaps/reverse-engine.js.map +1 -1
  35. package/dist/lightning/swaps/submarine-engine.js +1606 -0
  36. package/dist/lightning/swaps/submarine-engine.js.map +1 -0
  37. package/dist/types/cli/beignet-node.d.ts +9 -3
  38. package/dist/types/cli/errors.d.ts +2 -0
  39. package/dist/types/cli/types.d.ts +38 -0
  40. package/dist/types/lightning/channel/channel-state.d.ts +4 -0
  41. package/dist/types/lightning/channel/channel.d.ts +1 -0
  42. package/dist/types/lightning/node/lightning-node.d.ts +8 -2
  43. package/dist/types/lightning/node/types.d.ts +12 -0
  44. package/dist/types/lightning/storage/serialization.d.ts +4 -0
  45. package/dist/types/lightning/swaps/client.d.ts +31 -1
  46. package/dist/types/lightning/swaps/engine-common.d.ts +10 -0
  47. package/dist/types/lightning/swaps/index.d.ts +2 -0
  48. package/dist/types/lightning/swaps/ledger.d.ts +14 -1
  49. package/dist/types/lightning/swaps/messages.d.ts +51 -2
  50. package/dist/types/lightning/swaps/reverse-engine.d.ts +1 -0
  51. package/dist/types/lightning/swaps/submarine-engine.d.ts +144 -0
  52. package/docs/AI_AGENT_GUIDE.md +2 -0
  53. package/package.json +2 -2
package/README.md CHANGED
@@ -497,23 +497,35 @@ node can run for others, each an explicit opt-in switched on with an exact
497
497
  The SSE stream carries `ffor:state`, `ffor:settled`, `ffor:delegated-failed`,
498
498
  `ffor:enforce` and the witness and issuer events.
499
499
 
500
- ### Reverse swaps (Lightning to on-chain)
500
+ ### Swaps (Lightning to on-chain, and on-chain to Lightning)
501
501
 
502
- A beignet node can serve reverse swaps to any Lightning peer (issue #737): the
503
- peer pays a hold invoice, this node funds a P2WSH contract the peer claims on
504
- chain with its preimage, and the claim settles the hold. The role is an
505
- explicit opt-in switched on with an exact `true`, because it locks this node's
506
- own coins in contracts for peers:
502
+ A beignet node can serve swaps to any Lightning peer in both directions.
503
+ Reverse (issue #737): the peer pays a hold invoice, this node funds a P2WSH
504
+ contract the peer claims on chain with its preimage, and the claim settles
505
+ the hold. Submarine (issue #743): the peer locks coins in a P2WSH contract,
506
+ this node pays the peer's own invoice under an absolute HTLC expiry ceiling,
507
+ and the preimage that payment reveals claims the coins. Each direction is an
508
+ explicit opt-in switched on with an exact `true`, because it puts this node's
509
+ own funds at risk for peers:
507
510
 
508
511
  | Env | Role |
509
512
  |---|---|
510
513
  | `BEIGNET_SWAPS` | Serve reverse swaps. `BEIGNET_SWAP_FLAT_FEE_SAT` and `BEIGNET_SWAP_FEE_PPM` price them; `BEIGNET_SWAP_MIN_SAT`, `BEIGNET_SWAP_MAX_SAT`, `BEIGNET_SWAP_MAX_EXPOSURE_SAT` and `BEIGNET_SWAP_MAX_CONCURRENT` cap what is at risk; `BEIGNET_SWAP_REFUND_DELTA_BLOCKS`, `BEIGNET_SWAP_FUNDING_CONFS` and `BEIGNET_SWAP_RESOLUTION_CONFS` set the timing. `GET /swaps/status`, `GET /swaps`, `POST /swaps/cancel`. |
511
-
512
- The provider funds only against the complete committed MPP set of the hold
513
- invoice, settles the hold the moment a claim reveals the preimage (mempool
514
- included), and cancels the hold only after its own refund has confirmed to
515
- policy depth; never because the refund height passed. The SSE stream carries
516
- `swap:created` through `swap:settled`, `swap:refunded` and `swap:exposed`.
514
+ | `BEIGNET_SWAP_SUBMARINE` | With `BEIGNET_SWAPS`, also serve submarine swaps (on-chain to Lightning): a peer locks coins in a contract, this node pays the peer's invoice under an absolute HTLC expiry ceiling and claims the coins with the preimage. `BEIGNET_SWAP_CLAIM_SAFETY_BLOCKS`, `BEIGNET_SWAP_PAYMENT_MAX_FEE_PPM`, `BEIGNET_SWAP_CLAIM_BUMP_INTERVAL_BLOCKS` and `BEIGNET_SWAP_SUBMARINE_REFUND_DELTA_BLOCKS` set the direction's margins; the fee and exposure caps above apply to both. |
515
+
516
+ The reverse provider funds only against the complete committed MPP set of
517
+ the hold invoice, settles the hold the moment a claim reveals the preimage
518
+ (mempool included), and cancels the hold only after its own refund has
519
+ confirmed to policy depth; never because the refund height passed. The
520
+ submarine provider pays only once the peer's funding has confirmed to policy
521
+ depth and been re-verified unspent immediately before the dispatch, binds
522
+ every HTLC of the payment to `refundHeight` minus its claim margins, judges
523
+ the payment by the node's own HTLC view (never by a wall clock or a failed
524
+ record while an HTLC is out), and persists its claim before broadcasting it.
525
+ The SSE stream carries `swap:created` through `swap:settled`, `swap:refunded`
526
+ and `swap:exposed` for the reverse direction and `swap:funding-seen`,
527
+ `swap:paying`, `swap:preimage`, `swap:claim-broadcast`,
528
+ `swap:claim-confirmed` and `swap:payment-failed` for the submarine one.
517
529
 
518
530
  ## Protocol layer (advanced)
519
531
 
@@ -616,7 +628,7 @@ LightningNode High-level API (EventEmitter)
616
628
  | `recovery/` | Safety transition layer: atomic persistence, the durable outbound-message outbox, the opt-in hash-chained recovery journal, and the peer_storage Recovery Capsule |
617
629
  | `liquidity/` | JIT channel receive (LSP role): intercept SCIDs, held HTLCs, zero-conf open or splice, then forward; the opening fee is skimmed off the delivery for wallets that accept it, or charged to the sender through the invoice hint (hop mode) for wallets that cannot settle a short HTLC |
618
630
  | `direct-funding/` | Third-party direct funding: the signed payment request envelope, sealed frames, protocol messages, outstanding-request store, the transport registry with its direct-peer, onion and blind-relay lanes, the receiver engine that turns a payer's offered UTXO into channel funding, and the payer engine that verifies and signs it |
619
- | `swaps/` | Swaps: the P2WSH HTLC contract, claim/refund transactions, preimage extraction, admission policies, the durable swap ledger, the chain resolver, the wire protocol, and the reverse swap provider engine (Lightning to on-chain) |
631
+ | `swaps/` | Swaps: the P2WSH HTLC contract, claim/refund transactions, preimage extraction, admission policies, the durable swap ledger, the chain resolver, the wire protocol, and the swap provider engines (reverse: Lightning to on-chain; submarine: on-chain to Lightning) |
620
632
  | `l402/` | L402 (Lightning HTTP 402) client: challenge parsing, macaroon reading, paid credentials |
621
633
  | `node/` | LightningNode orchestrator, the main protocol-layer entry point |
622
634
  | `wallet/` | WalletFundingProvider, adapts the on-chain Wallet for auto-funded opens |
@@ -977,6 +977,29 @@ class BeignetNode extends events_1.EventEmitter {
977
977
  refundDeltaBlocks: opts.swaps.refundDeltaBlocks
978
978
  }
979
979
  }
980
+ : {}),
981
+ ...(opts.swaps.submarine === true
982
+ ? {
983
+ submarine: {
984
+ enabled: true,
985
+ ...(opts.swaps.claimSafetyBlocks !== undefined
986
+ ? { claimSafetyBlocks: opts.swaps.claimSafetyBlocks }
987
+ : {}),
988
+ ...(opts.swaps.paymentMaxFeePpm !== undefined
989
+ ? { paymentMaxFeePpm: opts.swaps.paymentMaxFeePpm }
990
+ : {}),
991
+ ...(opts.swaps.claimBumpIntervalBlocks !== undefined
992
+ ? {
993
+ claimBumpIntervalBlocks: opts.swaps.claimBumpIntervalBlocks
994
+ }
995
+ : {}),
996
+ ...(opts.swaps.submarineRefundDeltaBlocks !== undefined
997
+ ? {
998
+ refundDeltaBlocks: opts.swaps.submarineRefundDeltaBlocks
999
+ }
1000
+ : {})
1001
+ }
1002
+ }
980
1003
  : {})
981
1004
  }
982
1005
  : undefined,
@@ -1280,7 +1303,27 @@ class BeignetNode extends events_1.EventEmitter {
1280
1303
  'ffor:witness-recorded',
1281
1304
  'ffor:witness-released',
1282
1305
  'ffor:issuer-provisioned',
1283
- 'ffor:issuer-issued'
1306
+ 'ffor:issuer-issued',
1307
+ 'swap:created',
1308
+ 'swap:held',
1309
+ 'swap:funding',
1310
+ 'swap:funded',
1311
+ 'swap:claimed',
1312
+ 'swap:settled',
1313
+ 'swap:refund-broadcast',
1314
+ 'swap:refunded',
1315
+ 'swap:hold-cancelled',
1316
+ 'swap:exposed',
1317
+ 'swap:failed',
1318
+ 'swap:funding-seen',
1319
+ 'swap:funding-lost',
1320
+ 'swap:paying',
1321
+ 'swap:payment-unresolved',
1322
+ 'swap:preimage',
1323
+ 'swap:claim-broadcast',
1324
+ 'swap:claim-confirmed',
1325
+ 'swap:payment-failed',
1326
+ 'swap:cancelled'
1284
1327
  ]) {
1285
1328
  this.node.on(evt, (data) => {
1286
1329
  this.emit(evt, jsonSafeEvent(data));
@@ -4247,7 +4290,26 @@ class BeignetNode extends events_1.EventEmitter {
4247
4290
  timeouts: status.timeouts,
4248
4291
  counts: status.counts,
4249
4292
  exposedSat: Number(status.exposedSat),
4250
- exposedCount: status.exposedCount
4293
+ exposedCount: status.exposedCount,
4294
+ submarine: status.submarine.enabled
4295
+ ? {
4296
+ enabled: true,
4297
+ fee: {
4298
+ flatFeeSat: Number(status.submarine.fee.flatFeeSat),
4299
+ feePpm: status.submarine.fee.feePpm
4300
+ },
4301
+ limits: {
4302
+ minSwapSat: Number(status.submarine.limits.minSwapSat),
4303
+ maxSwapSat: Number(status.submarine.limits.maxSwapSat),
4304
+ maxTotalExposureSat: Number(status.submarine.limits.maxTotalExposureSat),
4305
+ maxConcurrentSwaps: status.submarine.limits.maxConcurrentSwaps
4306
+ },
4307
+ timeouts: status.submarine.timeouts,
4308
+ counts: status.submarine.counts,
4309
+ exposedSat: Number(status.submarine.exposedSat),
4310
+ exposedCount: status.submarine.exposedCount
4311
+ }
4312
+ : { enabled: false }
4251
4313
  };
4252
4314
  }
4253
4315
  listSwaps(id) {
@@ -4937,7 +4999,19 @@ class BeignetNode extends events_1.EventEmitter {
4937
4999
  }
4938
5000
  return { status, summary, checks, invoice };
4939
5001
  }
4940
- async payInvoice(bolt11, timeoutMs = 60000, maxFeeSats, amountSats, metadata) {
5002
+ _cltvCeiling(cltvLimit) {
5003
+ if (cltvLimit === undefined)
5004
+ return undefined;
5005
+ if (!Number.isSafeInteger(cltvLimit) || cltvLimit < 1) {
5006
+ throw new errors_1.BeignetError('INVALID_PARAMS', 'cltvLimit must be a positive integer number of blocks');
5007
+ }
5008
+ const height = this.node.getCurrentBlockHeight();
5009
+ if (!(height > 0)) {
5010
+ throw new errors_1.BeignetError('CHAIN_NOT_SYNCED', 'cltvLimit needs the current block height, which is not known yet');
5011
+ }
5012
+ return height + cltvLimit;
5013
+ }
5014
+ async payInvoice(bolt11, timeoutMs = 60000, maxFeeSats, amountSats, metadata, cltvLimit) {
4941
5015
  this._checkDraining();
4942
5016
  const decoded = decodeInvoiceInput(bolt11);
4943
5017
  const paymentHashHex = decoded.paymentHash.toString('hex');
@@ -4950,6 +5024,7 @@ class BeignetNode extends events_1.EventEmitter {
4950
5024
  ? BigInt(requireNonNegativeSafeInteger(amountSats, 'amountSats')) *
4951
5025
  1000n
4952
5026
  : undefined;
5027
+ const maxCltvExpiryHeight = this._cltvCeiling(cltvLimit);
4953
5028
  if (spendAmountSats > 0) {
4954
5029
  this._checkMaxPayment(spendAmountSats);
4955
5030
  this._checkSpendLimit(spendAmountSats);
@@ -5015,7 +5090,7 @@ class BeignetNode extends events_1.EventEmitter {
5015
5090
  this.node.on('payment:sent', onSent);
5016
5091
  this.node.on('payment:failed', onFailed);
5017
5092
  try {
5018
- this.node.sendPayment(bolt11, undefined, maxFeeMsat, amountMsat);
5093
+ this.node.sendPayment(bolt11, undefined, maxFeeMsat, amountMsat, maxCltvExpiryHeight);
5019
5094
  }
5020
5095
  catch (err) {
5021
5096
  cleanup();
@@ -5029,6 +5104,7 @@ class BeignetNode extends events_1.EventEmitter {
5029
5104
  DUPLICATE_PAYMENT: 'DUPLICATE_PAYMENT',
5030
5105
  NO_CHANNEL_TO_HOP: 'PEER_NOT_CONNECTED',
5031
5106
  FEE_EXCEEDS_MAX: 'PAYMENT_FAILED',
5107
+ CLTV_EXCEEDS_MAX: 'CLTV_EXCEEDS_MAX',
5032
5108
  MISSING_AMOUNT: 'INVALID_PARAMS',
5033
5109
  INVALID_INVOICE: 'INVALID_PARAMS',
5034
5110
  INVOICE_EXPIRED: 'INVOICE_EXPIRED'
@@ -5048,9 +5124,9 @@ class BeignetNode extends events_1.EventEmitter {
5048
5124
  }
5049
5125
  });
5050
5126
  }
5051
- async payInvoiceSafe(bolt11, timeoutMs = 60000, maxFeeSats, amountSats, metadata) {
5127
+ async payInvoiceSafe(bolt11, timeoutMs = 60000, maxFeeSats, amountSats, metadata, cltvLimit) {
5052
5128
  try {
5053
- return await this.payInvoice(bolt11, timeoutMs, maxFeeSats, amountSats, metadata);
5129
+ return await this.payInvoice(bolt11, timeoutMs, maxFeeSats, amountSats, metadata, cltvLimit);
5054
5130
  }
5055
5131
  catch (err) {
5056
5132
  let hashHex = 'unknown';
@@ -5090,7 +5166,7 @@ class BeignetNode extends events_1.EventEmitter {
5090
5166
  let lastError;
5091
5167
  for (let attempt = 1; attempt <= maxRetries + 1; attempt++) {
5092
5168
  try {
5093
- const result = await this.payInvoice(bolt11, 60000, opts.maxFeeSats, opts.amountSats, opts.metadata);
5169
+ const result = await this.payInvoice(bolt11, 60000, opts.maxFeeSats, opts.amountSats, opts.metadata, opts.cltvLimit);
5094
5170
  return { ...result, attempts: attempt };
5095
5171
  }
5096
5172
  catch (err) {
@@ -5180,7 +5256,7 @@ class BeignetNode extends events_1.EventEmitter {
5180
5256
  attempts: maxRetries + 1
5181
5257
  };
5182
5258
  }
5183
- sendPaymentAsync(bolt11, maxFeeSats, amountSats, metadata) {
5259
+ sendPaymentAsync(bolt11, maxFeeSats, amountSats, metadata, cltvLimit) {
5184
5260
  this._checkDraining();
5185
5261
  const decoded = decodeInvoiceInput(bolt11);
5186
5262
  const paymentHashHex = decoded.paymentHash.toString('hex');
@@ -5193,6 +5269,7 @@ class BeignetNode extends events_1.EventEmitter {
5193
5269
  ? BigInt(requireNonNegativeSafeInteger(amountSats, 'amountSats')) *
5194
5270
  1000n
5195
5271
  : undefined;
5272
+ const maxCltvExpiryHeight = this._cltvCeiling(cltvLimit);
5196
5273
  let claim;
5197
5274
  if (spendAmountSats > 0) {
5198
5275
  this._checkMaxPayment(spendAmountSats);
@@ -5204,11 +5281,15 @@ class BeignetNode extends events_1.EventEmitter {
5204
5281
  if (metadata) {
5205
5282
  this.node.setPaymentMetadata(decoded.paymentHash, metadata);
5206
5283
  }
5207
- result = this.node.sendPayment(bolt11, undefined, maxFeeMsat, amountMsat);
5284
+ result = this.node.sendPayment(bolt11, undefined, maxFeeMsat, amountMsat, maxCltvExpiryHeight);
5208
5285
  }
5209
5286
  catch (err) {
5210
5287
  if (claim)
5211
5288
  this._closeAsyncSpendClaim(paymentHashHex, claim);
5289
+ if (err instanceof Error &&
5290
+ err.code === 'CLTV_EXCEEDS_MAX') {
5291
+ throw new errors_1.BeignetError('CLTV_EXCEEDS_MAX', err.message);
5292
+ }
5212
5293
  throw err;
5213
5294
  }
5214
5295
  if (claim && result.status === 'FAILED') {