@atomiqlabs/lp-lib 17.5.3 → 17.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/swaps/assertions/FromBtcAmountAssertions.js +2 -2
- package/dist/swaps/escrow/frombtc_abstract/FromBtcAbs.js +45 -19
- package/dist/swaps/escrow/frombtcln_abstract/FromBtcLnAbs.d.ts +2 -2
- package/dist/swaps/escrow/frombtcln_abstract/FromBtcLnAbs.js +35 -25
- package/dist/swaps/escrow/frombtcln_autoinit/FromBtcLnAuto.d.ts +2 -2
- package/dist/swaps/escrow/frombtcln_autoinit/FromBtcLnAuto.js +33 -22
- package/dist/swaps/escrow/tobtc_abstract/ToBtcAbs.d.ts +1 -1
- package/dist/swaps/escrow/tobtc_abstract/ToBtcAbs.js +29 -11
- 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.d.ts +1 -0
- package/dist/swaps/escrow/tobtcln_abstract/ToBtcLnAbs.js +54 -9
- package/dist/swaps/spv_vault_swap/SpvVaultSwapHandler.js +83 -30
- package/dist/swaps/trusted/frombtc_trusted/FromBtcTrusted.js +2 -2
- package/dist/swaps/trusted/frombtcln_trusted/FromBtcLnTrusted.js +11 -17
- package/dist/utils/Utils.d.ts +33 -0
- package/dist/utils/Utils.js +51 -1
- package/dist/utils/paramcoders/ParamDecoder.js +4 -86
- package/dist/utils/paramcoders/SchemaVerifier.d.ts +11 -2
- package/dist/utils/paramcoders/SchemaVerifier.js +93 -47
- package/package.json +1 -1
- package/src/swaps/assertions/FromBtcAmountAssertions.ts +2 -2
- package/src/swaps/escrow/frombtc_abstract/FromBtcAbs.ts +44 -19
- package/src/swaps/escrow/frombtcln_abstract/FromBtcLnAbs.ts +47 -30
- package/src/swaps/escrow/frombtcln_autoinit/FromBtcLnAuto.ts +45 -27
- package/src/swaps/escrow/tobtc_abstract/ToBtcAbs.ts +28 -13
- package/src/swaps/escrow/tobtc_abstract/ToBtcSwapAbs.ts +4 -4
- package/src/swaps/escrow/tobtcln_abstract/ToBtcLnAbs.ts +63 -13
- package/src/swaps/spv_vault_swap/SpvVaultSwapHandler.ts +79 -33
- package/src/swaps/trusted/frombtc_trusted/FromBtcTrusted.ts +25 -2
- package/src/swaps/trusted/frombtcln_trusted/FromBtcLnTrusted.ts +10 -16
- package/src/utils/Utils.ts +48 -0
- package/src/utils/paramcoders/ParamDecoder.ts +10 -66
- package/src/utils/paramcoders/SchemaVerifier.ts +80 -37
|
@@ -4,7 +4,13 @@ import {FromBtcLnAutoSwap, FromBtcLnAutoSwapState} from "./FromBtcLnAutoSwap";
|
|
|
4
4
|
import {MultichainData, SwapHandlerType} from "../../SwapHandler";
|
|
5
5
|
import {ISwapPrice} from "../../../prices/ISwapPrice";
|
|
6
6
|
import {ChainSwapType, ClaimEvent, InitializeEvent, RefundEvent, SwapCommitStateType, SwapData} from "@atomiqlabs/base";
|
|
7
|
-
import {
|
|
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
|
/**
|
|
@@ -639,7 +657,7 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
|
|
|
639
657
|
typeof(val)==="string" &&
|
|
640
658
|
val.length===64 &&
|
|
641
659
|
HEX_REGEX.test(val) ? val: null,
|
|
642
|
-
amount: FieldTypeEnum.
|
|
660
|
+
amount: FieldTypeEnum.BigIntPositive,
|
|
643
661
|
token: (val: string) => val!=null &&
|
|
644
662
|
typeof(val)==="string" &&
|
|
645
663
|
this.isTokenSupported(chainIdentifier, val) ? val : null,
|
|
@@ -648,8 +666,8 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
|
|
|
648
666
|
gasToken: (val: string) => val!=null &&
|
|
649
667
|
typeof(val)==="string" &&
|
|
650
668
|
chainInterface.isValidToken(val) ? val : null,
|
|
651
|
-
gasAmount: FieldTypeEnum.
|
|
652
|
-
claimerBounty: FieldTypeEnum.
|
|
669
|
+
gasAmount: FieldTypeEnum.BigIntNotNegative,
|
|
670
|
+
claimerBounty: FieldTypeEnum.BigIntNotNegative
|
|
653
671
|
});
|
|
654
672
|
if(parsedBody==null) throw {
|
|
655
673
|
code: 20100,
|
|
@@ -752,7 +770,7 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
|
|
|
752
770
|
//Create swap
|
|
753
771
|
const hodlInvoiceObj: HodlInvoiceInit = {
|
|
754
772
|
description: description ?? (chainIdentifier+"-"+parsedBody.address),
|
|
755
|
-
cltvDelta: Number(this.
|
|
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);
|
|
@@ -534,7 +540,7 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
|
|
|
534
540
|
* @throws {DefinedRuntimeError} will throw an error if the nonce is invalid
|
|
535
541
|
*/
|
|
536
542
|
private checkNonceValid(nonce: bigint): void {
|
|
537
|
-
if(nonce <
|
|
543
|
+
if(nonce < 1 || nonce >= (2n ** 64n)) throw {
|
|
538
544
|
code: 20021,
|
|
539
545
|
msg: "Invalid request body (nonce - cannot be parsed)"
|
|
540
546
|
};
|
|
@@ -555,6 +561,10 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
|
|
|
555
561
|
* @throws {DefinedRuntimeError} will throw an error if the confirmationTarget is out of bounds
|
|
556
562
|
*/
|
|
557
563
|
protected checkConfirmationTarget(confirmationTarget: number): void {
|
|
564
|
+
if(!Number.isFinite(confirmationTarget) || !Number.isSafeInteger(confirmationTarget)) throw {
|
|
565
|
+
code: 20028,
|
|
566
|
+
msg: "Invalid request body (confirmationTarget - not whole number)"
|
|
567
|
+
};
|
|
558
568
|
if(confirmationTarget>this.config.maxConfTarget) throw {
|
|
559
569
|
code: 20023,
|
|
560
570
|
msg: "Invalid request body (confirmationTarget - too high)"
|
|
@@ -572,6 +582,10 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
|
|
|
572
582
|
* @throws {DefinedRuntimeError} will throw an error if the confirmations are out of bounds
|
|
573
583
|
*/
|
|
574
584
|
protected checkRequiredConfirmations(confirmations: number): void {
|
|
585
|
+
if(!Number.isFinite(confirmations) || !Number.isSafeInteger(confirmations)) throw {
|
|
586
|
+
code: 20027,
|
|
587
|
+
msg: "Invalid request body (confirmations - not whole number)"
|
|
588
|
+
};
|
|
575
589
|
if(confirmations>this.config.maxConfirmations) throw {
|
|
576
590
|
code: 20025,
|
|
577
591
|
msg: "Invalid request body (confirmations - too high)"
|
|
@@ -629,7 +643,7 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
|
|
|
629
643
|
* @param amount
|
|
630
644
|
* @throws {DefinedRuntimeError} will throw an error if there are not enough BTC funds
|
|
631
645
|
*/
|
|
632
|
-
private async checkAndGetNetworkFee(address: string, amount: bigint): Promise<{ networkFee: bigint, satsPerVbyte:
|
|
646
|
+
private async checkAndGetNetworkFee(address: string, amount: bigint): Promise<{ networkFee: bigint, satsPerVbyte: number }> {
|
|
633
647
|
let chainFeeResp = await this.bitcoin.estimateFee(address, Number(amount), null, this.config.networkFeeMultiplier);
|
|
634
648
|
|
|
635
649
|
const hasEnoughFunds = chainFeeResp!=null;
|
|
@@ -640,7 +654,7 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
|
|
|
640
654
|
|
|
641
655
|
return {
|
|
642
656
|
networkFee: BigInt(chainFeeResp.networkFee),
|
|
643
|
-
satsPerVbyte:
|
|
657
|
+
satsPerVbyte: chainFeeResp.satsPerVbyte
|
|
644
658
|
};
|
|
645
659
|
}
|
|
646
660
|
|
|
@@ -672,10 +686,10 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
|
|
|
672
686
|
*/
|
|
673
687
|
const parsedBody: ToBtcRequestType = await req.paramReader.getParams({
|
|
674
688
|
address: FieldTypeEnum.String,
|
|
675
|
-
amount: FieldTypeEnum.
|
|
676
|
-
confirmationTarget: FieldTypeEnum.
|
|
677
|
-
confirmations: FieldTypeEnum.
|
|
678
|
-
nonce: FieldTypeEnum.
|
|
689
|
+
amount: FieldTypeEnum.BigIntPositive,
|
|
690
|
+
confirmationTarget: FieldTypeEnum.NumberPositive,
|
|
691
|
+
confirmations: FieldTypeEnum.NumberPositive,
|
|
692
|
+
nonce: FieldTypeEnum.BigIntNotNegative,
|
|
679
693
|
token: (val: string) => val!=null &&
|
|
680
694
|
typeof(val)==="string" &&
|
|
681
695
|
this.isTokenSupported(chainIdentifier, val) ? val : null,
|
|
@@ -799,7 +813,8 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
|
|
|
799
813
|
data: {
|
|
800
814
|
amount: amountBD.toString(10),
|
|
801
815
|
address: signer.getAddress(),
|
|
802
|
-
satsPervByte: networkFeeData.satsPerVbyte.toString(10),
|
|
816
|
+
satsPervByte: Math.floor(networkFeeData.satsPerVbyte).toString(10),
|
|
817
|
+
satsPervByteNumber: networkFeeData.satsPerVbyte,
|
|
803
818
|
networkFee: networkFeeInToken.toString(10),
|
|
804
819
|
swapFee: swapFeeInToken.toString(10),
|
|
805
820
|
totalFee: (swapFeeInToken + networkFeeInToken).toString(10),
|
|
@@ -20,7 +20,7 @@ export class ToBtcSwapAbs<T extends SwapData = SwapData> extends ToBtcBaseSwap<T
|
|
|
20
20
|
sending: boolean;
|
|
21
21
|
|
|
22
22
|
readonly address: string;
|
|
23
|
-
readonly satsPerVbyte:
|
|
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;
|
|
@@ -7,10 +7,17 @@ import {
|
|
|
7
7
|
ChainSwapType,
|
|
8
8
|
ClaimEvent,
|
|
9
9
|
InitializeEvent,
|
|
10
|
-
RefundEvent,
|
|
10
|
+
RefundEvent,
|
|
11
|
+
SwapCommitStateType,
|
|
11
12
|
SwapData
|
|
12
13
|
} from "@atomiqlabs/base";
|
|
13
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
expressHandlerWrapper,
|
|
16
|
+
getAbortController,
|
|
17
|
+
getMinSafeBlockWindowSlow,
|
|
18
|
+
HEX_REGEX,
|
|
19
|
+
isDefinedRuntimeError
|
|
20
|
+
} from "../../../utils/Utils";
|
|
14
21
|
import {PluginManager} from "../../../plugins/PluginManager";
|
|
15
22
|
import {IIntermediaryStorage} from "../../../storage/IIntermediaryStorage";
|
|
16
23
|
import {randomBytes} from "crypto";
|
|
@@ -27,9 +34,8 @@ import {
|
|
|
27
34
|
ProbeAndRouteResponse,
|
|
28
35
|
routesMatch
|
|
29
36
|
} from "../../../wallets/ILightningWallet";
|
|
30
|
-
import {
|
|
37
|
+
import {LightningAssertions} from "../../assertions/LightningAssertions";
|
|
31
38
|
import {isQuoteThrow} from "../../../plugins/IPlugin";
|
|
32
|
-
import {ToBtcSwapState} from "../tobtc_abstract/ToBtcSwapAbs";
|
|
33
39
|
|
|
34
40
|
export type ToBtcLnConfig = ToBtcBaseConfig & {
|
|
35
41
|
routingFeeMultiplier: bigint | number,
|
|
@@ -105,6 +111,8 @@ export class ToBtcLnAbs extends ToBtcBaseSwapHandler<ToBtcLnSwapAbs, ToBtcLnSwap
|
|
|
105
111
|
readonly lightning: ILightningWallet;
|
|
106
112
|
readonly LightningAssertions: LightningAssertions;
|
|
107
113
|
|
|
114
|
+
readonly cltvDeltaLowerBound: bigint;
|
|
115
|
+
|
|
108
116
|
constructor(
|
|
109
117
|
storageDirectory: IIntermediaryStorage<ToBtcLnSwapAbs>,
|
|
110
118
|
path: string,
|
|
@@ -123,9 +131,13 @@ export class ToBtcLnAbs extends ToBtcBaseSwapHandler<ToBtcLnSwapAbs, ToBtcLnSwap
|
|
|
123
131
|
this.config.minLnBaseFee = this.config.minLnBaseFee || 5n;
|
|
124
132
|
this.config.exactInExpiry = this.config.exactInExpiry || 10*1000;
|
|
125
133
|
this.config.lnSendBitcoinBlockTimeSafetyFactorPPM = this.config.lnSendBitcoinBlockTimeSafetyFactorPPM ?? (this.config.safetyFactor * 1_000_000n);
|
|
126
|
-
if(this.config.lnSendBitcoinBlockTimeSafetyFactorPPM <=
|
|
127
|
-
throw new Error("Lightning network send block safety factor set below 1.
|
|
134
|
+
if(this.config.lnSendBitcoinBlockTimeSafetyFactorPPM <= 1_250_000n) {
|
|
135
|
+
throw new Error("Lightning network send block safety factor set below 1.25, this is insecure!");
|
|
128
136
|
}
|
|
137
|
+
|
|
138
|
+
this.cltvDeltaLowerBound = getMinSafeBlockWindowSlow(
|
|
139
|
+
Number(this.config.lnSendBitcoinBlockTimeSafetyFactorPPM) / 1_000_000
|
|
140
|
+
);
|
|
129
141
|
}
|
|
130
142
|
|
|
131
143
|
/**
|
|
@@ -197,7 +209,8 @@ export class ToBtcLnAbs extends ToBtcBaseSwapHandler<ToBtcLnSwapAbs, ToBtcLnSwap
|
|
|
197
209
|
]);
|
|
198
210
|
|
|
199
211
|
for(let {obj: swap} of queriedData) {
|
|
200
|
-
await this.processPastSwap(swap)
|
|
212
|
+
await this.processPastSwap(swap)
|
|
213
|
+
.catch(e => this.swapLogger.error(swap, "processPastSwap(): Error executing watchdog function: ", e));
|
|
201
214
|
}
|
|
202
215
|
}
|
|
203
216
|
|
|
@@ -300,6 +313,10 @@ export class ToBtcLnAbs extends ToBtcBaseSwapHandler<ToBtcLnSwapAbs, ToBtcLnSwap
|
|
|
300
313
|
this.swapLogger.info(invoiceData, "subscribeToPayment(): result callback, outcome: "+result.status+" invoice: "+invoiceData.pr);
|
|
301
314
|
this.processPaymentResult(invoiceData, result).catch(e => this.swapLogger.error(invoiceData, "subscribeToPayment(): process payment result", e));
|
|
302
315
|
this.activeSubscriptions.delete(paymentHash);
|
|
316
|
+
}).catch(e => {
|
|
317
|
+
this.swapLogger.error(invoiceData, "subscribeToPayment(): error while waiting for invoice payment: ", e);
|
|
318
|
+
//Delete from active subscription, the watchdog will re-subscribe later
|
|
319
|
+
this.activeSubscriptions.delete(paymentHash);
|
|
303
320
|
});
|
|
304
321
|
this.swapLogger.info(invoiceData, "subscribeToPayment(): subscribe to payment outcome, invoice: "+invoiceData.pr);
|
|
305
322
|
|
|
@@ -329,6 +346,10 @@ export class ToBtcLnAbs extends ToBtcBaseSwapHandler<ToBtcLnSwapAbs, ToBtcLnSwap
|
|
|
329
346
|
const maxFee = swap.quotedNetworkFee;
|
|
330
347
|
const maxUsableCLTVdelta = (expiryTimestamp - currentTimestamp - this.config.gracePeriod)
|
|
331
348
|
/ (this.config.bitcoinBlocktime * this.config.lnSendBitcoinBlockTimeSafetyFactorPPM / 1_000_000n);
|
|
349
|
+
if(maxUsableCLTVdelta < this.cltvDeltaLowerBound) throw {
|
|
350
|
+
code: 90008,
|
|
351
|
+
msg: "Calculated CLTV delta is too low for current safety factor!"
|
|
352
|
+
}
|
|
332
353
|
|
|
333
354
|
//Initiate payment
|
|
334
355
|
this.swapLogger.info(swap, "sendLightningPayment(): paying lightning network invoice,"+
|
|
@@ -418,6 +439,9 @@ export class ToBtcLnAbs extends ToBtcBaseSwapHandler<ToBtcLnSwapAbs, ToBtcLnSwap
|
|
|
418
439
|
} else throw e;
|
|
419
440
|
}
|
|
420
441
|
|
|
442
|
+
const unlock = swap.lock(120);
|
|
443
|
+
if(unlock==null) return;
|
|
444
|
+
|
|
421
445
|
await swap.setState(ToBtcLnSwapState.COMMITED);
|
|
422
446
|
await this.saveSwapData(swap);
|
|
423
447
|
try {
|
|
@@ -426,10 +450,22 @@ export class ToBtcLnAbs extends ToBtcBaseSwapHandler<ToBtcLnSwapAbs, ToBtcLnSwap
|
|
|
426
450
|
this.swapLogger.error(swap, "processInitialized(): lightning payment error", e);
|
|
427
451
|
if(isDefinedRuntimeError(e)) {
|
|
428
452
|
if(swap.metadata!=null) swap.metadata.payError = e;
|
|
429
|
-
await
|
|
430
|
-
|
|
431
|
-
|
|
453
|
+
const payment = await this.lightning.getPayment(swap.lnPaymentHash);
|
|
454
|
+
if(!unlock()) return;
|
|
455
|
+
if(payment==null || payment.status==="failed") {
|
|
456
|
+
if(swap.state as ToBtcLnSwapState === ToBtcLnSwapState.COMMITED)
|
|
457
|
+
await swap.setState(ToBtcLnSwapState.NON_PAYABLE);
|
|
458
|
+
await this.saveSwapData(swap);
|
|
459
|
+
return;
|
|
460
|
+
} else if(payment.status==="confirmed") {
|
|
461
|
+
await this.processPaymentResult(swap, payment);
|
|
462
|
+
return;
|
|
463
|
+
} else {
|
|
464
|
+
//Fall-through to subscribe
|
|
465
|
+
}
|
|
432
466
|
} else throw e;
|
|
467
|
+
} finally {
|
|
468
|
+
unlock();
|
|
433
469
|
}
|
|
434
470
|
this.subscribeToPayment(swap);
|
|
435
471
|
return;
|
|
@@ -571,6 +607,10 @@ export class ToBtcLnAbs extends ToBtcBaseSwapHandler<ToBtcLnSwapAbs, ToBtcLnSwap
|
|
|
571
607
|
}> {
|
|
572
608
|
const maxUsableCLTV: bigint = (expiryTimestamp - currentTimestamp - this.config.gracePeriod)
|
|
573
609
|
/ (this.config.bitcoinBlocktime * this.config.lnSendBitcoinBlockTimeSafetyFactorPPM / 1_000_000n);
|
|
610
|
+
if(maxUsableCLTV < this.cltvDeltaLowerBound) throw {
|
|
611
|
+
code: 20002,
|
|
612
|
+
msg: "Cannot route the payment (calculated CLTV delta too short - increase timeout)!"
|
|
613
|
+
};
|
|
574
614
|
|
|
575
615
|
const blockHeight = await this.lightning.getBlockheight();
|
|
576
616
|
abortSignal.throwIfAborted();
|
|
@@ -843,8 +883,8 @@ export class ToBtcLnAbs extends ToBtcBaseSwapHandler<ToBtcLnSwapAbs, ToBtcLnSwap
|
|
|
843
883
|
*/
|
|
844
884
|
const parsedBody: ToBtcLnRequestType = await req.paramReader.getParams({
|
|
845
885
|
pr: FieldTypeEnum.String,
|
|
846
|
-
maxFee: FieldTypeEnum.
|
|
847
|
-
expiryTimestamp: FieldTypeEnum.
|
|
886
|
+
maxFee: FieldTypeEnum.BigIntPositive,
|
|
887
|
+
expiryTimestamp: FieldTypeEnum.BigIntPositive,
|
|
848
888
|
token: (val: string) => val!=null &&
|
|
849
889
|
typeof(val)==="string" &&
|
|
850
890
|
this.isTokenSupported(chainIdentifier, val) ? val : null,
|
|
@@ -852,7 +892,7 @@ export class ToBtcLnAbs extends ToBtcBaseSwapHandler<ToBtcLnSwapAbs, ToBtcLnSwap
|
|
|
852
892
|
typeof(val)==="string" &&
|
|
853
893
|
chainInterface.isValidAddress(val, true) ? val : null,
|
|
854
894
|
exactIn: FieldTypeEnum.BooleanOptional,
|
|
855
|
-
amount: FieldTypeEnum.
|
|
895
|
+
amount: FieldTypeEnum.BigIntPositiveOptional
|
|
856
896
|
});
|
|
857
897
|
if (parsedBody==null) {
|
|
858
898
|
throw {
|
|
@@ -1078,6 +1118,16 @@ export class ToBtcLnAbs extends ToBtcBaseSwapHandler<ToBtcLnSwapAbs, ToBtcLnSwap
|
|
|
1078
1118
|
};
|
|
1079
1119
|
|
|
1080
1120
|
if(data.state===ToBtcLnSwapState.NON_PAYABLE) {
|
|
1121
|
+
if(data.payInitiated) {
|
|
1122
|
+
//Check that the ln payment is indeed non-existent or failed
|
|
1123
|
+
const lnPayment = await this.lightning.getPayment(data.lnPaymentHash);
|
|
1124
|
+
if(lnPayment!=null && lnPayment.status!=="failed") throw {
|
|
1125
|
+
_httpStatus: 200,
|
|
1126
|
+
code: 20008,
|
|
1127
|
+
msg: "Payment in-flight"
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1081
1131
|
const refundSigData = await swapContract.getRefundSignature(signer, data.data, this.config.refundAuthorizationTimeout);
|
|
1082
1132
|
|
|
1083
1133
|
//Double check the state after promise result
|