@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.
- package/dist/swaps/assertions/FromBtcAmountAssertions.js +2 -2
- package/dist/swaps/escrow/frombtc_abstract/FromBtcAbs.js +16 -6
- package/dist/swaps/escrow/frombtcln_abstract/FromBtcLnAbs.d.ts +2 -2
- package/dist/swaps/escrow/frombtcln_abstract/FromBtcLnAbs.js +34 -24
- package/dist/swaps/escrow/frombtcln_autoinit/FromBtcLnAuto.d.ts +2 -2
- package/dist/swaps/escrow/frombtcln_autoinit/FromBtcLnAuto.js +30 -19
- package/dist/swaps/escrow/tobtc_abstract/ToBtcAbs.d.ts +1 -1
- package/dist/swaps/escrow/tobtc_abstract/ToBtcAbs.js +12 -5
- package/dist/swaps/escrow/tobtc_abstract/ToBtcSwapAbs.d.ts +2 -2
- package/dist/swaps/escrow/tobtc_abstract/ToBtcSwapAbs.js +1 -1
- package/dist/swaps/escrow/tobtcln_abstract/ToBtcLnAbs.js +38 -4
- package/dist/swaps/spv_vault_swap/SpvVaultSwapHandler.js +35 -15
- package/dist/swaps/trusted/frombtc_trusted/FromBtcTrusted.js +2 -2
- package/dist/swaps/trusted/frombtcln_trusted/FromBtcLnTrusted.js +7 -2
- package/dist/utils/Utils.d.ts +33 -0
- package/dist/utils/Utils.js +51 -1
- package/package.json +1 -1
- package/src/swaps/assertions/FromBtcAmountAssertions.ts +2 -2
- package/src/swaps/escrow/frombtc_abstract/FromBtcAbs.ts +14 -5
- package/src/swaps/escrow/frombtcln_abstract/FromBtcLnAbs.ts +46 -29
- package/src/swaps/escrow/frombtcln_autoinit/FromBtcLnAuto.ts +42 -24
- package/src/swaps/escrow/tobtc_abstract/ToBtcAbs.ts +13 -7
- package/src/swaps/escrow/tobtc_abstract/ToBtcSwapAbs.ts +4 -4
- package/src/swaps/escrow/tobtcln_abstract/ToBtcLnAbs.ts +37 -7
- package/src/swaps/spv_vault_swap/SpvVaultSwapHandler.ts +36 -18
- package/src/swaps/trusted/frombtc_trusted/FromBtcTrusted.ts +25 -2
- package/src/swaps/trusted/frombtcln_trusted/FromBtcLnTrusted.ts +5 -1
- 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
|
-
|
|
134
|
-
|
|
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
|
}
|
package/dist/utils/Utils.d.ts
CHANGED
|
@@ -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;
|
package/dist/utils/Utils.js
CHANGED
|
@@ -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
|
@@ -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) /
|
|
190
|
-
const adjustedMax = max * (1000000n - fees.feePPM) /
|
|
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
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
|
|
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)) +
|
|
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<
|
|
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.
|
|
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.
|
|
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.
|
|
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,
|
|
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.
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
-
|
|
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)) +
|
|
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<
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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:
|
|
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,
|
|
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.
|
|
425
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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?:
|
|
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 =
|
|
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;
|