@atomiqlabs/lp-lib 17.5.3 → 17.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/swaps/assertions/FromBtcAmountAssertions.js +2 -2
  2. package/dist/swaps/escrow/frombtc_abstract/FromBtcAbs.js +45 -19
  3. package/dist/swaps/escrow/frombtcln_abstract/FromBtcLnAbs.d.ts +2 -2
  4. package/dist/swaps/escrow/frombtcln_abstract/FromBtcLnAbs.js +35 -25
  5. package/dist/swaps/escrow/frombtcln_autoinit/FromBtcLnAuto.d.ts +2 -2
  6. package/dist/swaps/escrow/frombtcln_autoinit/FromBtcLnAuto.js +33 -22
  7. package/dist/swaps/escrow/tobtc_abstract/ToBtcAbs.d.ts +1 -1
  8. package/dist/swaps/escrow/tobtc_abstract/ToBtcAbs.js +29 -11
  9. package/dist/swaps/escrow/tobtc_abstract/ToBtcSwapAbs.d.ts +2 -2
  10. package/dist/swaps/escrow/tobtc_abstract/ToBtcSwapAbs.js +1 -1
  11. package/dist/swaps/escrow/tobtcln_abstract/ToBtcLnAbs.d.ts +1 -0
  12. package/dist/swaps/escrow/tobtcln_abstract/ToBtcLnAbs.js +54 -9
  13. package/dist/swaps/spv_vault_swap/SpvVaultSwapHandler.js +83 -30
  14. package/dist/swaps/trusted/frombtc_trusted/FromBtcTrusted.js +2 -2
  15. package/dist/swaps/trusted/frombtcln_trusted/FromBtcLnTrusted.js +11 -17
  16. package/dist/utils/Utils.d.ts +33 -0
  17. package/dist/utils/Utils.js +51 -1
  18. package/dist/utils/paramcoders/ParamDecoder.js +4 -86
  19. package/dist/utils/paramcoders/SchemaVerifier.d.ts +11 -2
  20. package/dist/utils/paramcoders/SchemaVerifier.js +93 -47
  21. package/package.json +1 -1
  22. package/src/swaps/assertions/FromBtcAmountAssertions.ts +2 -2
  23. package/src/swaps/escrow/frombtc_abstract/FromBtcAbs.ts +44 -19
  24. package/src/swaps/escrow/frombtcln_abstract/FromBtcLnAbs.ts +47 -30
  25. package/src/swaps/escrow/frombtcln_autoinit/FromBtcLnAuto.ts +45 -27
  26. package/src/swaps/escrow/tobtc_abstract/ToBtcAbs.ts +28 -13
  27. package/src/swaps/escrow/tobtc_abstract/ToBtcSwapAbs.ts +4 -4
  28. package/src/swaps/escrow/tobtcln_abstract/ToBtcLnAbs.ts +63 -13
  29. package/src/swaps/spv_vault_swap/SpvVaultSwapHandler.ts +79 -33
  30. package/src/swaps/trusted/frombtc_trusted/FromBtcTrusted.ts +25 -2
  31. package/src/swaps/trusted/frombtcln_trusted/FromBtcLnTrusted.ts +10 -16
  32. package/src/utils/Utils.ts +48 -0
  33. package/src/utils/paramcoders/ParamDecoder.ts +10 -66
  34. package/src/utils/paramcoders/SchemaVerifier.ts +80 -37
@@ -33,9 +33,10 @@ class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
33
33
  this.config.minLnBaseFee = this.config.minLnBaseFee || 5n;
34
34
  this.config.exactInExpiry = this.config.exactInExpiry || 10 * 1000;
35
35
  this.config.lnSendBitcoinBlockTimeSafetyFactorPPM = this.config.lnSendBitcoinBlockTimeSafetyFactorPPM ?? (this.config.safetyFactor * 1000000n);
36
- if (this.config.lnSendBitcoinBlockTimeSafetyFactorPPM <= 1100000n) {
37
- throw new Error("Lightning network send block safety factor set below 1.1, this is insecure!");
36
+ if (this.config.lnSendBitcoinBlockTimeSafetyFactorPPM <= 1250000n) {
37
+ throw new Error("Lightning network send block safety factor set below 1.25, this is insecure!");
38
38
  }
39
+ this.cltvDeltaLowerBound = (0, Utils_1.getMinSafeBlockWindowSlow)(Number(this.config.lnSendBitcoinBlockTimeSafetyFactorPPM) / 1000000);
39
40
  }
40
41
  /**
41
42
  * Cleans up exactIn authorization that are already past their expiry
@@ -100,7 +101,8 @@ class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
100
101
  }
101
102
  ]);
102
103
  for (let { obj: swap } of queriedData) {
103
- await this.processPastSwap(swap);
104
+ await this.processPastSwap(swap)
105
+ .catch(e => this.swapLogger.error(swap, "processPastSwap(): Error executing watchdog function: ", e));
104
106
  }
105
107
  }
106
108
  /**
@@ -198,6 +200,10 @@ class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
198
200
  this.swapLogger.info(invoiceData, "subscribeToPayment(): result callback, outcome: " + result.status + " invoice: " + invoiceData.pr);
199
201
  this.processPaymentResult(invoiceData, result).catch(e => this.swapLogger.error(invoiceData, "subscribeToPayment(): process payment result", e));
200
202
  this.activeSubscriptions.delete(paymentHash);
203
+ }).catch(e => {
204
+ this.swapLogger.error(invoiceData, "subscribeToPayment(): error while waiting for invoice payment: ", e);
205
+ //Delete from active subscription, the watchdog will re-subscribe later
206
+ this.activeSubscriptions.delete(paymentHash);
201
207
  });
202
208
  this.swapLogger.info(invoiceData, "subscribeToPayment(): subscribe to payment outcome, invoice: " + invoiceData.pr);
203
209
  this.activeSubscriptions.add(paymentHash);
@@ -224,6 +230,11 @@ class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
224
230
  const maxFee = swap.quotedNetworkFee;
225
231
  const maxUsableCLTVdelta = (expiryTimestamp - currentTimestamp - this.config.gracePeriod)
226
232
  / (this.config.bitcoinBlocktime * this.config.lnSendBitcoinBlockTimeSafetyFactorPPM / 1000000n);
233
+ if (maxUsableCLTVdelta < this.cltvDeltaLowerBound)
234
+ throw {
235
+ code: 90008,
236
+ msg: "Calculated CLTV delta is too low for current safety factor!"
237
+ };
227
238
  //Initiate payment
228
239
  this.swapLogger.info(swap, "sendLightningPayment(): paying lightning network invoice," +
229
240
  " cltvDelta: " + maxUsableCLTVdelta.toString(10) +
@@ -311,6 +322,9 @@ class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
311
322
  else
312
323
  throw e;
313
324
  }
325
+ const unlock = swap.lock(120);
326
+ if (unlock == null)
327
+ return;
314
328
  await swap.setState(ToBtcLnSwapAbs_1.ToBtcLnSwapState.COMMITED);
315
329
  await this.saveSwapData(swap);
316
330
  try {
@@ -322,13 +336,29 @@ class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
322
336
  if ((0, Utils_1.isDefinedRuntimeError)(e)) {
323
337
  if (swap.metadata != null)
324
338
  swap.metadata.payError = e;
325
- await swap.setState(ToBtcLnSwapAbs_1.ToBtcLnSwapState.NON_PAYABLE);
326
- await this.saveSwapData(swap);
327
- return;
339
+ const payment = await this.lightning.getPayment(swap.lnPaymentHash);
340
+ if (!unlock())
341
+ return;
342
+ if (payment == null || payment.status === "failed") {
343
+ if (swap.state === ToBtcLnSwapAbs_1.ToBtcLnSwapState.COMMITED)
344
+ await swap.setState(ToBtcLnSwapAbs_1.ToBtcLnSwapState.NON_PAYABLE);
345
+ await this.saveSwapData(swap);
346
+ return;
347
+ }
348
+ else if (payment.status === "confirmed") {
349
+ await this.processPaymentResult(swap, payment);
350
+ return;
351
+ }
352
+ else {
353
+ //Fall-through to subscribe
354
+ }
328
355
  }
329
356
  else
330
357
  throw e;
331
358
  }
359
+ finally {
360
+ unlock();
361
+ }
332
362
  this.subscribeToPayment(swap);
333
363
  return;
334
364
  }
@@ -452,6 +482,11 @@ class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
452
482
  async checkAndGetNetworkFee(amountBD, maxFee, expiryTimestamp, currentTimestamp, pr, metadata, abortSignal) {
453
483
  const maxUsableCLTV = (expiryTimestamp - currentTimestamp - this.config.gracePeriod)
454
484
  / (this.config.bitcoinBlocktime * this.config.lnSendBitcoinBlockTimeSafetyFactorPPM / 1000000n);
485
+ if (maxUsableCLTV < this.cltvDeltaLowerBound)
486
+ throw {
487
+ code: 20002,
488
+ msg: "Cannot route the payment (calculated CLTV delta too short - increase timeout)!"
489
+ };
455
490
  const blockHeight = await this.lightning.getBlockheight();
456
491
  abortSignal.throwIfAborted();
457
492
  metadata.times.blockheightFetched = Date.now();
@@ -658,8 +693,8 @@ class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
658
693
  */
659
694
  const parsedBody = await req.paramReader.getParams({
660
695
  pr: SchemaVerifier_1.FieldTypeEnum.String,
661
- maxFee: SchemaVerifier_1.FieldTypeEnum.BigInt,
662
- expiryTimestamp: SchemaVerifier_1.FieldTypeEnum.BigInt,
696
+ maxFee: SchemaVerifier_1.FieldTypeEnum.BigIntPositive,
697
+ expiryTimestamp: SchemaVerifier_1.FieldTypeEnum.BigIntPositive,
663
698
  token: (val) => val != null &&
664
699
  typeof (val) === "string" &&
665
700
  this.isTokenSupported(chainIdentifier, val) ? val : null,
@@ -667,7 +702,7 @@ class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
667
702
  typeof (val) === "string" &&
668
703
  chainInterface.isValidAddress(val, true) ? val : null,
669
704
  exactIn: SchemaVerifier_1.FieldTypeEnum.BooleanOptional,
670
- amount: SchemaVerifier_1.FieldTypeEnum.BigIntOptional
705
+ amount: SchemaVerifier_1.FieldTypeEnum.BigIntPositiveOptional
671
706
  });
672
707
  if (parsedBody == null) {
673
708
  throw {
@@ -832,6 +867,16 @@ class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
832
867
  msg: "Payment expired"
833
868
  };
834
869
  if (data.state === ToBtcLnSwapAbs_1.ToBtcLnSwapState.NON_PAYABLE) {
870
+ if (data.payInitiated) {
871
+ //Check that the ln payment is indeed non-existent or failed
872
+ const lnPayment = await this.lightning.getPayment(data.lnPaymentHash);
873
+ if (lnPayment != null && lnPayment.status !== "failed")
874
+ throw {
875
+ _httpStatus: 200,
876
+ code: 20008,
877
+ msg: "Payment in-flight"
878
+ };
879
+ }
835
880
  const refundSigData = await swapContract.getRefundSignature(signer, data.data, this.config.refundAuthorizationTimeout);
836
881
  //Double check the state after promise result
837
882
  if (data.state !== ToBtcLnSwapAbs_1.ToBtcLnSwapState.NON_PAYABLE)
@@ -174,13 +174,21 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
174
174
  const vault = await this.Vaults.getVault(swap.chainIdentifier, swap.vaultOwner, swap.vaultId);
175
175
  const foundWithdrawal = vault.pendingWithdrawals.find(val => val.btcTx.txid === swap.btcTxId);
176
176
  let tx = foundWithdrawal?.btcTx;
177
- if (tx == null)
177
+ if (tx == null) {
178
178
  tx = await this.bitcoinRpc.getTransaction(swap.btcTxId);
179
+ }
180
+ else {
181
+ tx = await (0, BitcoinUtils_1.checkTransactionReplaced)(tx.txid, tx.raw, this.bitcoinRpc);
182
+ }
179
183
  if (tx == null) {
180
184
  await this.removeSwapData(swap, SpvVaultSwap_1.SpvVaultSwapState.FAILED);
185
+ if (foundWithdrawal != null) {
186
+ vault.removeWithdrawal(foundWithdrawal);
187
+ await this.Vaults.saveVault(vault);
188
+ }
181
189
  return;
182
190
  }
183
- else if (tx.confirmations === 0) {
191
+ else if (tx.confirmations == null || tx.confirmations === 0) {
184
192
  await swap.setState(SpvVaultSwap_1.SpvVaultSwapState.SENT);
185
193
  await this.saveSwapData(swap);
186
194
  return;
@@ -223,7 +231,8 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
223
231
  }
224
232
  ]);
225
233
  for (let { obj: swap } of swaps) {
226
- await this.processPastSwap(swap);
234
+ await this.processPastSwap(swap)
235
+ .catch(e => this.swapLogger.error(swap, "processPastSwap(): Error executing watchdog function: ", e));
227
236
  }
228
237
  }
229
238
  getPricePrefetches(chainIdentifier, token, gasToken, abortController) {
@@ -314,11 +323,11 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
314
323
  * frontingFeeRate: string Fronting fee (in output token) to assign to the swap
315
324
  */
316
325
  const actualParsedBody = await req.paramReader.getParams({
317
- amount: SchemaVerifier_1.FieldTypeEnum.BigInt,
318
- gasAmount: SchemaVerifier_1.FieldTypeEnum.BigInt,
326
+ amount: SchemaVerifier_1.FieldTypeEnum.BigIntPositive,
327
+ gasAmount: SchemaVerifier_1.FieldTypeEnum.BigIntNotNegative,
319
328
  exactOut: SchemaVerifier_1.FieldTypeEnum.BooleanOptional,
320
- callerFeeRate: SchemaVerifier_1.FieldTypeEnum.BigInt,
321
- frontingFeeRate: SchemaVerifier_1.FieldTypeEnum.BigInt,
329
+ callerFeeRate: SchemaVerifier_1.FieldTypeEnum.BigIntNotNegative,
330
+ frontingFeeRate: SchemaVerifier_1.FieldTypeEnum.BigIntNotNegative,
322
331
  });
323
332
  abortController.signal.throwIfAborted();
324
333
  if (actualParsedBody == null)
@@ -328,7 +337,7 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
328
337
  };
329
338
  const inputAmountAdjustments = req.paramReader.getExistingParamsOrNull({
330
339
  amountUtxos: SchemaVerifier_1.FieldTypeEnum.AnyOptional,
331
- amountFeeRate: SchemaVerifier_1.FieldTypeEnum.NumberOptional
340
+ amountFeeRate: SchemaVerifier_1.FieldTypeEnum.NumberPositiveOptional
332
341
  });
333
342
  if (inputAmountAdjustments == null)
334
343
  throw {
@@ -615,14 +624,12 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
615
624
  };
616
625
  const pluginCheckResult = await PluginManager_1.PluginManager.onHandlePreFromBtcExecute(SwapHandler_1.SwapHandlerType.FROM_BTC_SPV, swap);
617
626
  if ((0, IPlugin_1.isQuoteThrow)(pluginCheckResult)) {
618
- const error = {
627
+ if (swap.state === SpvVaultSwap_1.SpvVaultSwapState.CREATED)
628
+ await this.removeSwapData(swap, SpvVaultSwap_1.SpvVaultSwapState.FAILED);
629
+ throw {
619
630
  code: 29999,
620
631
  msg: pluginCheckResult.message
621
632
  };
622
- if (swap.metadata != null)
623
- swap.metadata.postQuoteError = error;
624
- await this.removeSwapData(swap, SpvVaultSwap_1.SpvVaultSwapState.FAILED);
625
- throw error;
626
633
  }
627
634
  await this.Vaults.checkVaultReplacedTransactions(vault, true);
628
635
  if (swap.vaultUtxo !== vault.getLatestUtxo()) {
@@ -631,26 +638,36 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
631
638
  msg: "Vault UTXO already spent, please get another quote and try again!"
632
639
  };
633
640
  }
641
+ //Double-check the state to prevent race condition
642
+ if (swap.state !== SpvVaultSwap_1.SpvVaultSwapState.CREATED)
643
+ throw {
644
+ code: 20505,
645
+ msg: "Invalid quote ID, not found or expired!"
646
+ }; //Continues here is only synchronous code until the state change to SIGNED
647
+ const unlock = swap.lock(120);
648
+ if (!unlock)
649
+ throw {
650
+ code: 20517,
651
+ msg: "Bitcoin transaction submission already in progress, please retry later!"
652
+ };
653
+ const txId = signedTx.id;
654
+ let swapSendingSet = false;
655
+ let dataSendingSet = false;
634
656
  try {
635
657
  const btcRawTx = Buffer.from(signedTx.toBytes(true, true)).toString("hex");
636
- //Double-check the state to prevent race condition
637
- if (swap.state !== SpvVaultSwap_1.SpvVaultSwapState.CREATED) {
638
- throw {
639
- code: 20505,
640
- msg: "Invalid quote ID, not found or expired!"
641
- };
642
- }
643
658
  //Double check in-flight swap count
644
659
  this.checkTooManyInflightSwaps();
645
- swap.btcTxId = signedTx.id;
660
+ swap.btcTxId = txId;
646
661
  swap.state = SpvVaultSwap_1.SpvVaultSwapState.SIGNED;
647
662
  swap.sending = true;
663
+ swapSendingSet = true;
648
664
  await this.saveSwapData(swap);
649
665
  data.btcTx.raw = btcRawTx;
650
666
  data.sending = true;
651
667
  vault.addWithdrawal(data);
668
+ dataSendingSet = true;
652
669
  await this.Vaults.saveVault(vault);
653
- this.swapLogger.info(swap, "REST: /postQuote: BTC transaction signed, txId: " + swap.btcTxId);
670
+ this.swapLogger.info(swap, "REST: /postQuote: BTC transaction signed, txId: " + txId);
654
671
  try {
655
672
  await this.bitcoin.sendRawTransaction(btcRawTx);
656
673
  await swap.setState(SpvVaultSwap_1.SpvVaultSwapState.SENT);
@@ -666,14 +683,50 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
666
683
  }
667
684
  }
668
685
  catch (e) {
669
- data.sending = false;
670
- swap.sending = false;
671
- vault.removeWithdrawal(data);
672
- await this.Vaults.saveVault(vault);
673
- if ((0, Utils_1.isDefinedRuntimeError)(e) && swap.metadata != null)
674
- swap.metadata.postQuoteError = e;
675
- await this.removeSwapData(swap, SpvVaultSwap_1.SpvVaultSwapState.FAILED);
676
- throw e;
686
+ if (swapSendingSet)
687
+ swap.sending = false;
688
+ if (dataSendingSet)
689
+ data.sending = false;
690
+ //We only make the swap failed if the error happened in CREATED or SIGNED states
691
+ if (swap.state === SpvVaultSwap_1.SpvVaultSwapState.CREATED || swap.state === SpvVaultSwap_1.SpvVaultSwapState.SIGNED) {
692
+ try {
693
+ const fetchedBtcTx = await this.bitcoin.getWalletTransaction(txId);
694
+ if (fetchedBtcTx == null) {
695
+ if (dataSendingSet) {
696
+ vault.removeWithdrawal(data);
697
+ await this.Vaults.saveVault(vault);
698
+ }
699
+ if ((0, Utils_1.isDefinedRuntimeError)(e) && swap.metadata != null)
700
+ swap.metadata.postQuoteError = e;
701
+ await this.removeSwapData(swap, SpvVaultSwap_1.SpvVaultSwapState.FAILED);
702
+ throw e; //This will get caught locally and throw the post quote error
703
+ }
704
+ else {
705
+ //Transaction not-null set state to sent and fall through without throwing
706
+ await swap.setState(SpvVaultSwap_1.SpvVaultSwapState.SENT);
707
+ }
708
+ }
709
+ catch (getTxErr) {
710
+ if (getTxErr === e)
711
+ throw e;
712
+ //Cannot determine whether the bitcoin transaction was broadcasted or not
713
+ // do nothing here and let the watchdog handle this!
714
+ throw {
715
+ _httpStatus: 200,
716
+ code: 20001,
717
+ msg: "Transaction status unknown",
718
+ data: {
719
+ txId: swap.btcTxId
720
+ }
721
+ };
722
+ }
723
+ }
724
+ else {
725
+ throw e;
726
+ }
727
+ }
728
+ finally {
729
+ unlock();
677
730
  }
678
731
  await responseStream.writeParamsAndEnd({
679
732
  code: 20000,
@@ -246,7 +246,7 @@ class FromBtcTrusted extends SwapHandler_1.SwapHandler {
246
246
  if (swap.state !== FromBtcTrustedSwap_1.FromBtcTrustedSwapState.BTC_CONFIRMED)
247
247
  return;
248
248
  const txns = await chainInterface.txsTransfer(signer.getAddress(), swap.token, swap.adjustedOutput, swap.dstAddress);
249
- let unlock = swap.lock(30 * 1000);
249
+ let unlock = swap.lock(30);
250
250
  if (unlock == null)
251
251
  return;
252
252
  const pluginCheckResult = await PluginManager_1.PluginManager.onHandlePreFromBtcExecute(SwapHandler_1.SwapHandlerType.FROM_BTC_TRUSTED, swap);
@@ -341,7 +341,7 @@ class FromBtcTrusted extends SwapHandler_1.SwapHandler {
341
341
  await this.processPastSwap(swap, txs[0]?.tx, txs[0]?.vout);
342
342
  }
343
343
  catch (e) {
344
- this.swapLogger.error(swap, "processPastSwaps(): Error ocurred while processing swap: ", e);
344
+ this.swapLogger.error(swap, "processPastSwap(): Error executing watchdog function: ", e);
345
345
  }
346
346
  }
347
347
  }
@@ -130,8 +130,13 @@ class FromBtcLnTrusted extends SwapHandler_1.SwapHandler {
130
130
  }
131
131
  ]);
132
132
  for (let { obj: swap } of queriedData) {
133
- if (await this.processPastSwap(swap))
134
- cancelInvoices.push(swap);
133
+ try {
134
+ if (await this.processPastSwap(swap))
135
+ cancelInvoices.push(swap);
136
+ }
137
+ catch (e) {
138
+ this.swapLogger.error(swap, "processPastSwap(): Error executing watchdog function: ", e);
139
+ }
135
140
  }
136
141
  await this.cancelInvoices(cancelInvoices);
137
142
  }
@@ -206,13 +211,9 @@ class FromBtcLnTrusted extends SwapHandler_1.SwapHandler {
206
211
  }
207
212
  }).catch(e => this.swapLogger.error(invoiceData, "htlcReceived(): Error sending transfer txns", e));
208
213
  if (result == null) {
209
- //Cancel invoice
210
- await invoiceData.setState(FromBtcLnTrustedSwap_1.FromBtcLnTrustedSwapState.REFUNDED);
211
- await this.storageManager.saveData(invoice.id, null, invoiceData);
212
- await this.lightning.cancelHodlInvoice(invoice.id);
213
- this.unsubscribeInvoice(invoice.id);
214
- await this.removeSwapData(invoiceData);
215
- this.swapLogger.info(invoiceData, "htlcReceived(): transaction sending failed, refunding lightning: ", invoiceData.pr);
214
+ this.swapLogger.info(invoiceData, "htlcReceived(): transaction sending failed: ", invoiceData.pr);
215
+ //Let the swap watchdog handle this case
216
+ unlock();
216
217
  throw {
217
218
  code: 20002,
218
219
  msg: "Transaction sending failed"
@@ -230,14 +231,7 @@ class FromBtcLnTrusted extends SwapHandler_1.SwapHandler {
230
231
  if (invoiceData.isLocked())
231
232
  return;
232
233
  const txStatus = await chainInterface.getTxStatus(invoiceData.scRawTx);
233
- if (txStatus === "not_found") {
234
- //Retry
235
- invoiceData.txIds = { init: null };
236
- invoiceData.scRawTx = null;
237
- await invoiceData.setState(FromBtcLnTrustedSwap_1.FromBtcLnTrustedSwapState.RECEIVED);
238
- await this.storageManager.saveData(invoice.id, null, invoiceData);
239
- }
240
- if (txStatus === "reverted") {
234
+ if (txStatus === "reverted" || txStatus === "not_found") {
241
235
  //Cancel invoice
242
236
  await invoiceData.setState(FromBtcLnTrustedSwap_1.FromBtcLnTrustedSwapState.REFUNDED);
243
237
  await this.storageManager.saveData(invoice.id, null, invoiceData);
@@ -30,3 +30,36 @@ export declare function bigIntSorter(a: bigint, b: bigint): -1 | 0 | 1;
30
30
  */
31
31
  export declare function getAbortController(responseStream: ServerParamEncoder): AbortController;
32
32
  export declare function parsePsbt(btcTx: Transaction): BtcTx;
33
+ /**
34
+ * Returns the minimum Bitcoin block window (in blocks) for which a "fast blocks" safety
35
+ * factor is genuinely usable: the probability that N consecutive blocks are produced
36
+ * with an average interval below (blocktime / safetyFactor) is less than `probability`.
37
+ *
38
+ * Model: block arrivals are a Poisson process (i.i.d. exponential intervals), so the
39
+ * production time of N blocks is Erlang/Gamma(shape=N). A Chernoff (Cramer) bound on
40
+ * the lower tail gives the per-block large-deviation rate I(S) = ln(S) + 1/S - 1, i.e.
41
+ * P(N blocks faster than blocktime/S on average) <= exp(-N * I(S))
42
+ * which this function inverts for N. Conservative: the exact Erlang CDF already
43
+ * satisfies the bound ~25% earlier. Use for the fast-block tail (e.g. incoming LN
44
+ * HTLC expiry racing an escrow claim window).
45
+ *
46
+ * @param {number|bigint} safetyFactor Assumed max block-speed multiplier (must be > 1)
47
+ * @param {number} [probability=0.0001] Tolerated failure probability (default 0.01%)
48
+ * @returns {number} Minimum safe block window in blocks
49
+ */
50
+ export declare function getMinSafeBlockWindowFast(safetyFactor: number | bigint, probability?: number): bigint;
51
+ /**
52
+ * Slow-tail mirror of getMinSafeBlockWindowFast(): returns the minimum block window
53
+ * (in blocks) for which a "slow blocks" safety factor is genuinely usable: the
54
+ * probability that N consecutive blocks take LONGER than N * blocktime * safetyFactor
55
+ * is less than `probability`. Chernoff bound on the upper Erlang tail gives the rate
56
+ * I(S) = S - 1 - ln(S). Use when converting a wall-clock deadline into a block count
57
+ * (e.g. payout/claim timeouts that must expire before some real-world time even if
58
+ * blocks come slowly).
59
+ *
60
+ * @param {number|bigint} safetyFactor Assumed max block-slowness multiplier (must be > 1)
61
+ * @param {number} [probability=0.0001] Tolerated failure probability (default 0.01%)
62
+ * @returns {number} Minimum safe block window in blocks
63
+ */
64
+ export declare function getMinSafeBlockWindowSlow(safetyFactor: bigint | number, probability?: number): bigint;
65
+ export declare function bigIntMax(a: bigint, b: bigint): bigint;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.parsePsbt = exports.getAbortController = exports.bigIntSorter = exports.deserializeBN = exports.serializeBN = exports.HEX_REGEX = exports.expressHandlerWrapper = exports.isDefinedRuntimeError = exports.getLogger = void 0;
3
+ exports.bigIntMax = exports.getMinSafeBlockWindowSlow = exports.getMinSafeBlockWindowFast = exports.parsePsbt = exports.getAbortController = exports.bigIntSorter = exports.deserializeBN = exports.serializeBN = exports.HEX_REGEX = exports.expressHandlerWrapper = exports.isDefinedRuntimeError = exports.getLogger = void 0;
4
4
  const crypto_1 = require("crypto");
5
5
  const btc_signer_1 = require("@scure/btc-signer");
6
6
  function getLogger(prefix) {
@@ -128,3 +128,53 @@ function parsePsbt(btcTx) {
128
128
  };
129
129
  }
130
130
  exports.parsePsbt = parsePsbt;
131
+ /**
132
+ * Returns the minimum Bitcoin block window (in blocks) for which a "fast blocks" safety
133
+ * factor is genuinely usable: the probability that N consecutive blocks are produced
134
+ * with an average interval below (blocktime / safetyFactor) is less than `probability`.
135
+ *
136
+ * Model: block arrivals are a Poisson process (i.i.d. exponential intervals), so the
137
+ * production time of N blocks is Erlang/Gamma(shape=N). A Chernoff (Cramer) bound on
138
+ * the lower tail gives the per-block large-deviation rate I(S) = ln(S) + 1/S - 1, i.e.
139
+ * P(N blocks faster than blocktime/S on average) <= exp(-N * I(S))
140
+ * which this function inverts for N. Conservative: the exact Erlang CDF already
141
+ * satisfies the bound ~25% earlier. Use for the fast-block tail (e.g. incoming LN
142
+ * HTLC expiry racing an escrow claim window).
143
+ *
144
+ * @param {number|bigint} safetyFactor Assumed max block-speed multiplier (must be > 1)
145
+ * @param {number} [probability=0.0001] Tolerated failure probability (default 0.01%)
146
+ * @returns {number} Minimum safe block window in blocks
147
+ */
148
+ function getMinSafeBlockWindowFast(safetyFactor, probability = 0.0001) {
149
+ const S = Number(safetyFactor);
150
+ const rate = Math.log(S) + 1 / S - 1;
151
+ if (!(rate > 0))
152
+ throw new RangeError("safetyFactor must be > 1");
153
+ return BigInt(Math.ceil(Math.log(1 / probability) / rate));
154
+ }
155
+ exports.getMinSafeBlockWindowFast = getMinSafeBlockWindowFast;
156
+ /**
157
+ * Slow-tail mirror of getMinSafeBlockWindowFast(): returns the minimum block window
158
+ * (in blocks) for which a "slow blocks" safety factor is genuinely usable: the
159
+ * probability that N consecutive blocks take LONGER than N * blocktime * safetyFactor
160
+ * is less than `probability`. Chernoff bound on the upper Erlang tail gives the rate
161
+ * I(S) = S - 1 - ln(S). Use when converting a wall-clock deadline into a block count
162
+ * (e.g. payout/claim timeouts that must expire before some real-world time even if
163
+ * blocks come slowly).
164
+ *
165
+ * @param {number|bigint} safetyFactor Assumed max block-slowness multiplier (must be > 1)
166
+ * @param {number} [probability=0.0001] Tolerated failure probability (default 0.01%)
167
+ * @returns {number} Minimum safe block window in blocks
168
+ */
169
+ function getMinSafeBlockWindowSlow(safetyFactor, probability = 0.0001) {
170
+ const S = Number(safetyFactor);
171
+ const rate = S - 1 - Math.log(S);
172
+ if (!(rate > 0))
173
+ throw new RangeError("safetyFactor must be > 1");
174
+ return BigInt(Math.ceil(Math.log(1 / probability) / rate));
175
+ }
176
+ exports.getMinSafeBlockWindowSlow = getMinSafeBlockWindowSlow;
177
+ function bigIntMax(a, b) {
178
+ return a > b ? a : b;
179
+ }
180
+ exports.bigIntMax = bigIntMax;
@@ -118,51 +118,8 @@ class ParamDecoder {
118
118
  const resultSchema = {};
119
119
  for (let fieldName in schema) {
120
120
  const val = await this.getParam(fieldName);
121
- const type = schema[fieldName];
122
- if (typeof (type) === "function") {
123
- const result = type(val);
124
- if (result == null)
125
- return null;
126
- resultSchema[fieldName] = result;
127
- continue;
128
- }
129
- if (val == null && type >= 100) {
130
- resultSchema[fieldName] = null;
131
- continue;
132
- }
133
- if (type === SchemaVerifier_1.FieldTypeEnum.Any || type === SchemaVerifier_1.FieldTypeEnum.AnyOptional) {
134
- resultSchema[fieldName] = val;
135
- }
136
- else if (type === SchemaVerifier_1.FieldTypeEnum.Boolean || type === SchemaVerifier_1.FieldTypeEnum.BooleanOptional) {
137
- if (typeof (val) !== "boolean")
138
- return null;
139
- resultSchema[fieldName] = val;
140
- }
141
- else if (type === SchemaVerifier_1.FieldTypeEnum.Number || type === SchemaVerifier_1.FieldTypeEnum.NumberOptional) {
142
- if (typeof (val) !== "number")
143
- return null;
144
- if (isNaN(val))
145
- return null;
146
- resultSchema[fieldName] = val;
147
- }
148
- else if (type === SchemaVerifier_1.FieldTypeEnum.BigInt || type === SchemaVerifier_1.FieldTypeEnum.BigIntOptional) {
149
- const result = (0, SchemaVerifier_1.parseBigInt)(val);
150
- if (result == null)
151
- return null;
152
- resultSchema[fieldName] = result;
153
- }
154
- else if (type === SchemaVerifier_1.FieldTypeEnum.String || type === SchemaVerifier_1.FieldTypeEnum.StringOptional) {
155
- if (typeof (val) !== "string")
156
- return null;
157
- resultSchema[fieldName] = val;
158
- }
159
- else {
160
- //Probably another request schema
161
- const result = (0, SchemaVerifier_1.verifySchema)(val, type);
162
- if (result == null)
163
- return null;
164
- resultSchema[fieldName] = result;
165
- }
121
+ if (!(0, SchemaVerifier_1.verifySchemaField)(val, schema[fieldName], fieldName, resultSchema))
122
+ return null;
166
123
  }
167
124
  return resultSchema;
168
125
  }
@@ -174,47 +131,8 @@ class ParamDecoder {
174
131
  resultSchema[fieldName] = null;
175
132
  continue;
176
133
  }
177
- const type = schema[fieldName];
178
- if (typeof (type) === "function") {
179
- const result = type(val);
180
- if (result == null)
181
- return null;
182
- resultSchema[fieldName] = result;
183
- continue;
184
- }
185
- if (type === SchemaVerifier_1.FieldTypeEnum.Any || type === SchemaVerifier_1.FieldTypeEnum.AnyOptional) {
186
- resultSchema[fieldName] = val;
187
- }
188
- else if (type === SchemaVerifier_1.FieldTypeEnum.Boolean || type === SchemaVerifier_1.FieldTypeEnum.BooleanOptional) {
189
- if (typeof (val) !== "boolean")
190
- return null;
191
- resultSchema[fieldName] = val;
192
- }
193
- else if (type === SchemaVerifier_1.FieldTypeEnum.Number || type === SchemaVerifier_1.FieldTypeEnum.NumberOptional) {
194
- if (typeof (val) !== "number")
195
- return null;
196
- if (isNaN(val))
197
- return null;
198
- resultSchema[fieldName] = val;
199
- }
200
- else if (type === SchemaVerifier_1.FieldTypeEnum.BigInt || type === SchemaVerifier_1.FieldTypeEnum.BigIntOptional) {
201
- const result = (0, SchemaVerifier_1.parseBigInt)(val);
202
- if (result == null)
203
- return null;
204
- resultSchema[fieldName] = result;
205
- }
206
- else if (type === SchemaVerifier_1.FieldTypeEnum.String || type === SchemaVerifier_1.FieldTypeEnum.StringOptional) {
207
- if (typeof (val) !== "string")
208
- return null;
209
- resultSchema[fieldName] = val;
210
- }
211
- else {
212
- //Probably another request schema
213
- const result = (0, SchemaVerifier_1.verifySchema)(val, type);
214
- if (result == null)
215
- return null;
216
- resultSchema[fieldName] = result;
217
- }
134
+ if (!(0, SchemaVerifier_1.verifySchemaField)(val, schema[fieldName], fieldName, resultSchema))
135
+ return null;
218
136
  }
219
137
  return resultSchema;
220
138
  }
@@ -5,17 +5,26 @@ export declare enum FieldTypeEnum {
5
5
  Number = 2,
6
6
  BigInt = 3,
7
7
  Any = 4,
8
+ BigIntPositive = 5,
9
+ BigIntNotNegative = 6,
10
+ NumberPositive = 7,
11
+ NumberNotNegative = 8,
8
12
  StringOptional = 100,
9
13
  BooleanOptional = 101,
10
14
  NumberOptional = 102,
11
15
  BigIntOptional = 103,
12
- AnyOptional = 104
16
+ AnyOptional = 104,
17
+ BigIntPositiveOptional = 105,
18
+ BigIntNotNegativeOptional = 106,
19
+ NumberPositiveOptional = 107,
20
+ NumberNotNegativeOptional = 108
13
21
  }
14
- export type FieldType<T extends FieldTypeEnum | RequestSchema | ((val: any) => (string | boolean | number | bigint | any))> = T extends FieldTypeEnum.String ? string : T extends FieldTypeEnum.Boolean ? boolean : T extends FieldTypeEnum.Number ? number : T extends FieldTypeEnum.BigInt ? bigint : T extends FieldTypeEnum.Any ? any : T extends FieldTypeEnum.StringOptional ? string : T extends FieldTypeEnum.BooleanOptional ? boolean : T extends FieldTypeEnum.NumberOptional ? number : T extends FieldTypeEnum.BigIntOptional ? bigint : T extends FieldTypeEnum.AnyOptional ? any : T extends RequestSchema ? RequestSchemaResult<T> : T extends ((val: any) => string) ? string : T extends ((val: any) => boolean) ? boolean : T extends ((val: any) => number) ? number : T extends ((val: any) => bigint) ? bigint : T extends ((val: any) => any) ? any : never;
22
+ export type FieldType<T extends FieldTypeEnum | RequestSchema | ((val: any) => (string | boolean | number | bigint | any))> = T extends FieldTypeEnum.String ? string : T extends FieldTypeEnum.Boolean ? boolean : T extends FieldTypeEnum.Number ? number : T extends FieldTypeEnum.NumberPositive ? number : T extends FieldTypeEnum.NumberNotNegative ? number : T extends FieldTypeEnum.BigInt ? bigint : T extends FieldTypeEnum.Any ? any : T extends FieldTypeEnum.BigIntPositive ? bigint : T extends FieldTypeEnum.BigIntNotNegative ? bigint : T extends FieldTypeEnum.StringOptional ? string : T extends FieldTypeEnum.BooleanOptional ? boolean : T extends FieldTypeEnum.NumberOptional ? number : T extends FieldTypeEnum.NumberPositiveOptional ? number : T extends FieldTypeEnum.NumberNotNegativeOptional ? number : T extends FieldTypeEnum.BigIntOptional ? bigint : T extends FieldTypeEnum.AnyOptional ? any : T extends FieldTypeEnum.BigIntPositiveOptional ? bigint : T extends FieldTypeEnum.BigIntNotNegativeOptional ? bigint : T extends RequestSchema ? RequestSchemaResult<T> : T extends ((val: any) => string) ? string : T extends ((val: any) => boolean) ? boolean : T extends ((val: any) => number) ? number : T extends ((val: any) => bigint) ? bigint : T extends ((val: any) => any) ? any : never;
15
23
  export type RequestSchemaResult<T extends RequestSchema> = {
16
24
  [key in keyof T]: FieldType<T[key]>;
17
25
  };
18
26
  export type RequestSchema = {
19
27
  [fieldName: string]: FieldTypeEnum | RequestSchema | ((val: any) => any);
20
28
  };
29
+ export declare function verifySchemaField(val: any, type: FieldTypeEnum | RequestSchema | ((val: any) => any), fieldName: string, resultSchema: any): boolean;
21
30
  export declare function verifySchema<T extends RequestSchema>(req: any, schema: T): RequestSchemaResult<T>;