@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
@@ -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
  }
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atomiqlabs/lp-lib",
3
- "version": "17.5.3",
3
+ "version": "17.6.0",
4
4
  "description": "Main functionality implementation for atomiq LP node",
5
5
  "main": "./dist/index.js",
6
6
  "types:": "./dist/index.d.ts",
@@ -186,8 +186,8 @@ export class FromBtcAmountAssertions extends AmountAssertions {
186
186
  const tooLow = amountBD < (min * 95n / 100n);
187
187
  const tooHigh = amountBD > (max * 105n / 100n);
188
188
  if(tooLow || tooHigh) {
189
- const adjustedMin = min * (1000000n - fees.feePPM) / (1000000n - fees.baseFee);
190
- const adjustedMax = max * (1000000n - fees.feePPM) / (1000000n - fees.baseFee);
189
+ const adjustedMin = min * (1000000n - fees.feePPM) / 1_000_000n - fees.baseFee;
190
+ const adjustedMax = max * (1000000n - fees.feePPM) / 1_000_000n - fees.baseFee;
191
191
  const minIn = await this.swapPricing.getFromBtcSwapAmount(
192
192
  adjustedMin, requestedAmount.token, chainIdentifier, null, requestedAmount.pricePrefetch
193
193
  );
@@ -139,7 +139,11 @@ export class FromBtcAbs extends FromBtcBaseSwapHandler<FromBtcSwapAbs, FromBtcSw
139
139
  const refundSwaps: FromBtcSwapAbs[] = [];
140
140
 
141
141
  for(let {obj: swap} of queriedData) {
142
- if(await this.processPastSwap(swap)) refundSwaps.push(swap);
142
+ try {
143
+ if (await this.processPastSwap(swap)) refundSwaps.push(swap);
144
+ } catch (e) {
145
+ this.swapLogger.error(swap, "processPastSwap(): Error executing watchdog function: ", e);
146
+ }
143
147
  }
144
148
 
145
149
  await this.refundSwaps(refundSwaps);
@@ -156,11 +160,16 @@ export class FromBtcAbs extends FromBtcBaseSwapHandler<FromBtcSwapAbs, FromBtcSw
156
160
  const {swapContract, signer} = this.getChain(refundSwap.chainIdentifier);
157
161
  const unlock = refundSwap.lock(swapContract.refundTimeout);
158
162
  if(unlock==null) continue;
163
+
159
164
  this.swapLogger.debug(refundSwap, "refundSwaps(): initiate refund of swap");
160
- await swapContract.refund(signer, refundSwap.data, true, false, {waitForConfirmation: true});
161
- this.swapLogger.info(refundSwap, "refundSwaps(): swap refunded, address: "+refundSwap.address);
162
- //The swap should be removed by the event handler
163
- await refundSwap.setState(FromBtcSwapState.REFUNDED);
165
+ try {
166
+ await swapContract.refund(signer, refundSwap.data, true, false, {waitForConfirmation: true});
167
+ this.swapLogger.info(refundSwap, "refundSwaps(): swap refunded, address: "+refundSwap.address);
168
+ //The swap should be removed by the event handler
169
+ await refundSwap.setState(FromBtcSwapState.REFUNDED);
170
+ } catch (e) {
171
+ this.swapLogger.error(refundSwap, "refundSwaps(): error refunding swap: ", e);
172
+ }
164
173
  unlock();
165
174
  }
166
175
  }
@@ -12,7 +12,13 @@ import {
12
12
  SwapCommitStateType,
13
13
  SwapData
14
14
  } from "@atomiqlabs/base";
15
- import {expressHandlerWrapper, getAbortController, HEX_REGEX, isDefinedRuntimeError} from "../../../utils/Utils";
15
+ import {
16
+ bigIntMax,
17
+ expressHandlerWrapper,
18
+ getAbortController, getMinSafeBlockWindowFast,
19
+ HEX_REGEX,
20
+ isDefinedRuntimeError
21
+ } from "../../../utils/Utils";
16
22
  import {PluginManager} from "../../../plugins/PluginManager";
17
23
  import {IIntermediaryStorage} from "../../../storage/IIntermediaryStorage";
18
24
  import {FieldTypeEnum, verifySchema} from "../../../utils/paramcoders/SchemaVerifier";
@@ -32,7 +38,7 @@ import {FromBtcLnAutoSwapState} from "../frombtcln_autoinit/FromBtcLnAutoSwap";
32
38
 
33
39
  export type FromBtcLnConfig = FromBtcBaseConfig & {
34
40
  invoiceTimeoutSeconds?: number,
35
- minCltv: bigint,
41
+ destinationHtlcTimeoutSeconds: bigint,
36
42
  gracePeriod: bigint
37
43
  }
38
44
 
@@ -57,6 +63,8 @@ export class FromBtcLnAbs extends FromBtcBaseSwapHandler<FromBtcLnSwapAbs, FromB
57
63
  readonly lightning: ILightningWallet;
58
64
  readonly LightningAssertions: LightningAssertions;
59
65
 
66
+ readonly minCltv: bigint;
67
+
60
68
  constructor(
61
69
  storageDirectory: IIntermediaryStorage<FromBtcLnSwapAbs>,
62
70
  path: string,
@@ -70,6 +78,13 @@ export class FromBtcLnAbs extends FromBtcBaseSwapHandler<FromBtcLnSwapAbs, FromB
70
78
  this.config.invoiceTimeoutSeconds = this.config.invoiceTimeoutSeconds || 90;
71
79
  this.lightning = lightning;
72
80
  this.LightningAssertions = new LightningAssertions(this.logger, lightning);
81
+
82
+ const numerator = (this.config.destinationHtlcTimeoutSeconds + this.config.gracePeriod) * this.config.safetyFactor;
83
+ this.minCltv = bigIntMax(
84
+ (numerator + this.config.bitcoinBlocktime - 1n)
85
+ / this.config.bitcoinBlocktime, //Ceil division
86
+ getMinSafeBlockWindowFast(this.config.safetyFactor)
87
+ );
73
88
  }
74
89
 
75
90
  protected async processPastSwap(swap: FromBtcLnSwapAbs): Promise<"REFUND" | "SETTLE" | "CANCEL" | null> {
@@ -169,10 +184,13 @@ export class FromBtcLnAbs extends FromBtcBaseSwapHandler<FromBtcLnSwapAbs, FromB
169
184
  if(unlock==null) continue;
170
185
 
171
186
  this.swapLogger.debug(refundSwap, "refundSwaps(): initiate refund of swap");
172
- await swapContract.refund(signer, refundSwap.data, true, false, {waitForConfirmation: true});
173
- this.swapLogger.info(refundSwap, "refundsSwaps(): swap refunded, invoice: "+refundSwap.pr);
174
-
175
- await refundSwap.setState(FromBtcLnSwapState.REFUNDED);
187
+ try {
188
+ await swapContract.refund(signer, refundSwap.data, true, false, {waitForConfirmation: true});
189
+ this.swapLogger.info(refundSwap, "refundsSwaps(): swap refunded, invoice: "+refundSwap.pr);
190
+ await refundSwap.setState(FromBtcLnSwapState.REFUNDED);
191
+ } catch (e) {
192
+ this.swapLogger.error(refundSwap, "refundSwaps(): error refunding swap: ", e);
193
+ }
176
194
  unlock();
177
195
  }
178
196
  }
@@ -228,16 +246,20 @@ export class FromBtcLnAbs extends FromBtcBaseSwapHandler<FromBtcLnSwapAbs, FromB
228
246
  ]);
229
247
 
230
248
  for(let {obj: swap} of queriedData) {
231
- switch(await this.processPastSwap(swap)) {
232
- case "CANCEL":
233
- cancelInvoices.push(swap);
234
- break;
235
- case "SETTLE":
236
- settleInvoices.push(swap);
237
- break;
238
- case "REFUND":
239
- refundSwaps.push(swap);
240
- break;
249
+ try {
250
+ switch (await this.processPastSwap(swap)) {
251
+ case "CANCEL":
252
+ cancelInvoices.push(swap);
253
+ break;
254
+ case "SETTLE":
255
+ settleInvoices.push(swap);
256
+ break;
257
+ case "REFUND":
258
+ refundSwaps.push(swap);
259
+ break;
260
+ }
261
+ } catch (e) {
262
+ this.swapLogger.error(swap, "processPastSwap(): Error executing watchdog function: ", e)
241
263
  }
242
264
  }
243
265
 
@@ -327,14 +349,13 @@ export class FromBtcLnAbs extends FromBtcBaseSwapHandler<FromBtcLnSwapAbs, FromB
327
349
  const blockheightPrefetch = this.getBlockheightPrefetch(abortController);
328
350
  const signDataPrefetchPromise: Promise<any> = this.getSignDataPrefetch(invoiceData.chainIdentifier, abortController);
329
351
 
330
- let expiryTimeout: bigint;
331
352
  try {
332
353
  //Check if we have enough liquidity to proceed
333
354
  await this.checkBalance(escrowAmount, balancePrefetch, abortController.signal);
334
355
  if(invoiceData.metadata!=null) invoiceData.metadata.times.htlcBalanceChecked = Date.now();
335
356
 
336
357
  //Check if HTLC expiry is long enough
337
- expiryTimeout = await this.checkHtlcExpiry(invoice, blockheightPrefetch, abortController.signal);
358
+ await this.checkHtlcExpiry(invoice, blockheightPrefetch, abortController.signal);
338
359
  if(invoiceData.metadata!=null) invoiceData.metadata.times.htlcTimeoutCalculated = Date.now();
339
360
  } catch (e) {
340
361
  if(!abortController.signal.aborted) {
@@ -355,7 +376,7 @@ export class FromBtcLnAbs extends FromBtcBaseSwapHandler<FromBtcLnSwapAbs, FromB
355
376
  escrowAmount,
356
377
  invoiceData.claimHash,
357
378
  0n,
358
- BigInt(Math.floor(Date.now() / 1000)) + expiryTimeout,
379
+ BigInt(Math.floor(Date.now() / 1000)) + this.config.destinationHtlcTimeoutSeconds,
359
380
  false,
360
381
  true,
361
382
  invoiceData.securityDeposit,
@@ -493,28 +514,25 @@ export class FromBtcLnAbs extends FromBtcBaseSwapHandler<FromBtcLnSwapAbs, FromB
493
514
  * @param blockheightPrefetch
494
515
  * @param signal
495
516
  * @throws {DefinedRuntimeError} Will throw if HTLC expires too soon and therefore cannot be processed
496
- * @returns expiry timeout in seconds
497
517
  */
498
- private async checkHtlcExpiry(invoice: LightningNetworkInvoice, blockheightPrefetch: Promise<number>, signal: AbortSignal): Promise<bigint> {
518
+ private async checkHtlcExpiry(invoice: LightningNetworkInvoice, blockheightPrefetch: Promise<number>, signal: AbortSignal): Promise<void> {
499
519
  const timeout: number = this.getInvoicePaymentsTimeout(invoice);
500
520
  const current_block_height = await blockheightPrefetch;
501
521
  signal.throwIfAborted();
502
522
 
503
523
  const blockDelta = BigInt(timeout - current_block_height);
504
524
 
505
- const htlcExpiresTooSoon = blockDelta < this.config.minCltv;
525
+ const htlcExpiresTooSoon = blockDelta < this.minCltv;
506
526
  if(htlcExpiresTooSoon) {
507
527
  throw {
508
528
  code: 20002,
509
529
  msg: "Not enough time to reliably process the swap",
510
530
  data: {
511
- requiredDelta: this.config.minCltv.toString(10),
531
+ requiredDelta: this.minCltv.toString(10),
512
532
  actualDelta: blockDelta.toString(10)
513
533
  }
514
534
  };
515
535
  }
516
-
517
- return (this.config.minCltv * this.config.bitcoinBlocktime / this.config.safetyFactor) - this.config.gracePeriod;
518
536
  }
519
537
 
520
538
  /**
@@ -711,7 +729,7 @@ export class FromBtcLnAbs extends FromBtcBaseSwapHandler<FromBtcLnSwapAbs, FromB
711
729
  //Create swap
712
730
  const hodlInvoiceObj: HodlInvoiceInit = {
713
731
  description: description ?? (chainIdentifier+"-"+parsedBody.address),
714
- cltvDelta: Number(this.config.minCltv) + 5,
732
+ cltvDelta: Number(this.minCltv) + 5,
715
733
  expiresAt: Date.now()+(this.config.invoiceTimeoutSeconds*1000),
716
734
  id: parsedBody.paymentHash,
717
735
  mtokens: amountBD * 1000n,
@@ -725,9 +743,8 @@ export class FromBtcLnAbs extends FromBtcBaseSwapHandler<FromBtcLnSwapAbs, FromB
725
743
  metadata.invoiceResponse = {...hodlInvoice};
726
744
 
727
745
  //Pre-compute the security deposit
728
- const expiryTimeout = (this.config.minCltv * this.config.bitcoinBlocktime / this.config.safetyFactor) - this.config.gracePeriod;
729
746
  const totalSecurityDeposit = await this.getSecurityDeposit(
730
- chainIdentifier, amountBD, swapFee, expiryTimeout,
747
+ chainIdentifier, amountBD, swapFee, this.config.destinationHtlcTimeoutSeconds,
731
748
  baseSDPromise, depositToken, depositTokenPricePrefetchPromise, fees,
732
749
  abortController.signal, metadata
733
750
  );
@@ -892,7 +909,7 @@ export class FromBtcLnAbs extends FromBtcBaseSwapHandler<FromBtcLnSwapAbs, FromB
892
909
 
893
910
  getInfoData(): any {
894
911
  return {
895
- minCltv: Number(this.config.minCltv)
912
+ minCltv: Number(this.minCltv)
896
913
  };
897
914
  }
898
915
 
@@ -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
  /**
@@ -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);
@@ -629,7 +635,7 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
629
635
  * @param amount
630
636
  * @throws {DefinedRuntimeError} will throw an error if there are not enough BTC funds
631
637
  */
632
- private async checkAndGetNetworkFee(address: string, amount: bigint): Promise<{ networkFee: bigint, satsPerVbyte: bigint }> {
638
+ private async checkAndGetNetworkFee(address: string, amount: bigint): Promise<{ networkFee: bigint, satsPerVbyte: number }> {
633
639
  let chainFeeResp = await this.bitcoin.estimateFee(address, Number(amount), null, this.config.networkFeeMultiplier);
634
640
 
635
641
  const hasEnoughFunds = chainFeeResp!=null;
@@ -640,7 +646,7 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
640
646
 
641
647
  return {
642
648
  networkFee: BigInt(chainFeeResp.networkFee),
643
- satsPerVbyte: BigInt(chainFeeResp.satsPerVbyte)
649
+ satsPerVbyte: chainFeeResp.satsPerVbyte
644
650
  };
645
651
  }
646
652
 
@@ -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;