@atomiqlabs/lp-lib 17.5.3 → 17.6.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 (28) hide show
  1. package/dist/swaps/assertions/FromBtcAmountAssertions.js +2 -2
  2. package/dist/swaps/escrow/frombtc_abstract/FromBtcAbs.js +16 -6
  3. package/dist/swaps/escrow/frombtcln_abstract/FromBtcLnAbs.d.ts +2 -2
  4. package/dist/swaps/escrow/frombtcln_abstract/FromBtcLnAbs.js +34 -24
  5. package/dist/swaps/escrow/frombtcln_autoinit/FromBtcLnAuto.d.ts +2 -2
  6. package/dist/swaps/escrow/frombtcln_autoinit/FromBtcLnAuto.js +30 -19
  7. package/dist/swaps/escrow/tobtc_abstract/ToBtcAbs.d.ts +1 -1
  8. package/dist/swaps/escrow/tobtc_abstract/ToBtcAbs.js +12 -5
  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.js +38 -4
  12. package/dist/swaps/spv_vault_swap/SpvVaultSwapHandler.js +35 -15
  13. package/dist/swaps/trusted/frombtc_trusted/FromBtcTrusted.js +2 -2
  14. package/dist/swaps/trusted/frombtcln_trusted/FromBtcLnTrusted.js +7 -2
  15. package/dist/utils/Utils.d.ts +33 -0
  16. package/dist/utils/Utils.js +51 -1
  17. package/package.json +1 -1
  18. package/src/swaps/assertions/FromBtcAmountAssertions.ts +2 -2
  19. package/src/swaps/escrow/frombtc_abstract/FromBtcAbs.ts +14 -5
  20. package/src/swaps/escrow/frombtcln_abstract/FromBtcLnAbs.ts +46 -29
  21. package/src/swaps/escrow/frombtcln_autoinit/FromBtcLnAuto.ts +42 -24
  22. package/src/swaps/escrow/tobtc_abstract/ToBtcAbs.ts +13 -7
  23. package/src/swaps/escrow/tobtc_abstract/ToBtcSwapAbs.ts +4 -4
  24. package/src/swaps/escrow/tobtcln_abstract/ToBtcLnAbs.ts +37 -7
  25. package/src/swaps/spv_vault_swap/SpvVaultSwapHandler.ts +36 -18
  26. package/src/swaps/trusted/frombtc_trusted/FromBtcTrusted.ts +25 -2
  27. package/src/swaps/trusted/frombtcln_trusted/FromBtcLnTrusted.ts +5 -1
  28. package/src/utils/Utils.ts +48 -0
@@ -124,8 +124,8 @@ class FromBtcAmountAssertions extends AmountAssertions_1.AmountAssertions {
124
124
  const tooLow = amountBD < (min * 95n / 100n);
125
125
  const tooHigh = amountBD > (max * 105n / 100n);
126
126
  if (tooLow || tooHigh) {
127
- const adjustedMin = min * (1000000n - fees.feePPM) / (1000000n - fees.baseFee);
128
- const adjustedMax = max * (1000000n - fees.feePPM) / (1000000n - fees.baseFee);
127
+ const adjustedMin = min * (1000000n - fees.feePPM) / 1000000n - fees.baseFee;
128
+ const adjustedMax = max * (1000000n - fees.feePPM) / 1000000n - fees.baseFee;
129
129
  const minIn = await this.swapPricing.getFromBtcSwapAmount(adjustedMin, requestedAmount.token, chainIdentifier, null, requestedAmount.pricePrefetch);
130
130
  const maxIn = await this.swapPricing.getFromBtcSwapAmount(adjustedMax, requestedAmount.token, chainIdentifier, null, requestedAmount.pricePrefetch);
131
131
  throw {
@@ -92,8 +92,13 @@ class FromBtcAbs extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
92
92
  ]);
93
93
  const refundSwaps = [];
94
94
  for (let { obj: swap } of queriedData) {
95
- if (await this.processPastSwap(swap))
96
- refundSwaps.push(swap);
95
+ try {
96
+ if (await this.processPastSwap(swap))
97
+ refundSwaps.push(swap);
98
+ }
99
+ catch (e) {
100
+ this.swapLogger.error(swap, "processPastSwap(): Error executing watchdog function: ", e);
101
+ }
97
102
  }
98
103
  await this.refundSwaps(refundSwaps);
99
104
  }
@@ -110,10 +115,15 @@ class FromBtcAbs extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
110
115
  if (unlock == null)
111
116
  continue;
112
117
  this.swapLogger.debug(refundSwap, "refundSwaps(): initiate refund of swap");
113
- await swapContract.refund(signer, refundSwap.data, true, false, { waitForConfirmation: true });
114
- this.swapLogger.info(refundSwap, "refundSwaps(): swap refunded, address: " + refundSwap.address);
115
- //The swap should be removed by the event handler
116
- await refundSwap.setState(FromBtcSwapAbs_1.FromBtcSwapState.REFUNDED);
118
+ try {
119
+ await swapContract.refund(signer, refundSwap.data, true, false, { waitForConfirmation: true });
120
+ this.swapLogger.info(refundSwap, "refundSwaps(): swap refunded, address: " + refundSwap.address);
121
+ //The swap should be removed by the event handler
122
+ await refundSwap.setState(FromBtcSwapAbs_1.FromBtcSwapState.REFUNDED);
123
+ }
124
+ catch (e) {
125
+ this.swapLogger.error(refundSwap, "refundSwaps(): error refunding swap: ", e);
126
+ }
117
127
  unlock();
118
128
  }
119
129
  }
@@ -9,7 +9,7 @@ import { ILightningWallet } from "../../../wallets/ILightningWallet";
9
9
  import { LightningAssertions } from "../../assertions/LightningAssertions";
10
10
  export type FromBtcLnConfig = FromBtcBaseConfig & {
11
11
  invoiceTimeoutSeconds?: number;
12
- minCltv: bigint;
12
+ destinationHtlcTimeoutSeconds: bigint;
13
13
  gracePeriod: bigint;
14
14
  };
15
15
  export type FromBtcLnRequestType = {
@@ -30,6 +30,7 @@ export declare class FromBtcLnAbs extends FromBtcBaseSwapHandler<FromBtcLnSwapAb
30
30
  readonly config: FromBtcLnConfig;
31
31
  readonly lightning: ILightningWallet;
32
32
  readonly LightningAssertions: LightningAssertions;
33
+ readonly minCltv: bigint;
33
34
  constructor(storageDirectory: IIntermediaryStorage<FromBtcLnSwapAbs>, path: string, chains: MultichainData, lightning: ILightningWallet, swapPricing: ISwapPrice, config: FromBtcLnConfig);
34
35
  protected processPastSwap(swap: FromBtcLnSwapAbs): Promise<"REFUND" | "SETTLE" | "CANCEL" | null>;
35
36
  protected refundSwaps(refundSwaps: FromBtcLnSwapAbs[]): Promise<void>;
@@ -90,7 +91,6 @@ export declare class FromBtcLnAbs extends FromBtcBaseSwapHandler<FromBtcLnSwapAb
90
91
  * @param blockheightPrefetch
91
92
  * @param signal
92
93
  * @throws {DefinedRuntimeError} Will throw if HTLC expires too soon and therefore cannot be processed
93
- * @returns expiry timeout in seconds
94
94
  */
95
95
  private checkHtlcExpiry;
96
96
  /**
@@ -25,6 +25,10 @@ class FromBtcLnAbs extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
25
25
  this.config.invoiceTimeoutSeconds = this.config.invoiceTimeoutSeconds || 90;
26
26
  this.lightning = lightning;
27
27
  this.LightningAssertions = new LightningAssertions_1.LightningAssertions(this.logger, lightning);
28
+ const numerator = (this.config.destinationHtlcTimeoutSeconds + this.config.gracePeriod) * this.config.safetyFactor;
29
+ this.minCltv = (0, Utils_1.bigIntMax)((numerator + this.config.bitcoinBlocktime - 1n)
30
+ / this.config.bitcoinBlocktime, //Ceil division
31
+ (0, Utils_1.getMinSafeBlockWindowFast)(this.config.safetyFactor));
28
32
  }
29
33
  async processPastSwap(swap) {
30
34
  const { swapContract, signer } = this.getChain(swap.chainIdentifier);
@@ -116,9 +120,14 @@ class FromBtcLnAbs extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
116
120
  if (unlock == null)
117
121
  continue;
118
122
  this.swapLogger.debug(refundSwap, "refundSwaps(): initiate refund of swap");
119
- await swapContract.refund(signer, refundSwap.data, true, false, { waitForConfirmation: true });
120
- this.swapLogger.info(refundSwap, "refundsSwaps(): swap refunded, invoice: " + refundSwap.pr);
121
- await refundSwap.setState(FromBtcLnSwapAbs_1.FromBtcLnSwapState.REFUNDED);
123
+ try {
124
+ await swapContract.refund(signer, refundSwap.data, true, false, { waitForConfirmation: true });
125
+ this.swapLogger.info(refundSwap, "refundsSwaps(): swap refunded, invoice: " + refundSwap.pr);
126
+ await refundSwap.setState(FromBtcLnSwapAbs_1.FromBtcLnSwapState.REFUNDED);
127
+ }
128
+ catch (e) {
129
+ this.swapLogger.error(refundSwap, "refundSwaps(): error refunding swap: ", e);
130
+ }
122
131
  unlock();
123
132
  }
124
133
  }
@@ -170,16 +179,21 @@ class FromBtcLnAbs extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
170
179
  }
171
180
  ]);
172
181
  for (let { obj: swap } of queriedData) {
173
- switch (await this.processPastSwap(swap)) {
174
- case "CANCEL":
175
- cancelInvoices.push(swap);
176
- break;
177
- case "SETTLE":
178
- settleInvoices.push(swap);
179
- break;
180
- case "REFUND":
181
- refundSwaps.push(swap);
182
- break;
182
+ try {
183
+ switch (await this.processPastSwap(swap)) {
184
+ case "CANCEL":
185
+ cancelInvoices.push(swap);
186
+ break;
187
+ case "SETTLE":
188
+ settleInvoices.push(swap);
189
+ break;
190
+ case "REFUND":
191
+ refundSwaps.push(swap);
192
+ break;
193
+ }
194
+ }
195
+ catch (e) {
196
+ this.swapLogger.error(swap, "processPastSwap(): Error executing watchdog function: ", e);
183
197
  }
184
198
  }
185
199
  await this.refundSwaps(refundSwaps);
@@ -261,14 +275,13 @@ class FromBtcLnAbs extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
261
275
  const balancePrefetch = this.getBalancePrefetch(invoiceData.chainIdentifier, useToken, abortController);
262
276
  const blockheightPrefetch = this.getBlockheightPrefetch(abortController);
263
277
  const signDataPrefetchPromise = this.getSignDataPrefetch(invoiceData.chainIdentifier, abortController);
264
- let expiryTimeout;
265
278
  try {
266
279
  //Check if we have enough liquidity to proceed
267
280
  await this.checkBalance(escrowAmount, balancePrefetch, abortController.signal);
268
281
  if (invoiceData.metadata != null)
269
282
  invoiceData.metadata.times.htlcBalanceChecked = Date.now();
270
283
  //Check if HTLC expiry is long enough
271
- expiryTimeout = await this.checkHtlcExpiry(invoice, blockheightPrefetch, abortController.signal);
284
+ await this.checkHtlcExpiry(invoice, blockheightPrefetch, abortController.signal);
272
285
  if (invoiceData.metadata != null)
273
286
  invoiceData.metadata.times.htlcTimeoutCalculated = Date.now();
274
287
  }
@@ -283,7 +296,7 @@ class FromBtcLnAbs extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
283
296
  }
284
297
  const { swapContract, signer } = this.getChain(invoiceData.chainIdentifier);
285
298
  //Create real swap data
286
- const payInvoiceObject = await swapContract.createSwapData(base_1.ChainSwapType.HTLC, signer.getAddress(), invoiceData.claimer, useToken, escrowAmount, invoiceData.claimHash, 0n, BigInt(Math.floor(Date.now() / 1000)) + expiryTimeout, false, true, invoiceData.securityDeposit, 0n, invoiceData.depositToken);
299
+ const payInvoiceObject = await swapContract.createSwapData(base_1.ChainSwapType.HTLC, signer.getAddress(), invoiceData.claimer, useToken, escrowAmount, invoiceData.claimHash, 0n, BigInt(Math.floor(Date.now() / 1000)) + this.config.destinationHtlcTimeoutSeconds, false, true, invoiceData.securityDeposit, 0n, invoiceData.depositToken);
287
300
  abortController.signal.throwIfAborted();
288
301
  if (invoiceData.metadata != null)
289
302
  invoiceData.metadata.times.htlcSwapCreated = Date.now();
@@ -402,25 +415,23 @@ class FromBtcLnAbs extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
402
415
  * @param blockheightPrefetch
403
416
  * @param signal
404
417
  * @throws {DefinedRuntimeError} Will throw if HTLC expires too soon and therefore cannot be processed
405
- * @returns expiry timeout in seconds
406
418
  */
407
419
  async checkHtlcExpiry(invoice, blockheightPrefetch, signal) {
408
420
  const timeout = this.getInvoicePaymentsTimeout(invoice);
409
421
  const current_block_height = await blockheightPrefetch;
410
422
  signal.throwIfAborted();
411
423
  const blockDelta = BigInt(timeout - current_block_height);
412
- const htlcExpiresTooSoon = blockDelta < this.config.minCltv;
424
+ const htlcExpiresTooSoon = blockDelta < this.minCltv;
413
425
  if (htlcExpiresTooSoon) {
414
426
  throw {
415
427
  code: 20002,
416
428
  msg: "Not enough time to reliably process the swap",
417
429
  data: {
418
- requiredDelta: this.config.minCltv.toString(10),
430
+ requiredDelta: this.minCltv.toString(10),
419
431
  actualDelta: blockDelta.toString(10)
420
432
  }
421
433
  };
422
434
  }
423
- return (this.config.minCltv * this.config.bitcoinBlocktime / this.config.safetyFactor) - this.config.gracePeriod;
424
435
  }
425
436
  /**
426
437
  * Cancels the swap (CANCELED state) & also cancels the LN invoice (including all pending HTLCs)
@@ -568,7 +579,7 @@ class FromBtcLnAbs extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
568
579
  //Create swap
569
580
  const hodlInvoiceObj = {
570
581
  description: description ?? (chainIdentifier + "-" + parsedBody.address),
571
- cltvDelta: Number(this.config.minCltv) + 5,
582
+ cltvDelta: Number(this.minCltv) + 5,
572
583
  expiresAt: Date.now() + (this.config.invoiceTimeoutSeconds * 1000),
573
584
  id: parsedBody.paymentHash,
574
585
  mtokens: amountBD * 1000n,
@@ -580,8 +591,7 @@ class FromBtcLnAbs extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
580
591
  metadata.times.invoiceCreated = Date.now();
581
592
  metadata.invoiceResponse = { ...hodlInvoice };
582
593
  //Pre-compute the security deposit
583
- const expiryTimeout = (this.config.minCltv * this.config.bitcoinBlocktime / this.config.safetyFactor) - this.config.gracePeriod;
584
- const totalSecurityDeposit = await this.getSecurityDeposit(chainIdentifier, amountBD, swapFee, expiryTimeout, baseSDPromise, depositToken, depositTokenPricePrefetchPromise, fees, abortController.signal, metadata);
594
+ const totalSecurityDeposit = await this.getSecurityDeposit(chainIdentifier, amountBD, swapFee, this.config.destinationHtlcTimeoutSeconds, baseSDPromise, depositToken, depositTokenPricePrefetchPromise, fees, abortController.signal, metadata);
585
595
  metadata.times.securityDepositCalculated = Date.now();
586
596
  const createdSwap = new FromBtcLnSwapAbs_1.FromBtcLnSwapAbs(chainIdentifier, hodlInvoice.request, parsedBody.paymentHash, hodlInvoice.mtokens, swapFee, swapFeeInToken, parsedBody.address, useToken, totalInToken, swapContract.getHashForHtlc(Buffer.from(parsedBody.paymentHash, "hex")).toString("hex"), totalSecurityDeposit, depositToken);
587
597
  metadata.times.swapCreated = Date.now();
@@ -712,7 +722,7 @@ class FromBtcLnAbs extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
712
722
  }
713
723
  getInfoData() {
714
724
  return {
715
- minCltv: Number(this.config.minCltv)
725
+ minCltv: Number(this.minCltv)
716
726
  };
717
727
  }
718
728
  }
@@ -9,7 +9,7 @@ import { ILightningWallet } from "../../../wallets/ILightningWallet";
9
9
  import { LightningAssertions } from "../../assertions/LightningAssertions";
10
10
  export type FromBtcLnAutoConfig = FromBtcBaseConfig & {
11
11
  invoiceTimeoutSeconds?: number;
12
- minCltv: bigint;
12
+ destinationHtlcTimeoutSeconds: bigint;
13
13
  gracePeriod: bigint;
14
14
  gasTokenMax: {
15
15
  [chainId: string]: bigint;
@@ -37,6 +37,7 @@ export declare class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoS
37
37
  readonly config: FromBtcLnAutoConfig;
38
38
  readonly lightning: ILightningWallet;
39
39
  readonly LightningAssertions: LightningAssertions;
40
+ readonly minCltv: bigint;
40
41
  constructor(storageDirectory: IIntermediaryStorage<FromBtcLnAutoSwap>, path: string, chains: MultichainData, lightning: ILightningWallet, swapPricing: ISwapPrice, config: FromBtcLnAutoConfig);
41
42
  protected processPastSwap(swap: FromBtcLnAutoSwap): Promise<"REFUND" | "SETTLE" | null>;
42
43
  protected refundSwaps(refundSwaps: FromBtcLnAutoSwap[]): Promise<void>;
@@ -95,7 +96,6 @@ export declare class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoS
95
96
  *
96
97
  * @param invoice
97
98
  * @throws {DefinedRuntimeError} Will throw if HTLC expires too soon and therefore cannot be processed
98
- * @returns expiry timeout in seconds
99
99
  */
100
100
  private checkHtlcExpiry;
101
101
  /**
@@ -32,6 +32,10 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
32
32
  if (!swapContract.supportsInitWithoutClaimer)
33
33
  this.allowedTokens[chain].clear();
34
34
  }
35
+ const numerator = (this.config.destinationHtlcTimeoutSeconds + this.config.gracePeriod) * this.config.safetyFactor;
36
+ this.minCltv = (0, Utils_1.bigIntMax)((numerator + this.config.bitcoinBlocktime - 1n)
37
+ / this.config.bitcoinBlocktime, //Ceil division
38
+ (0, Utils_1.getMinSafeBlockWindowFast)(this.config.safetyFactor));
35
39
  }
36
40
  async processPastSwap(swap) {
37
41
  const { swapContract, signer } = this.getChain(swap.chainIdentifier);
@@ -134,9 +138,14 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
134
138
  if (unlock == null)
135
139
  continue;
136
140
  this.swapLogger.debug(refundSwap, "refundSwaps(): initiate refund of swap");
137
- await swapContract.refund(signer, refundSwap.data, true, false, { waitForConfirmation: true });
138
- this.swapLogger.info(refundSwap, "refundsSwaps(): swap refunded, invoice: " + refundSwap.pr);
139
- await refundSwap.setState(FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.REFUNDED);
141
+ try {
142
+ await swapContract.refund(signer, refundSwap.data, true, false, { waitForConfirmation: true });
143
+ this.swapLogger.info(refundSwap, "refundsSwaps(): swap refunded, invoice: " + refundSwap.pr);
144
+ await refundSwap.setState(FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.REFUNDED);
145
+ }
146
+ catch (e) {
147
+ this.swapLogger.error(refundSwap, "refundSwaps(): error refunding swap: ", e);
148
+ }
140
149
  unlock();
141
150
  }
142
151
  }
@@ -174,13 +183,18 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
174
183
  }
175
184
  ]);
176
185
  for (let { obj: swap } of queriedData) {
177
- switch (await this.processPastSwap(swap)) {
178
- case "SETTLE":
179
- settleInvoices.push(swap);
180
- break;
181
- case "REFUND":
182
- refundSwaps.push(swap);
183
- break;
186
+ try {
187
+ switch (await this.processPastSwap(swap)) {
188
+ case "SETTLE":
189
+ settleInvoices.push(swap);
190
+ break;
191
+ case "REFUND":
192
+ refundSwaps.push(swap);
193
+ break;
194
+ }
195
+ }
196
+ catch (e) {
197
+ this.swapLogger.error(swap, "processPastSwap(): Error executing watchdog function: ", e);
184
198
  }
185
199
  }
186
200
  await this.refundSwaps(refundSwaps);
@@ -257,10 +271,9 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
257
271
  if (invoiceData.metadata != null)
258
272
  invoiceData.metadata.times.htlcReceived = Date.now();
259
273
  const useToken = invoiceData.token;
260
- let expiryTimeout;
261
274
  try {
262
275
  //Check if HTLC expiry is long enough
263
- expiryTimeout = await this.checkHtlcExpiry(invoice);
276
+ await this.checkHtlcExpiry(invoice);
264
277
  if (invoiceData.metadata != null)
265
278
  invoiceData.metadata.times.htlcTimeoutCalculated = Date.now();
266
279
  }
@@ -273,7 +286,7 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
273
286
  }
274
287
  const { swapContract, signer } = this.getChain(invoiceData.chainIdentifier);
275
288
  //Create real swap data
276
- const swapData = await swapContract.createSwapData(base_1.ChainSwapType.HTLC, signer.getAddress(), invoiceData.claimer, useToken, invoiceData.amountToken, invoiceData.claimHash, 0n, BigInt(Math.floor(Date.now() / 1000)) + expiryTimeout, false, true, invoiceData.amountGasToken + invoiceData.claimerBounty, invoiceData.claimerBounty, invoiceData.gasToken);
289
+ const swapData = await swapContract.createSwapData(base_1.ChainSwapType.HTLC, signer.getAddress(), invoiceData.claimer, useToken, invoiceData.amountToken, invoiceData.claimHash, 0n, BigInt(Math.floor(Date.now() / 1000)) + this.config.destinationHtlcTimeoutSeconds, false, true, invoiceData.amountGasToken + invoiceData.claimerBounty, invoiceData.claimerBounty, invoiceData.gasToken);
277
290
  if (invoiceData.metadata != null)
278
291
  invoiceData.metadata.times.htlcSwapCreated = Date.now();
279
292
  //Important to prevent race condition and issuing 2 signed init messages at the same time
@@ -440,24 +453,22 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
440
453
  *
441
454
  * @param invoice
442
455
  * @throws {DefinedRuntimeError} Will throw if HTLC expires too soon and therefore cannot be processed
443
- * @returns expiry timeout in seconds
444
456
  */
445
457
  async checkHtlcExpiry(invoice) {
446
458
  const timeout = this.getInvoicePaymentsTimeout(invoice);
447
459
  const current_block_height = await this.lightning.getBlockheight();
448
460
  const blockDelta = BigInt(timeout - current_block_height);
449
- const htlcExpiresTooSoon = blockDelta < this.config.minCltv;
461
+ const htlcExpiresTooSoon = blockDelta < this.minCltv;
450
462
  if (htlcExpiresTooSoon) {
451
463
  throw {
452
464
  code: 20002,
453
465
  msg: "Not enough time to reliably process the swap",
454
466
  data: {
455
- requiredDelta: this.config.minCltv.toString(10),
467
+ requiredDelta: this.minCltv.toString(10),
456
468
  actualDelta: blockDelta.toString(10)
457
469
  }
458
470
  };
459
471
  }
460
- return (this.config.minCltv * this.config.bitcoinBlocktime / this.config.safetyFactor) - this.config.gracePeriod;
461
472
  }
462
473
  /**
463
474
  * Cancels the swap (CANCELED state) & also cancels the LN invoice (including all pending HTLCs)
@@ -630,7 +641,7 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
630
641
  //Create swap
631
642
  const hodlInvoiceObj = {
632
643
  description: description ?? (chainIdentifier + "-" + parsedBody.address),
633
- cltvDelta: Number(this.config.minCltv) + 5,
644
+ cltvDelta: Number(this.minCltv) + 5,
634
645
  expiresAt: Date.now() + (this.config.invoiceTimeoutSeconds * 1000),
635
646
  id: parsedBody.paymentHash,
636
647
  mtokens: totalBtcInput * 1000n,
@@ -721,7 +732,7 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
721
732
  };
722
733
  }
723
734
  return {
724
- minCltv: Number(this.config.minCltv),
735
+ minCltv: Number(this.minCltv),
725
736
  invoiceTimeoutSeconds: this.config.invoiceTimeoutSeconds,
726
737
  gasTokens: mappedDict
727
738
  };
@@ -91,7 +91,7 @@ export declare class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSw
91
91
  * @private
92
92
  * @throws DefinedRuntimeError will throw an error in case the actual fee is higher than quoted fee
93
93
  */
94
- protected checkCalculatedTxFee(quotedSatsPerVbyte: bigint, actualSatsPerVbyte: bigint): void;
94
+ protected checkCalculatedTxFee(quotedSatsPerVbyte: number, actualSatsPerVbyte: number): void;
95
95
  /**
96
96
  * Sends a bitcoin transaction to payout BTC for a swap
97
97
  *
@@ -149,7 +149,8 @@ class ToBtcAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
149
149
  }
150
150
  ]);
151
151
  for (let { obj: swap } of queriedData) {
152
- await this.processPastSwap(swap);
152
+ await this.processPastSwap(swap)
153
+ .catch(e => this.swapLogger.error(swap, "processPastSwap(): Error executing watchdog function: ", e));
153
154
  }
154
155
  }
155
156
  async processBtcTx(swap, tx) {
@@ -285,7 +286,7 @@ class ToBtcAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
285
286
  if (swap.metadata != null)
286
287
  swap.metadata.times.payCLTVChecked = Date.now();
287
288
  const satsPerVbyte = await this.bitcoin.getFeeRate();
288
- this.checkCalculatedTxFee(swap.satsPerVbyte, BigInt(satsPerVbyte));
289
+ this.checkCalculatedTxFee(swap.satsPerVbyte, satsPerVbyte);
289
290
  if (swap.metadata != null)
290
291
  swap.metadata.times.payChainFee = Date.now();
291
292
  const signResult = await this.bitcoin.getSignedTransaction(swap.address, Number(swap.amount), satsPerVbyte, swap.nonce, Number(swap.satsPerVbyte));
@@ -332,8 +333,14 @@ class ToBtcAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
332
333
  const isTxSent = tx != null;
333
334
  if (!isTxSent) {
334
335
  //Reset the state to COMMITED
335
- this.swapLogger.info(swap, "processInitialized(state=BTC_SENDING): btc transaction not found, resetting to COMMITED state, txId: " + swap.txId + " address: " + swap.address);
336
- await swap.setState(ToBtcSwapAbs_1.ToBtcSwapState.COMMITED);
336
+ const walletTx = await this.bitcoin.getWalletTransaction(swap.txId);
337
+ if (walletTx == null) {
338
+ this.swapLogger.info(swap, "processInitialized(state=BTC_SENDING): btc transaction not found, resetting to COMMITED state, txId: " + swap.txId + " address: " + swap.address);
339
+ await swap.setState(ToBtcSwapAbs_1.ToBtcSwapState.COMMITED);
340
+ }
341
+ else {
342
+ 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);
343
+ }
337
344
  }
338
345
  else {
339
346
  this.swapLogger.info(swap, "processInitialized(state=BTC_SENDING): btc transaction found, advancing to BTC_SENT state, txId: " + swap.txId + " address: " + swap.address);
@@ -536,7 +543,7 @@ class ToBtcAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
536
543
  };
537
544
  return {
538
545
  networkFee: BigInt(chainFeeResp.networkFee),
539
- satsPerVbyte: BigInt(chainFeeResp.satsPerVbyte)
546
+ satsPerVbyte: chainFeeResp.satsPerVbyte
540
547
  };
541
548
  }
542
549
  startRestServer(restServer) {
@@ -13,13 +13,13 @@ export declare enum ToBtcSwapState {
13
13
  export declare class ToBtcSwapAbs<T extends SwapData = SwapData> extends ToBtcBaseSwap<T, ToBtcSwapState> {
14
14
  sending: boolean;
15
15
  readonly address: string;
16
- readonly satsPerVbyte: bigint;
16
+ readonly satsPerVbyte: number;
17
17
  readonly nonce: bigint;
18
18
  readonly requiredConfirmations: number;
19
19
  readonly preferedConfirmationTarget: number;
20
20
  btcRawTx: string;
21
21
  txId: string;
22
- constructor(chainIdentifier: string, address: string, amount: bigint, swapFee: bigint, swapFeeInToken: bigint, networkFee: bigint, networkFeeInToken: bigint, satsPerVbyte: bigint, nonce: bigint, requiredConfirmations: number, preferedConfirmationTarget: number);
22
+ constructor(chainIdentifier: string, address: string, amount: bigint, swapFee: bigint, swapFeeInToken: bigint, networkFee: bigint, networkFeeInToken: bigint, satsPerVbyte: number, nonce: bigint, requiredConfirmations: number, preferedConfirmationTarget: number);
23
23
  constructor(obj: any);
24
24
  serialize(): any;
25
25
  isInitiated(): boolean;
@@ -29,7 +29,7 @@ class ToBtcSwapAbs extends ToBtcBaseSwap_1.ToBtcBaseSwap {
29
29
  else {
30
30
  super(chainIdOrObj);
31
31
  this.address = chainIdOrObj.address;
32
- this.satsPerVbyte = BigInt(chainIdOrObj.satsPerVbyte);
32
+ this.satsPerVbyte = Number(chainIdOrObj.satsPerVbyte);
33
33
  this.nonce = BigInt(chainIdOrObj.nonce);
34
34
  this.requiredConfirmations = chainIdOrObj.requiredConfirmations;
35
35
  this.preferedConfirmationTarget = chainIdOrObj.preferedConfirmationTarget;
@@ -100,7 +100,8 @@ class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
100
100
  }
101
101
  ]);
102
102
  for (let { obj: swap } of queriedData) {
103
- await this.processPastSwap(swap);
103
+ await this.processPastSwap(swap)
104
+ .catch(e => this.swapLogger.error(swap, "processPastSwap(): Error executing watchdog function: ", e));
104
105
  }
105
106
  }
106
107
  /**
@@ -198,6 +199,10 @@ class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
198
199
  this.swapLogger.info(invoiceData, "subscribeToPayment(): result callback, outcome: " + result.status + " invoice: " + invoiceData.pr);
199
200
  this.processPaymentResult(invoiceData, result).catch(e => this.swapLogger.error(invoiceData, "subscribeToPayment(): process payment result", e));
200
201
  this.activeSubscriptions.delete(paymentHash);
202
+ }).catch(e => {
203
+ this.swapLogger.error(invoiceData, "subscribeToPayment(): error while waiting for invoice payment: ", e);
204
+ //Delete from active subscription, the watchdog will re-subscribe later
205
+ this.activeSubscriptions.delete(paymentHash);
201
206
  });
202
207
  this.swapLogger.info(invoiceData, "subscribeToPayment(): subscribe to payment outcome, invoice: " + invoiceData.pr);
203
208
  this.activeSubscriptions.add(paymentHash);
@@ -311,6 +316,9 @@ class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
311
316
  else
312
317
  throw e;
313
318
  }
319
+ const unlock = swap.lock(120);
320
+ if (unlock == null)
321
+ return;
314
322
  await swap.setState(ToBtcLnSwapAbs_1.ToBtcLnSwapState.COMMITED);
315
323
  await this.saveSwapData(swap);
316
324
  try {
@@ -322,13 +330,29 @@ class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
322
330
  if ((0, Utils_1.isDefinedRuntimeError)(e)) {
323
331
  if (swap.metadata != null)
324
332
  swap.metadata.payError = e;
325
- await swap.setState(ToBtcLnSwapAbs_1.ToBtcLnSwapState.NON_PAYABLE);
326
- await this.saveSwapData(swap);
327
- return;
333
+ const payment = await this.lightning.getPayment(swap.lnPaymentHash);
334
+ if (!unlock())
335
+ return;
336
+ if (payment == null || payment.status === "failed") {
337
+ if (swap.state === ToBtcLnSwapAbs_1.ToBtcLnSwapState.COMMITED)
338
+ await swap.setState(ToBtcLnSwapAbs_1.ToBtcLnSwapState.NON_PAYABLE);
339
+ await this.saveSwapData(swap);
340
+ return;
341
+ }
342
+ else if (payment.status === "confirmed") {
343
+ await this.processPaymentResult(swap, payment);
344
+ return;
345
+ }
346
+ else {
347
+ //Fall-through to subscribe
348
+ }
328
349
  }
329
350
  else
330
351
  throw e;
331
352
  }
353
+ finally {
354
+ unlock();
355
+ }
332
356
  this.subscribeToPayment(swap);
333
357
  return;
334
358
  }
@@ -832,6 +856,16 @@ class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
832
856
  msg: "Payment expired"
833
857
  };
834
858
  if (data.state === ToBtcLnSwapAbs_1.ToBtcLnSwapState.NON_PAYABLE) {
859
+ if (data.payInitiated) {
860
+ //Check that the ln payment is indeed non-existent or failed
861
+ const lnPayment = await this.lightning.getPayment(data.lnPaymentHash);
862
+ if (lnPayment != null && lnPayment.status !== "failed")
863
+ throw {
864
+ _httpStatus: 200,
865
+ code: 20008,
866
+ msg: "Payment in-flight"
867
+ };
868
+ }
835
869
  const refundSigData = await swapContract.getRefundSignature(signer, data.data, this.config.refundAuthorizationTimeout);
836
870
  //Double check the state after promise result
837
871
  if (data.state !== ToBtcLnSwapAbs_1.ToBtcLnSwapState.NON_PAYABLE)
@@ -223,7 +223,8 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
223
223
  }
224
224
  ]);
225
225
  for (let { obj: swap } of swaps) {
226
- await this.processPastSwap(swap);
226
+ await this.processPastSwap(swap)
227
+ .catch(e => this.swapLogger.error(swap, "processPastSwap(): Error executing watchdog function: ", e));
227
228
  }
228
229
  }
229
230
  getPricePrefetches(chainIdentifier, token, gasToken, abortController) {
@@ -615,14 +616,12 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
615
616
  };
616
617
  const pluginCheckResult = await PluginManager_1.PluginManager.onHandlePreFromBtcExecute(SwapHandler_1.SwapHandlerType.FROM_BTC_SPV, swap);
617
618
  if ((0, IPlugin_1.isQuoteThrow)(pluginCheckResult)) {
618
- const error = {
619
+ if (swap.state === SpvVaultSwap_1.SpvVaultSwapState.CREATED)
620
+ await this.removeSwapData(swap, SpvVaultSwap_1.SpvVaultSwapState.FAILED);
621
+ throw {
619
622
  code: 29999,
620
623
  msg: pluginCheckResult.message
621
624
  };
622
- if (swap.metadata != null)
623
- swap.metadata.postQuoteError = error;
624
- await this.removeSwapData(swap, SpvVaultSwap_1.SpvVaultSwapState.FAILED);
625
- throw error;
626
625
  }
627
626
  await this.Vaults.checkVaultReplacedTransactions(vault, true);
628
627
  if (swap.vaultUtxo !== vault.getLatestUtxo()) {
@@ -631,24 +630,33 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
631
630
  msg: "Vault UTXO already spent, please get another quote and try again!"
632
631
  };
633
632
  }
633
+ const unlock = swap.lock(120);
634
+ if (!unlock)
635
+ throw {
636
+ code: 20517,
637
+ msg: "Bitcoin transaction submission already in progress, please retry later!"
638
+ };
639
+ let swapSendingSet = false;
640
+ let dataSendingSet = false;
634
641
  try {
635
642
  const btcRawTx = Buffer.from(signedTx.toBytes(true, true)).toString("hex");
636
643
  //Double-check the state to prevent race condition
637
- if (swap.state !== SpvVaultSwap_1.SpvVaultSwapState.CREATED) {
644
+ if (swap.state !== SpvVaultSwap_1.SpvVaultSwapState.CREATED)
638
645
  throw {
639
646
  code: 20505,
640
647
  msg: "Invalid quote ID, not found or expired!"
641
648
  };
642
- }
643
649
  //Double check in-flight swap count
644
650
  this.checkTooManyInflightSwaps();
645
651
  swap.btcTxId = signedTx.id;
646
652
  swap.state = SpvVaultSwap_1.SpvVaultSwapState.SIGNED;
647
653
  swap.sending = true;
654
+ swapSendingSet = true;
648
655
  await this.saveSwapData(swap);
649
656
  data.btcTx.raw = btcRawTx;
650
657
  data.sending = true;
651
658
  vault.addWithdrawal(data);
659
+ dataSendingSet = true;
652
660
  await this.Vaults.saveVault(vault);
653
661
  this.swapLogger.info(swap, "REST: /postQuote: BTC transaction signed, txId: " + swap.btcTxId);
654
662
  try {
@@ -666,15 +674,27 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
666
674
  }
667
675
  }
668
676
  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);
677
+ if (swapSendingSet)
678
+ swap.sending = false;
679
+ if (dataSendingSet) {
680
+ data.sending = false;
681
+ vault.removeWithdrawal(data);
682
+ await this.Vaults.saveVault(vault);
683
+ }
684
+ //Check if the error is only because the state has already changed
685
+ if (!(0, Utils_1.isDefinedRuntimeError)(e) || e.code !== 20505) {
686
+ //We only make the swap failed if the error happened in CREATED or SIGNED states
687
+ if (swap.state === SpvVaultSwap_1.SpvVaultSwapState.CREATED || swap.state === SpvVaultSwap_1.SpvVaultSwapState.SIGNED) {
688
+ if ((0, Utils_1.isDefinedRuntimeError)(e) && swap.metadata != null)
689
+ swap.metadata.postQuoteError = e;
690
+ await this.removeSwapData(swap, SpvVaultSwap_1.SpvVaultSwapState.FAILED);
691
+ }
692
+ }
676
693
  throw e;
677
694
  }
695
+ finally {
696
+ unlock();
697
+ }
678
698
  await responseStream.writeParamsAndEnd({
679
699
  code: 20000,
680
700
  msg: "Success",
@@ -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
  }