@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
@@ -4,7 +4,13 @@ import {FromBtcLnAutoSwap, FromBtcLnAutoSwapState} from "./FromBtcLnAutoSwap";
4
4
  import {MultichainData, SwapHandlerType} from "../../SwapHandler";
5
5
  import {ISwapPrice} from "../../../prices/ISwapPrice";
6
6
  import {ChainSwapType, ClaimEvent, InitializeEvent, RefundEvent, SwapCommitStateType, SwapData} from "@atomiqlabs/base";
7
- import {expressHandlerWrapper, getAbortController, HEX_REGEX, isDefinedRuntimeError} from "../../../utils/Utils";
7
+ import {
8
+ bigIntMax,
9
+ expressHandlerWrapper,
10
+ getAbortController, getMinSafeBlockWindowFast,
11
+ HEX_REGEX,
12
+ isDefinedRuntimeError
13
+ } from "../../../utils/Utils";
8
14
  import {PluginManager} from "../../../plugins/PluginManager";
9
15
  import {IIntermediaryStorage} from "../../../storage/IIntermediaryStorage";
10
16
  import {FieldTypeEnum, verifySchema} from "../../../utils/paramcoders/SchemaVerifier";
@@ -23,7 +29,7 @@ import {isQuoteThrow} from "../../../plugins/IPlugin";
23
29
 
24
30
  export type FromBtcLnAutoConfig = FromBtcBaseConfig & {
25
31
  invoiceTimeoutSeconds?: number,
26
- minCltv: bigint,
32
+ destinationHtlcTimeoutSeconds: bigint,
27
33
  gracePeriod: bigint,
28
34
  gasTokenMax: {[chainId: string]: bigint}
29
35
  }
@@ -54,6 +60,8 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
54
60
  readonly lightning: ILightningWallet;
55
61
  readonly LightningAssertions: LightningAssertions;
56
62
 
63
+ readonly minCltv: bigint;
64
+
57
65
  constructor(
58
66
  storageDirectory: IIntermediaryStorage<FromBtcLnAutoSwap>,
59
67
  path: string,
@@ -73,6 +81,13 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
73
81
  const {swapContract} = this.getChain(chain);
74
82
  if(!swapContract.supportsInitWithoutClaimer) this.allowedTokens[chain].clear();
75
83
  }
84
+
85
+ const numerator = (this.config.destinationHtlcTimeoutSeconds + this.config.gracePeriod) * this.config.safetyFactor;
86
+ this.minCltv = bigIntMax(
87
+ (numerator + this.config.bitcoinBlocktime - 1n)
88
+ / this.config.bitcoinBlocktime, //Ceil division
89
+ getMinSafeBlockWindowFast(this.config.safetyFactor)
90
+ );
76
91
  }
77
92
 
78
93
  protected async processPastSwap(swap: FromBtcLnAutoSwap): Promise<"REFUND" | "SETTLE" | null> {
@@ -184,10 +199,13 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
184
199
  if(unlock==null) continue;
185
200
 
186
201
  this.swapLogger.debug(refundSwap, "refundSwaps(): initiate refund of swap");
187
- await swapContract.refund(signer, refundSwap.data, true, false, {waitForConfirmation: true});
188
- this.swapLogger.info(refundSwap, "refundsSwaps(): swap refunded, invoice: "+refundSwap.pr);
189
-
190
- await refundSwap.setState(FromBtcLnAutoSwapState.REFUNDED);
202
+ try {
203
+ await swapContract.refund(signer, refundSwap.data, true, false, {waitForConfirmation: true});
204
+ this.swapLogger.info(refundSwap, "refundsSwaps(): swap refunded, invoice: "+refundSwap.pr);
205
+ await refundSwap.setState(FromBtcLnAutoSwapState.REFUNDED);
206
+ } catch (e) {
207
+ this.swapLogger.error(refundSwap, "refundSwaps(): error refunding swap: ", e);
208
+ }
191
209
  unlock();
192
210
  }
193
211
  }
@@ -229,13 +247,17 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
229
247
  ]);
230
248
 
231
249
  for(let {obj: swap} of queriedData) {
232
- switch(await this.processPastSwap(swap)) {
233
- case "SETTLE":
234
- settleInvoices.push(swap);
235
- break;
236
- case "REFUND":
237
- refundSwaps.push(swap);
238
- break;
250
+ try {
251
+ switch(await this.processPastSwap(swap)) {
252
+ case "SETTLE":
253
+ settleInvoices.push(swap);
254
+ break;
255
+ case "REFUND":
256
+ refundSwaps.push(swap);
257
+ break;
258
+ }
259
+ } catch (e) {
260
+ this.swapLogger.error(swap, "processPastSwap(): Error executing watchdog function: ", e);
239
261
  }
240
262
  }
241
263
 
@@ -322,10 +344,9 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
322
344
 
323
345
  const useToken = invoiceData.token;
324
346
 
325
- let expiryTimeout: bigint;
326
347
  try {
327
348
  //Check if HTLC expiry is long enough
328
- expiryTimeout = await this.checkHtlcExpiry(invoice);
349
+ await this.checkHtlcExpiry(invoice);
329
350
  if(invoiceData.metadata!=null) invoiceData.metadata.times.htlcTimeoutCalculated = Date.now();
330
351
  } catch (e) {
331
352
  if(isDefinedRuntimeError(e) && invoiceData.metadata!=null) invoiceData.metadata.htlcReceiveError = e;
@@ -344,7 +365,7 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
344
365
  invoiceData.amountToken,
345
366
  invoiceData.claimHash,
346
367
  0n,
347
- BigInt(Math.floor(Date.now() / 1000)) + expiryTimeout,
368
+ BigInt(Math.floor(Date.now() / 1000)) + this.config.destinationHtlcTimeoutSeconds,
348
369
  false,
349
370
  true,
350
371
  invoiceData.amountGasToken + invoiceData.claimerBounty,
@@ -524,27 +545,24 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
524
545
  *
525
546
  * @param invoice
526
547
  * @throws {DefinedRuntimeError} Will throw if HTLC expires too soon and therefore cannot be processed
527
- * @returns expiry timeout in seconds
528
548
  */
529
- private async checkHtlcExpiry(invoice: LightningNetworkInvoice): Promise<bigint> {
549
+ private async checkHtlcExpiry(invoice: LightningNetworkInvoice): Promise<void> {
530
550
  const timeout: number = this.getInvoicePaymentsTimeout(invoice);
531
551
  const current_block_height = await this.lightning.getBlockheight();
532
552
 
533
553
  const blockDelta = BigInt(timeout - current_block_height);
534
554
 
535
- const htlcExpiresTooSoon = blockDelta < this.config.minCltv;
555
+ const htlcExpiresTooSoon = blockDelta < this.minCltv;
536
556
  if(htlcExpiresTooSoon) {
537
557
  throw {
538
558
  code: 20002,
539
559
  msg: "Not enough time to reliably process the swap",
540
560
  data: {
541
- requiredDelta: this.config.minCltv.toString(10),
561
+ requiredDelta: this.minCltv.toString(10),
542
562
  actualDelta: blockDelta.toString(10)
543
563
  }
544
564
  };
545
565
  }
546
-
547
- return (this.config.minCltv * this.config.bitcoinBlocktime / this.config.safetyFactor) - this.config.gracePeriod;
548
566
  }
549
567
 
550
568
  /**
@@ -639,7 +657,7 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
639
657
  typeof(val)==="string" &&
640
658
  val.length===64 &&
641
659
  HEX_REGEX.test(val) ? val: null,
642
- amount: FieldTypeEnum.BigInt,
660
+ amount: FieldTypeEnum.BigIntPositive,
643
661
  token: (val: string) => val!=null &&
644
662
  typeof(val)==="string" &&
645
663
  this.isTokenSupported(chainIdentifier, val) ? val : null,
@@ -648,8 +666,8 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
648
666
  gasToken: (val: string) => val!=null &&
649
667
  typeof(val)==="string" &&
650
668
  chainInterface.isValidToken(val) ? val : null,
651
- gasAmount: FieldTypeEnum.BigInt,
652
- claimerBounty: FieldTypeEnum.BigInt
669
+ gasAmount: FieldTypeEnum.BigIntNotNegative,
670
+ claimerBounty: FieldTypeEnum.BigIntNotNegative
653
671
  });
654
672
  if(parsedBody==null) throw {
655
673
  code: 20100,
@@ -752,7 +770,7 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
752
770
  //Create swap
753
771
  const hodlInvoiceObj: HodlInvoiceInit = {
754
772
  description: description ?? (chainIdentifier+"-"+parsedBody.address),
755
- cltvDelta: Number(this.config.minCltv) + 5,
773
+ cltvDelta: Number(this.minCltv) + 5,
756
774
  expiresAt: Date.now()+(this.config.invoiceTimeoutSeconds*1000),
757
775
  id: parsedBody.paymentHash,
758
776
  mtokens: totalBtcInput * 1000n,
@@ -882,7 +900,7 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
882
900
  };
883
901
  }
884
902
  return {
885
- minCltv: Number(this.config.minCltv),
903
+ minCltv: Number(this.minCltv),
886
904
  invoiceTimeoutSeconds: this.config.invoiceTimeoutSeconds,
887
905
  gasTokens: mappedDict
888
906
  };
@@ -220,7 +220,8 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
220
220
  ]);
221
221
 
222
222
  for(let {obj: swap} of queriedData) {
223
- await this.processPastSwap(swap);
223
+ await this.processPastSwap(swap)
224
+ .catch(e => this.swapLogger.error(swap, "processPastSwap(): Error executing watchdog function: ", e));
224
225
  }
225
226
  }
226
227
 
@@ -338,7 +339,7 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
338
339
  * @private
339
340
  * @throws DefinedRuntimeError will throw an error in case the actual fee is higher than quoted fee
340
341
  */
341
- protected checkCalculatedTxFee(quotedSatsPerVbyte: bigint, actualSatsPerVbyte: bigint): void {
342
+ protected checkCalculatedTxFee(quotedSatsPerVbyte: number, actualSatsPerVbyte: number): void {
342
343
  const swapPaysEnoughNetworkFee = quotedSatsPerVbyte >= actualSatsPerVbyte;
343
344
  if(!swapPaysEnoughNetworkFee) throw {
344
345
  code: 90003,
@@ -367,7 +368,7 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
367
368
  if(swap.metadata!=null) swap.metadata.times.payCLTVChecked = Date.now();
368
369
 
369
370
  const satsPerVbyte = await this.bitcoin.getFeeRate();
370
- this.checkCalculatedTxFee(swap.satsPerVbyte, BigInt(satsPerVbyte));
371
+ this.checkCalculatedTxFee(swap.satsPerVbyte, satsPerVbyte);
371
372
  if(swap.metadata!=null) swap.metadata.times.payChainFee = Date.now();
372
373
 
373
374
  const signResult = await this.bitcoin.getSignedTransaction(
@@ -421,8 +422,13 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
421
422
  const isTxSent = tx!=null;
422
423
  if(!isTxSent) {
423
424
  //Reset the state to COMMITED
424
- this.swapLogger.info(swap, "processInitialized(state=BTC_SENDING): btc transaction not found, resetting to COMMITED state, txId: "+swap.txId+" address: "+swap.address);
425
- await swap.setState(ToBtcSwapState.COMMITED);
425
+ const walletTx = await this.bitcoin.getWalletTransaction(swap.txId);
426
+ if(walletTx==null) {
427
+ this.swapLogger.info(swap, "processInitialized(state=BTC_SENDING): btc transaction not found, resetting to COMMITED state, txId: "+swap.txId+" address: "+swap.address);
428
+ await swap.setState(ToBtcSwapState.COMMITED);
429
+ } else {
430
+ this.swapLogger.error(swap, "processInitialized(state=BTC_SENDING): btc transaction not found in mempool, but known to the wallet! txId: "+swap.txId+" address: "+swap.address);
431
+ }
426
432
  } else {
427
433
  this.swapLogger.info(swap, "processInitialized(state=BTC_SENDING): btc transaction found, advancing to BTC_SENT state, txId: "+swap.txId+" address: "+swap.address);
428
434
  await swap.setState(ToBtcSwapState.BTC_SENT);
@@ -534,7 +540,7 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
534
540
  * @throws {DefinedRuntimeError} will throw an error if the nonce is invalid
535
541
  */
536
542
  private checkNonceValid(nonce: bigint): void {
537
- if(nonce < 0 || nonce >= (2n ** 64n)) throw {
543
+ if(nonce < 1 || nonce >= (2n ** 64n)) throw {
538
544
  code: 20021,
539
545
  msg: "Invalid request body (nonce - cannot be parsed)"
540
546
  };
@@ -555,6 +561,10 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
555
561
  * @throws {DefinedRuntimeError} will throw an error if the confirmationTarget is out of bounds
556
562
  */
557
563
  protected checkConfirmationTarget(confirmationTarget: number): void {
564
+ if(!Number.isFinite(confirmationTarget) || !Number.isSafeInteger(confirmationTarget)) throw {
565
+ code: 20028,
566
+ msg: "Invalid request body (confirmationTarget - not whole number)"
567
+ };
558
568
  if(confirmationTarget>this.config.maxConfTarget) throw {
559
569
  code: 20023,
560
570
  msg: "Invalid request body (confirmationTarget - too high)"
@@ -572,6 +582,10 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
572
582
  * @throws {DefinedRuntimeError} will throw an error if the confirmations are out of bounds
573
583
  */
574
584
  protected checkRequiredConfirmations(confirmations: number): void {
585
+ if(!Number.isFinite(confirmations) || !Number.isSafeInteger(confirmations)) throw {
586
+ code: 20027,
587
+ msg: "Invalid request body (confirmations - not whole number)"
588
+ };
575
589
  if(confirmations>this.config.maxConfirmations) throw {
576
590
  code: 20025,
577
591
  msg: "Invalid request body (confirmations - too high)"
@@ -629,7 +643,7 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
629
643
  * @param amount
630
644
  * @throws {DefinedRuntimeError} will throw an error if there are not enough BTC funds
631
645
  */
632
- private async checkAndGetNetworkFee(address: string, amount: bigint): Promise<{ networkFee: bigint, satsPerVbyte: bigint }> {
646
+ private async checkAndGetNetworkFee(address: string, amount: bigint): Promise<{ networkFee: bigint, satsPerVbyte: number }> {
633
647
  let chainFeeResp = await this.bitcoin.estimateFee(address, Number(amount), null, this.config.networkFeeMultiplier);
634
648
 
635
649
  const hasEnoughFunds = chainFeeResp!=null;
@@ -640,7 +654,7 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
640
654
 
641
655
  return {
642
656
  networkFee: BigInt(chainFeeResp.networkFee),
643
- satsPerVbyte: BigInt(chainFeeResp.satsPerVbyte)
657
+ satsPerVbyte: chainFeeResp.satsPerVbyte
644
658
  };
645
659
  }
646
660
 
@@ -672,10 +686,10 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
672
686
  */
673
687
  const parsedBody: ToBtcRequestType = await req.paramReader.getParams({
674
688
  address: FieldTypeEnum.String,
675
- amount: FieldTypeEnum.BigInt,
676
- confirmationTarget: FieldTypeEnum.Number,
677
- confirmations: FieldTypeEnum.Number,
678
- nonce: FieldTypeEnum.BigInt,
689
+ amount: FieldTypeEnum.BigIntPositive,
690
+ confirmationTarget: FieldTypeEnum.NumberPositive,
691
+ confirmations: FieldTypeEnum.NumberPositive,
692
+ nonce: FieldTypeEnum.BigIntNotNegative,
679
693
  token: (val: string) => val!=null &&
680
694
  typeof(val)==="string" &&
681
695
  this.isTokenSupported(chainIdentifier, val) ? val : null,
@@ -799,7 +813,8 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
799
813
  data: {
800
814
  amount: amountBD.toString(10),
801
815
  address: signer.getAddress(),
802
- satsPervByte: networkFeeData.satsPerVbyte.toString(10),
816
+ satsPervByte: Math.floor(networkFeeData.satsPerVbyte).toString(10),
817
+ satsPervByteNumber: networkFeeData.satsPerVbyte,
803
818
  networkFee: networkFeeInToken.toString(10),
804
819
  swapFee: swapFeeInToken.toString(10),
805
820
  totalFee: (swapFeeInToken + networkFeeInToken).toString(10),
@@ -20,7 +20,7 @@ export class ToBtcSwapAbs<T extends SwapData = SwapData> extends ToBtcBaseSwap<T
20
20
  sending: boolean;
21
21
 
22
22
  readonly address: string;
23
- readonly satsPerVbyte: bigint;
23
+ readonly satsPerVbyte: number;
24
24
  readonly nonce: bigint;
25
25
  readonly requiredConfirmations: number;
26
26
  readonly preferedConfirmationTarget: number;
@@ -36,7 +36,7 @@ export class ToBtcSwapAbs<T extends SwapData = SwapData> extends ToBtcBaseSwap<T
36
36
  swapFeeInToken: bigint,
37
37
  networkFee: bigint,
38
38
  networkFeeInToken: bigint,
39
- satsPerVbyte: bigint,
39
+ satsPerVbyte: number,
40
40
  nonce: bigint,
41
41
  requiredConfirmations: number,
42
42
  preferedConfirmationTarget: number
@@ -51,7 +51,7 @@ export class ToBtcSwapAbs<T extends SwapData = SwapData> extends ToBtcBaseSwap<T
51
51
  swapFeeInToken?: bigint,
52
52
  networkFee?: bigint,
53
53
  networkFeeInToken?: bigint,
54
- satsPerVbyte?: bigint,
54
+ satsPerVbyte?: number,
55
55
  nonce?: bigint,
56
56
  requiredConfirmations?: number,
57
57
  preferedConfirmationTarget?: number
@@ -67,7 +67,7 @@ export class ToBtcSwapAbs<T extends SwapData = SwapData> extends ToBtcBaseSwap<T
67
67
  } else {
68
68
  super(chainIdOrObj);
69
69
  this.address = chainIdOrObj.address;
70
- this.satsPerVbyte = BigInt(chainIdOrObj.satsPerVbyte);
70
+ this.satsPerVbyte = Number(chainIdOrObj.satsPerVbyte);
71
71
  this.nonce = BigInt(chainIdOrObj.nonce);
72
72
  this.requiredConfirmations = chainIdOrObj.requiredConfirmations;
73
73
  this.preferedConfirmationTarget = chainIdOrObj.preferedConfirmationTarget;
@@ -7,10 +7,17 @@ import {
7
7
  ChainSwapType,
8
8
  ClaimEvent,
9
9
  InitializeEvent,
10
- RefundEvent, SwapCommitStateType,
10
+ RefundEvent,
11
+ SwapCommitStateType,
11
12
  SwapData
12
13
  } from "@atomiqlabs/base";
13
- import {expressHandlerWrapper, getAbortController, HEX_REGEX, isDefinedRuntimeError} from "../../../utils/Utils";
14
+ import {
15
+ expressHandlerWrapper,
16
+ getAbortController,
17
+ getMinSafeBlockWindowSlow,
18
+ HEX_REGEX,
19
+ isDefinedRuntimeError
20
+ } from "../../../utils/Utils";
14
21
  import {PluginManager} from "../../../plugins/PluginManager";
15
22
  import {IIntermediaryStorage} from "../../../storage/IIntermediaryStorage";
16
23
  import {randomBytes} from "crypto";
@@ -27,9 +34,8 @@ import {
27
34
  ProbeAndRouteResponse,
28
35
  routesMatch
29
36
  } from "../../../wallets/ILightningWallet";
30
- import { LightningAssertions } from "../../assertions/LightningAssertions";
37
+ import {LightningAssertions} from "../../assertions/LightningAssertions";
31
38
  import {isQuoteThrow} from "../../../plugins/IPlugin";
32
- import {ToBtcSwapState} from "../tobtc_abstract/ToBtcSwapAbs";
33
39
 
34
40
  export type ToBtcLnConfig = ToBtcBaseConfig & {
35
41
  routingFeeMultiplier: bigint | number,
@@ -105,6 +111,8 @@ export class ToBtcLnAbs extends ToBtcBaseSwapHandler<ToBtcLnSwapAbs, ToBtcLnSwap
105
111
  readonly lightning: ILightningWallet;
106
112
  readonly LightningAssertions: LightningAssertions;
107
113
 
114
+ readonly cltvDeltaLowerBound: bigint;
115
+
108
116
  constructor(
109
117
  storageDirectory: IIntermediaryStorage<ToBtcLnSwapAbs>,
110
118
  path: string,
@@ -123,9 +131,13 @@ export class ToBtcLnAbs extends ToBtcBaseSwapHandler<ToBtcLnSwapAbs, ToBtcLnSwap
123
131
  this.config.minLnBaseFee = this.config.minLnBaseFee || 5n;
124
132
  this.config.exactInExpiry = this.config.exactInExpiry || 10*1000;
125
133
  this.config.lnSendBitcoinBlockTimeSafetyFactorPPM = this.config.lnSendBitcoinBlockTimeSafetyFactorPPM ?? (this.config.safetyFactor * 1_000_000n);
126
- if(this.config.lnSendBitcoinBlockTimeSafetyFactorPPM <= 1_100_000n) {
127
- throw new Error("Lightning network send block safety factor set below 1.1, this is insecure!");
134
+ if(this.config.lnSendBitcoinBlockTimeSafetyFactorPPM <= 1_250_000n) {
135
+ throw new Error("Lightning network send block safety factor set below 1.25, this is insecure!");
128
136
  }
137
+
138
+ this.cltvDeltaLowerBound = getMinSafeBlockWindowSlow(
139
+ Number(this.config.lnSendBitcoinBlockTimeSafetyFactorPPM) / 1_000_000
140
+ );
129
141
  }
130
142
 
131
143
  /**
@@ -197,7 +209,8 @@ export class ToBtcLnAbs extends ToBtcBaseSwapHandler<ToBtcLnSwapAbs, ToBtcLnSwap
197
209
  ]);
198
210
 
199
211
  for(let {obj: swap} of queriedData) {
200
- await this.processPastSwap(swap);
212
+ await this.processPastSwap(swap)
213
+ .catch(e => this.swapLogger.error(swap, "processPastSwap(): Error executing watchdog function: ", e));
201
214
  }
202
215
  }
203
216
 
@@ -300,6 +313,10 @@ export class ToBtcLnAbs extends ToBtcBaseSwapHandler<ToBtcLnSwapAbs, ToBtcLnSwap
300
313
  this.swapLogger.info(invoiceData, "subscribeToPayment(): result callback, outcome: "+result.status+" invoice: "+invoiceData.pr);
301
314
  this.processPaymentResult(invoiceData, result).catch(e => this.swapLogger.error(invoiceData, "subscribeToPayment(): process payment result", e));
302
315
  this.activeSubscriptions.delete(paymentHash);
316
+ }).catch(e => {
317
+ this.swapLogger.error(invoiceData, "subscribeToPayment(): error while waiting for invoice payment: ", e);
318
+ //Delete from active subscription, the watchdog will re-subscribe later
319
+ this.activeSubscriptions.delete(paymentHash);
303
320
  });
304
321
  this.swapLogger.info(invoiceData, "subscribeToPayment(): subscribe to payment outcome, invoice: "+invoiceData.pr);
305
322
 
@@ -329,6 +346,10 @@ export class ToBtcLnAbs extends ToBtcBaseSwapHandler<ToBtcLnSwapAbs, ToBtcLnSwap
329
346
  const maxFee = swap.quotedNetworkFee;
330
347
  const maxUsableCLTVdelta = (expiryTimestamp - currentTimestamp - this.config.gracePeriod)
331
348
  / (this.config.bitcoinBlocktime * this.config.lnSendBitcoinBlockTimeSafetyFactorPPM / 1_000_000n);
349
+ if(maxUsableCLTVdelta < this.cltvDeltaLowerBound) throw {
350
+ code: 90008,
351
+ msg: "Calculated CLTV delta is too low for current safety factor!"
352
+ }
332
353
 
333
354
  //Initiate payment
334
355
  this.swapLogger.info(swap, "sendLightningPayment(): paying lightning network invoice,"+
@@ -418,6 +439,9 @@ export class ToBtcLnAbs extends ToBtcBaseSwapHandler<ToBtcLnSwapAbs, ToBtcLnSwap
418
439
  } else throw e;
419
440
  }
420
441
 
442
+ const unlock = swap.lock(120);
443
+ if(unlock==null) return;
444
+
421
445
  await swap.setState(ToBtcLnSwapState.COMMITED);
422
446
  await this.saveSwapData(swap);
423
447
  try {
@@ -426,10 +450,22 @@ export class ToBtcLnAbs extends ToBtcBaseSwapHandler<ToBtcLnSwapAbs, ToBtcLnSwap
426
450
  this.swapLogger.error(swap, "processInitialized(): lightning payment error", e);
427
451
  if(isDefinedRuntimeError(e)) {
428
452
  if(swap.metadata!=null) swap.metadata.payError = e;
429
- await swap.setState(ToBtcLnSwapState.NON_PAYABLE);
430
- await this.saveSwapData(swap);
431
- return;
453
+ const payment = await this.lightning.getPayment(swap.lnPaymentHash);
454
+ if(!unlock()) return;
455
+ if(payment==null || payment.status==="failed") {
456
+ if(swap.state as ToBtcLnSwapState === ToBtcLnSwapState.COMMITED)
457
+ await swap.setState(ToBtcLnSwapState.NON_PAYABLE);
458
+ await this.saveSwapData(swap);
459
+ return;
460
+ } else if(payment.status==="confirmed") {
461
+ await this.processPaymentResult(swap, payment);
462
+ return;
463
+ } else {
464
+ //Fall-through to subscribe
465
+ }
432
466
  } else throw e;
467
+ } finally {
468
+ unlock();
433
469
  }
434
470
  this.subscribeToPayment(swap);
435
471
  return;
@@ -571,6 +607,10 @@ export class ToBtcLnAbs extends ToBtcBaseSwapHandler<ToBtcLnSwapAbs, ToBtcLnSwap
571
607
  }> {
572
608
  const maxUsableCLTV: bigint = (expiryTimestamp - currentTimestamp - this.config.gracePeriod)
573
609
  / (this.config.bitcoinBlocktime * this.config.lnSendBitcoinBlockTimeSafetyFactorPPM / 1_000_000n);
610
+ if(maxUsableCLTV < this.cltvDeltaLowerBound) throw {
611
+ code: 20002,
612
+ msg: "Cannot route the payment (calculated CLTV delta too short - increase timeout)!"
613
+ };
574
614
 
575
615
  const blockHeight = await this.lightning.getBlockheight();
576
616
  abortSignal.throwIfAborted();
@@ -843,8 +883,8 @@ export class ToBtcLnAbs extends ToBtcBaseSwapHandler<ToBtcLnSwapAbs, ToBtcLnSwap
843
883
  */
844
884
  const parsedBody: ToBtcLnRequestType = await req.paramReader.getParams({
845
885
  pr: FieldTypeEnum.String,
846
- maxFee: FieldTypeEnum.BigInt,
847
- expiryTimestamp: FieldTypeEnum.BigInt,
886
+ maxFee: FieldTypeEnum.BigIntPositive,
887
+ expiryTimestamp: FieldTypeEnum.BigIntPositive,
848
888
  token: (val: string) => val!=null &&
849
889
  typeof(val)==="string" &&
850
890
  this.isTokenSupported(chainIdentifier, val) ? val : null,
@@ -852,7 +892,7 @@ export class ToBtcLnAbs extends ToBtcBaseSwapHandler<ToBtcLnSwapAbs, ToBtcLnSwap
852
892
  typeof(val)==="string" &&
853
893
  chainInterface.isValidAddress(val, true) ? val : null,
854
894
  exactIn: FieldTypeEnum.BooleanOptional,
855
- amount: FieldTypeEnum.BigIntOptional
895
+ amount: FieldTypeEnum.BigIntPositiveOptional
856
896
  });
857
897
  if (parsedBody==null) {
858
898
  throw {
@@ -1078,6 +1118,16 @@ export class ToBtcLnAbs extends ToBtcBaseSwapHandler<ToBtcLnSwapAbs, ToBtcLnSwap
1078
1118
  };
1079
1119
 
1080
1120
  if(data.state===ToBtcLnSwapState.NON_PAYABLE) {
1121
+ if(data.payInitiated) {
1122
+ //Check that the ln payment is indeed non-existent or failed
1123
+ const lnPayment = await this.lightning.getPayment(data.lnPaymentHash);
1124
+ if(lnPayment!=null && lnPayment.status!=="failed") throw {
1125
+ _httpStatus: 200,
1126
+ code: 20008,
1127
+ msg: "Payment in-flight"
1128
+ };
1129
+ }
1130
+
1081
1131
  const refundSigData = await swapContract.getRefundSignature(signer, data.data, this.config.refundAuthorizationTimeout);
1082
1132
 
1083
1133
  //Double check the state after promise result