@atomiqlabs/lp-lib 16.0.0 → 16.0.2
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/SwapHandler.d.ts +0 -7
- package/dist/swaps/SwapHandler.js +9 -12
- package/dist/swaps/escrow/EscrowHandler.d.ts +0 -1
- package/dist/swaps/escrow/EscrowHandler.js +3 -12
- package/dist/swaps/escrow/frombtcln_autoinit/FromBtcLnAuto.d.ts +7 -0
- package/dist/swaps/escrow/frombtcln_autoinit/FromBtcLnAuto.js +68 -8
- package/dist/swaps/trusted/frombtcln_trusted/FromBtcLnTrusted.js +2 -2
- package/package.json +1 -1
- package/src/swaps/SwapHandler.ts +2 -19
- package/src/swaps/escrow/EscrowHandler.ts +2 -10
- package/src/swaps/escrow/frombtcln_autoinit/FromBtcLnAuto.ts +67 -9
- package/src/swaps/trusted/frombtcln_trusted/FromBtcLnTrusted.ts +2 -2
|
@@ -106,13 +106,6 @@ export declare abstract class SwapHandler<V extends SwapHandlerSwap<S> = SwapHan
|
|
|
106
106
|
* Returns data to be returned in swap handler info
|
|
107
107
|
*/
|
|
108
108
|
abstract getInfoData(): any;
|
|
109
|
-
/**
|
|
110
|
-
* Remove swap data
|
|
111
|
-
*
|
|
112
|
-
* @param hash
|
|
113
|
-
* @param sequence
|
|
114
|
-
*/
|
|
115
|
-
protected removeSwapData(hash: string, sequence: bigint): Promise<void>;
|
|
116
109
|
/**
|
|
117
110
|
* Remove swap data
|
|
118
111
|
*
|
|
@@ -67,18 +67,15 @@ class SwapHandler {
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
swap
|
|
79
|
-
if (sequenceOrUltimateState != null && typeof (sequenceOrUltimateState) !== "bigint")
|
|
80
|
-
await swap.setState(sequenceOrUltimateState);
|
|
81
|
-
}
|
|
70
|
+
/**
|
|
71
|
+
* Remove swap data
|
|
72
|
+
*
|
|
73
|
+
* @param swap
|
|
74
|
+
* @param ultimateState set the ultimate state of the swap before removing
|
|
75
|
+
*/
|
|
76
|
+
async removeSwapData(swap, ultimateState) {
|
|
77
|
+
if (ultimateState != null)
|
|
78
|
+
await swap.setState(ultimateState);
|
|
82
79
|
if (swap != null)
|
|
83
80
|
await PluginManager_1.PluginManager.swapRemove(swap);
|
|
84
81
|
this.swapLogger.debug(swap, "removeSwapData(): removing swap final state: " + swap.state);
|
|
@@ -31,7 +31,6 @@ export declare abstract class EscrowHandler<V extends EscrowHandlerSwap<SwapData
|
|
|
31
31
|
*/
|
|
32
32
|
protected subscribeToEvents(): void;
|
|
33
33
|
protected loadData(ctor: new (data: any) => V): Promise<void>;
|
|
34
|
-
protected removeSwapData(hash: string, sequence: bigint): Promise<void>;
|
|
35
34
|
protected removeSwapData(swap: V, ultimateState?: S): Promise<void>;
|
|
36
35
|
protected saveSwapData(swap: V): Promise<void>;
|
|
37
36
|
protected saveSwapToEscrowHashMap(swap: V): void;
|
|
@@ -73,18 +73,9 @@ class EscrowHandler extends SwapHandler_1.SwapHandler {
|
|
|
73
73
|
this.saveSwapToEscrowHashMap(swap);
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
|
-
async removeSwapData(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (typeof (sequenceOrUltimateState) !== "bigint")
|
|
80
|
-
throw new Error("Sequence must be a BN instance!");
|
|
81
|
-
swap = await this.storageManager.getData(hashOrSwap, sequenceOrUltimateState);
|
|
82
|
-
}
|
|
83
|
-
else {
|
|
84
|
-
swap = hashOrSwap;
|
|
85
|
-
if (sequenceOrUltimateState != null && typeof (sequenceOrUltimateState) !== "bigint")
|
|
86
|
-
await swap.setState(sequenceOrUltimateState);
|
|
87
|
-
}
|
|
76
|
+
async removeSwapData(swap, ultimateState) {
|
|
77
|
+
if (ultimateState != null)
|
|
78
|
+
await swap.setState(ultimateState);
|
|
88
79
|
if (swap != null)
|
|
89
80
|
await PluginManager_1.PluginManager.swapRemove(swap);
|
|
90
81
|
this.swapLogger.debug(swap, "removeSwapData(): removing swap final state: " + swap.state);
|
|
@@ -8,6 +8,10 @@ import { FromBtcBaseConfig, FromBtcBaseSwapHandler } from "../FromBtcBaseSwapHan
|
|
|
8
8
|
import { ILightningWallet } from "../../../wallets/ILightningWallet";
|
|
9
9
|
import { LightningAssertions } from "../../assertions/LightningAssertions";
|
|
10
10
|
export type FromBtcLnAutoConfig = FromBtcBaseConfig & {
|
|
11
|
+
maxInflightHtlcs?: number;
|
|
12
|
+
minNativeBalances?: {
|
|
13
|
+
[chainId: string]: bigint;
|
|
14
|
+
};
|
|
11
15
|
invoiceTimeoutSeconds?: number;
|
|
12
16
|
minCltv: bigint;
|
|
13
17
|
gracePeriod: bigint;
|
|
@@ -33,6 +37,7 @@ export declare class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoS
|
|
|
33
37
|
readonly type = SwapHandlerType.FROM_BTCLN_AUTO;
|
|
34
38
|
readonly swapType = ChainSwapType.HTLC;
|
|
35
39
|
activeSubscriptions: Set<string>;
|
|
40
|
+
swapsWithInflighHtlcs: Set<FromBtcLnAutoSwap>;
|
|
36
41
|
readonly config: FromBtcLnAutoConfig;
|
|
37
42
|
readonly lightning: ILightningWallet;
|
|
38
43
|
readonly LightningAssertions: LightningAssertions;
|
|
@@ -105,7 +110,9 @@ export declare class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoS
|
|
|
105
110
|
* @returns the fetched lightning invoice
|
|
106
111
|
*/
|
|
107
112
|
private checkInvoiceStatus;
|
|
113
|
+
private checkTooManyInflightHtlcs;
|
|
108
114
|
startRestServer(restServer: Express): void;
|
|
109
115
|
init(): Promise<void>;
|
|
110
116
|
getInfoData(): any;
|
|
117
|
+
protected removeSwapData(swap: FromBtcLnAutoSwap, ultimateState?: FromBtcLnAutoSwapState): Promise<void>;
|
|
111
118
|
}
|
|
@@ -20,6 +20,7 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
|
|
|
20
20
|
this.type = SwapHandler_1.SwapHandlerType.FROM_BTCLN_AUTO;
|
|
21
21
|
this.swapType = base_1.ChainSwapType.HTLC;
|
|
22
22
|
this.activeSubscriptions = new Set();
|
|
23
|
+
this.swapsWithInflighHtlcs = new Set();
|
|
23
24
|
this.config = config;
|
|
24
25
|
this.config.invoiceTimeoutSeconds = this.config.invoiceTimeoutSeconds || 90;
|
|
25
26
|
this.lightning = lightning;
|
|
@@ -55,13 +56,7 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
|
|
|
55
56
|
}
|
|
56
57
|
if (swap.state === FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.RECEIVED) {
|
|
57
58
|
try {
|
|
58
|
-
|
|
59
|
-
//Expired
|
|
60
|
-
if (swap.state === FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.RECEIVED) {
|
|
61
|
-
this.swapLogger.info(swap, "processPastSwap(state=RECEIVED): offer HTLC expired, cancelling invoice: " + swap.pr);
|
|
62
|
-
await this.cancelSwapAndInvoice(swap);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
59
|
+
await this.offerHtlc(swap);
|
|
65
60
|
}
|
|
66
61
|
catch (e) {
|
|
67
62
|
this.swapLogger.error(swap, "processPastSwap(state=RECEIVED): offerHtlc error", e);
|
|
@@ -69,6 +64,7 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
|
|
|
69
64
|
return null;
|
|
70
65
|
}
|
|
71
66
|
if (swap.state === FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.TXS_SENT || swap.state === FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.COMMITED) {
|
|
67
|
+
this.swapsWithInflighHtlcs.add(swap);
|
|
72
68
|
const onchainStatus = await swapContract.getCommitStatus(signer.getAddress(), swap.data);
|
|
73
69
|
const state = swap.state;
|
|
74
70
|
if (onchainStatus.type === base_1.SwapCommitStateType.PAID) {
|
|
@@ -255,7 +251,6 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
|
|
|
255
251
|
if (invoiceData.metadata != null)
|
|
256
252
|
invoiceData.metadata.times.htlcReceived = Date.now();
|
|
257
253
|
const useToken = invoiceData.token;
|
|
258
|
-
const gasToken = invoiceData.gasToken;
|
|
259
254
|
let expiryTimeout;
|
|
260
255
|
try {
|
|
261
256
|
//Check if HTLC expiry is long enough
|
|
@@ -264,6 +259,8 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
|
|
|
264
259
|
invoiceData.metadata.times.htlcTimeoutCalculated = Date.now();
|
|
265
260
|
}
|
|
266
261
|
catch (e) {
|
|
262
|
+
if ((0, Utils_1.isDefinedRuntimeError)(e) && invoiceData.metadata != null)
|
|
263
|
+
invoiceData.metadata.htlcReceiveError = e;
|
|
267
264
|
if (invoiceData.state === FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.CREATED)
|
|
268
265
|
await this.cancelSwapAndInvoice(invoiceData);
|
|
269
266
|
throw e;
|
|
@@ -287,6 +284,16 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
|
|
|
287
284
|
async offerHtlc(invoiceData) {
|
|
288
285
|
if (invoiceData.state !== FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.RECEIVED)
|
|
289
286
|
return;
|
|
287
|
+
try {
|
|
288
|
+
this.checkTooManyInflightHtlcs();
|
|
289
|
+
}
|
|
290
|
+
catch (e) {
|
|
291
|
+
if ((0, Utils_1.isDefinedRuntimeError)(e) && invoiceData.metadata != null)
|
|
292
|
+
invoiceData.metadata.htlcOfferError = e;
|
|
293
|
+
if (invoiceData.state === FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.RECEIVED)
|
|
294
|
+
await this.cancelSwapAndInvoice(invoiceData);
|
|
295
|
+
throw e;
|
|
296
|
+
}
|
|
290
297
|
this.swapLogger.debug(invoiceData, "offerHtlc(): invoice: ", invoiceData.pr);
|
|
291
298
|
if (invoiceData.metadata != null)
|
|
292
299
|
invoiceData.metadata.times.offerHtlc = Date.now();
|
|
@@ -295,16 +302,34 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
|
|
|
295
302
|
const { swapContract, signer, chainInterface } = this.getChain(invoiceData.chainIdentifier);
|
|
296
303
|
//Create abort controller for parallel fetches
|
|
297
304
|
const abortController = new AbortController();
|
|
305
|
+
//Minimum reserve of native token to be kept in the wallet
|
|
306
|
+
const minNativeTokenReserve = this.config.minNativeBalances?.[invoiceData.chainIdentifier] ?? 0n;
|
|
298
307
|
//Pre-fetch data
|
|
299
308
|
const balancePrefetch = this.getBalancePrefetch(invoiceData.chainIdentifier, useToken, abortController);
|
|
300
309
|
const gasTokenBalancePrefetch = invoiceData.getTotalOutputGasAmount() === 0n || useToken === gasToken ?
|
|
301
310
|
null : this.getBalancePrefetch(invoiceData.chainIdentifier, gasToken, abortController);
|
|
311
|
+
const nativeBalancePrefetch = minNativeTokenReserve === 0n ?
|
|
312
|
+
null : this.getBalancePrefetch(invoiceData.chainIdentifier, chainInterface.getNativeCurrencyAddress(), abortController, false);
|
|
302
313
|
if (await swapContract.getInitAuthorizationExpiry(invoiceData.data, invoiceData) < Date.now()) {
|
|
314
|
+
if (invoiceData.metadata != null)
|
|
315
|
+
invoiceData.metadata.htlcOfferError = "Init authorization expired, before being sent!";
|
|
303
316
|
if (invoiceData.state === FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.RECEIVED) {
|
|
304
317
|
await this.cancelSwapAndInvoice(invoiceData);
|
|
305
318
|
}
|
|
306
319
|
return false;
|
|
307
320
|
}
|
|
321
|
+
try {
|
|
322
|
+
await this.checkBalance(minNativeTokenReserve, nativeBalancePrefetch, abortController.signal);
|
|
323
|
+
}
|
|
324
|
+
catch (e) {
|
|
325
|
+
if (!abortController.signal.aborted) {
|
|
326
|
+
if ((0, Utils_1.isDefinedRuntimeError)(e) && invoiceData.metadata != null)
|
|
327
|
+
invoiceData.metadata.htlcOfferError = "Not enough native balance!";
|
|
328
|
+
if (invoiceData.state === FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.RECEIVED)
|
|
329
|
+
await this.cancelSwapAndInvoice(invoiceData);
|
|
330
|
+
}
|
|
331
|
+
throw e;
|
|
332
|
+
}
|
|
308
333
|
try {
|
|
309
334
|
//Check if we have enough liquidity to proceed
|
|
310
335
|
if (useToken === gasToken) {
|
|
@@ -319,6 +344,8 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
|
|
|
319
344
|
}
|
|
320
345
|
catch (e) {
|
|
321
346
|
if (!abortController.signal.aborted) {
|
|
347
|
+
if ((0, Utils_1.isDefinedRuntimeError)(e) && invoiceData.metadata != null)
|
|
348
|
+
invoiceData.metadata.htlcOfferError = e;
|
|
322
349
|
if (invoiceData.state === FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.RECEIVED)
|
|
323
350
|
await this.cancelSwapAndInvoice(invoiceData);
|
|
324
351
|
}
|
|
@@ -331,6 +358,19 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
|
|
|
331
358
|
signature: invoiceData.signature
|
|
332
359
|
}, true);
|
|
333
360
|
if (invoiceData.state === FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.RECEIVED) {
|
|
361
|
+
//Re-check the current HTLC count
|
|
362
|
+
try {
|
|
363
|
+
this.checkTooManyInflightHtlcs();
|
|
364
|
+
}
|
|
365
|
+
catch (e) {
|
|
366
|
+
if ((0, Utils_1.isDefinedRuntimeError)(e) && invoiceData.metadata != null)
|
|
367
|
+
invoiceData.metadata.htlcOfferError = e;
|
|
368
|
+
if (invoiceData.state === FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.RECEIVED)
|
|
369
|
+
await this.cancelSwapAndInvoice(invoiceData);
|
|
370
|
+
throw e;
|
|
371
|
+
}
|
|
372
|
+
this.swapLogger.debug(invoiceData, `offerHtlc(): Sending HTLC offer, current in flight HTLCs: ${this.swapsWithInflighHtlcs.size}, invoice: `, invoiceData.pr);
|
|
373
|
+
this.swapsWithInflighHtlcs.add(invoiceData);
|
|
334
374
|
//Setting the state variable is done outside the promise, so is done synchronously
|
|
335
375
|
await invoiceData.setState(FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.TXS_SENT);
|
|
336
376
|
await this.saveSwapData(invoiceData);
|
|
@@ -471,6 +511,15 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
|
|
|
471
511
|
return invoice;
|
|
472
512
|
}
|
|
473
513
|
}
|
|
514
|
+
checkTooManyInflightHtlcs() {
|
|
515
|
+
if (this.config.maxInflightHtlcs == null)
|
|
516
|
+
return;
|
|
517
|
+
if (this.swapsWithInflighHtlcs.size >= this.config.maxInflightHtlcs)
|
|
518
|
+
throw {
|
|
519
|
+
code: 20401,
|
|
520
|
+
msg: "Too many in-flight HTLCs, retry later!"
|
|
521
|
+
};
|
|
522
|
+
}
|
|
474
523
|
startRestServer(restServer) {
|
|
475
524
|
restServer.use(this.path + "/createInvoice", (0, ServerParamDecoder_1.serverParamDecoder)(10 * 1000));
|
|
476
525
|
restServer.post(this.path + "/createInvoice", (0, Utils_1.expressHandlerWrapper)(async (req, res) => {
|
|
@@ -551,16 +600,21 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
|
|
|
551
600
|
const gasToken = parsedBody.gasToken;
|
|
552
601
|
//Check request params
|
|
553
602
|
this.checkDescriptionHash(parsedBody.descriptionHash);
|
|
603
|
+
this.checkTooManyInflightHtlcs();
|
|
554
604
|
const fees = await this.AmountAssertions.preCheckFromBtcAmounts(this.type, request, requestedAmount, gasTokenAmount);
|
|
555
605
|
metadata.times.requestChecked = Date.now();
|
|
556
606
|
//Create abortController for parallel prefetches
|
|
557
607
|
const responseStream = res.responseStream;
|
|
558
608
|
const abortController = (0, Utils_1.getAbortController)(responseStream);
|
|
609
|
+
//Minimum reserve of native token to be kept in the wallet
|
|
610
|
+
const minNativeTokenReserve = this.config.minNativeBalances?.[chainIdentifier] ?? 0n;
|
|
559
611
|
//Pre-fetch data
|
|
560
612
|
const { pricePrefetchPromise, gasTokenPricePrefetchPromise } = this.getFromBtcPricePrefetches(chainIdentifier, useToken, gasToken, abortController);
|
|
561
613
|
const balancePrefetch = this.getBalancePrefetch(chainIdentifier, useToken, abortController);
|
|
562
614
|
const gasTokenBalancePrefetch = gasTokenAmount.amount === 0n || useToken === gasToken ?
|
|
563
615
|
null : this.getBalancePrefetch(chainIdentifier, gasToken, abortController);
|
|
616
|
+
const nativeTokenBalancePrefetch = minNativeTokenReserve === 0n ?
|
|
617
|
+
null : this.getBalancePrefetch(chainIdentifier, chainInterface.getNativeCurrencyAddress(), abortController, false);
|
|
564
618
|
const channelsPrefetch = this.LightningAssertions.getChannelsPrefetch(abortController);
|
|
565
619
|
//Asynchronously send the node's public key to the client
|
|
566
620
|
this.sendPublicKeyAsync(responseStream);
|
|
@@ -568,6 +622,8 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
|
|
|
568
622
|
let { amountBD, swapFee, swapFeeInToken, totalInToken, amountBDgas, gasSwapFee, gasSwapFeeInToken, totalInGasToken } = await this.AmountAssertions.checkFromBtcAmount(this.type, request, { ...requestedAmount, pricePrefetch: pricePrefetchPromise }, fees, abortController.signal, { ...gasTokenAmount, pricePrefetch: gasTokenPricePrefetchPromise });
|
|
569
623
|
metadata.times.priceCalculated = Date.now();
|
|
570
624
|
const totalBtcInput = amountBD + amountBDgas;
|
|
625
|
+
//Check if we have at least the minimum needed native balance
|
|
626
|
+
await this.checkBalance(minNativeTokenReserve, nativeTokenBalancePrefetch, abortController.signal);
|
|
571
627
|
//Check if we have enough funds to honor the request
|
|
572
628
|
if (useToken === gasToken) {
|
|
573
629
|
await this.checkBalance(totalInToken + totalInGasToken, balancePrefetch, abortController.signal);
|
|
@@ -678,5 +734,9 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
|
|
|
678
734
|
gasTokens: mappedDict
|
|
679
735
|
};
|
|
680
736
|
}
|
|
737
|
+
async removeSwapData(swap, ultimateState) {
|
|
738
|
+
this.swapsWithInflighHtlcs.delete(swap);
|
|
739
|
+
return super.removeSwapData(swap, ultimateState);
|
|
740
|
+
}
|
|
681
741
|
}
|
|
682
742
|
exports.FromBtcLnAuto = FromBtcLnAuto;
|
|
@@ -189,7 +189,7 @@ class FromBtcLnTrusted extends SwapHandler_1.SwapHandler {
|
|
|
189
189
|
await this.storageManager.saveData(invoice.id, null, invoiceData);
|
|
190
190
|
await this.lightning.cancelHodlInvoice(invoice.id);
|
|
191
191
|
this.unsubscribeInvoice(invoice.id);
|
|
192
|
-
await this.removeSwapData(
|
|
192
|
+
await this.removeSwapData(invoiceData);
|
|
193
193
|
this.swapLogger.info(invoiceData, "htlcReceived(): transaction sending failed, refunding lightning: ", invoiceData.pr);
|
|
194
194
|
throw {
|
|
195
195
|
code: 20002,
|
|
@@ -221,7 +221,7 @@ class FromBtcLnTrusted extends SwapHandler_1.SwapHandler {
|
|
|
221
221
|
await this.storageManager.saveData(invoice.id, null, invoiceData);
|
|
222
222
|
await this.lightning.cancelHodlInvoice(invoice.id);
|
|
223
223
|
this.unsubscribeInvoice(invoice.id);
|
|
224
|
-
await this.removeSwapData(
|
|
224
|
+
await this.removeSwapData(invoiceData);
|
|
225
225
|
this.swapLogger.info(invoiceData, "htlcReceived(): transaction reverted, refunding lightning: ", invoiceData.pr);
|
|
226
226
|
throw {
|
|
227
227
|
code: 20002,
|
package/package.json
CHANGED
package/src/swaps/SwapHandler.ts
CHANGED
|
@@ -165,31 +165,14 @@ export abstract class SwapHandler<V extends SwapHandlerSwap<S> = SwapHandlerSwap
|
|
|
165
165
|
*/
|
|
166
166
|
abstract getInfoData(): any;
|
|
167
167
|
|
|
168
|
-
/**
|
|
169
|
-
* Remove swap data
|
|
170
|
-
*
|
|
171
|
-
* @param hash
|
|
172
|
-
* @param sequence
|
|
173
|
-
*/
|
|
174
|
-
protected removeSwapData(hash: string, sequence: bigint): Promise<void>;
|
|
175
|
-
|
|
176
168
|
/**
|
|
177
169
|
* Remove swap data
|
|
178
170
|
*
|
|
179
171
|
* @param swap
|
|
180
172
|
* @param ultimateState set the ultimate state of the swap before removing
|
|
181
173
|
*/
|
|
182
|
-
protected removeSwapData(swap: V, ultimateState?: S)
|
|
183
|
-
|
|
184
|
-
protected async removeSwapData(hashOrSwap: string | V, sequenceOrUltimateState?: bigint | S) {
|
|
185
|
-
let swap: V;
|
|
186
|
-
if(typeof(hashOrSwap)==="string") {
|
|
187
|
-
if(typeof(sequenceOrUltimateState)!=="bigint") throw new Error("Sequence must be a BN instance!");
|
|
188
|
-
swap = await this.storageManager.getData(hashOrSwap, sequenceOrUltimateState);
|
|
189
|
-
} else {
|
|
190
|
-
swap = hashOrSwap;
|
|
191
|
-
if(sequenceOrUltimateState!=null && typeof(sequenceOrUltimateState)!=="bigint") await swap.setState(sequenceOrUltimateState);
|
|
192
|
-
}
|
|
174
|
+
protected async removeSwapData(swap: V, ultimateState?: S) {
|
|
175
|
+
if(ultimateState!=null) await swap.setState(ultimateState);
|
|
193
176
|
if(swap!=null) await PluginManager.swapRemove(swap);
|
|
194
177
|
this.swapLogger.debug(swap, "removeSwapData(): removing swap final state: "+swap.state);
|
|
195
178
|
await this.storageManager.removeData(swap.getIdentifierHash(), swap.getSequence());
|
|
@@ -89,17 +89,9 @@ export abstract class EscrowHandler<V extends EscrowHandlerSwap<SwapData, S>, S>
|
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
protected removeSwapData(hash: string, sequence: bigint): Promise<void>;
|
|
93
92
|
protected removeSwapData(swap: V, ultimateState?: S): Promise<void>;
|
|
94
|
-
protected async removeSwapData(
|
|
95
|
-
|
|
96
|
-
if(typeof(hashOrSwap)==="string") {
|
|
97
|
-
if(typeof(sequenceOrUltimateState)!=="bigint") throw new Error("Sequence must be a BN instance!");
|
|
98
|
-
swap = await this.storageManager.getData(hashOrSwap, sequenceOrUltimateState);
|
|
99
|
-
} else {
|
|
100
|
-
swap = hashOrSwap;
|
|
101
|
-
if(sequenceOrUltimateState!=null && typeof(sequenceOrUltimateState)!=="bigint") await swap.setState(sequenceOrUltimateState);
|
|
102
|
-
}
|
|
93
|
+
protected async removeSwapData(swap: V, ultimateState?: S) {
|
|
94
|
+
if(ultimateState!=null) await swap.setState(ultimateState);
|
|
103
95
|
if(swap!=null) await PluginManager.swapRemove(swap);
|
|
104
96
|
this.swapLogger.debug(swap, "removeSwapData(): removing swap final state: "+swap.state);
|
|
105
97
|
this.removeSwapFromEscrowHashMap(swap);
|
|
@@ -4,7 +4,7 @@ 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} from "../../../utils/Utils";
|
|
7
|
+
import {expressHandlerWrapper, getAbortController, HEX_REGEX, isDefinedRuntimeError} from "../../../utils/Utils";
|
|
8
8
|
import {PluginManager} from "../../../plugins/PluginManager";
|
|
9
9
|
import {IIntermediaryStorage} from "../../../storage/IIntermediaryStorage";
|
|
10
10
|
import {FieldTypeEnum, verifySchema} from "../../../utils/paramcoders/SchemaVerifier";
|
|
@@ -21,6 +21,10 @@ import {
|
|
|
21
21
|
import {LightningAssertions} from "../../assertions/LightningAssertions";
|
|
22
22
|
|
|
23
23
|
export type FromBtcLnAutoConfig = FromBtcBaseConfig & {
|
|
24
|
+
maxInflightHtlcs?: number,
|
|
25
|
+
minNativeBalances?: {
|
|
26
|
+
[chainId: string]: bigint
|
|
27
|
+
},
|
|
24
28
|
invoiceTimeoutSeconds?: number,
|
|
25
29
|
minCltv: bigint,
|
|
26
30
|
gracePeriod: bigint,
|
|
@@ -47,6 +51,7 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
|
|
|
47
51
|
readonly swapType = ChainSwapType.HTLC;
|
|
48
52
|
|
|
49
53
|
activeSubscriptions: Set<string> = new Set<string>();
|
|
54
|
+
swapsWithInflighHtlcs: Set<FromBtcLnAutoSwap> = new Set();
|
|
50
55
|
|
|
51
56
|
readonly config: FromBtcLnAutoConfig;
|
|
52
57
|
readonly lightning: ILightningWallet;
|
|
@@ -100,13 +105,7 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
|
|
|
100
105
|
|
|
101
106
|
if(swap.state===FromBtcLnAutoSwapState.RECEIVED) {
|
|
102
107
|
try {
|
|
103
|
-
|
|
104
|
-
//Expired
|
|
105
|
-
if(swap.state===FromBtcLnAutoSwapState.RECEIVED) {
|
|
106
|
-
this.swapLogger.info(swap, "processPastSwap(state=RECEIVED): offer HTLC expired, cancelling invoice: "+swap.pr);
|
|
107
|
-
await this.cancelSwapAndInvoice(swap);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
108
|
+
await this.offerHtlc(swap);
|
|
110
109
|
} catch (e) {
|
|
111
110
|
this.swapLogger.error(swap, "processPastSwap(state=RECEIVED): offerHtlc error", e);
|
|
112
111
|
}
|
|
@@ -115,6 +114,8 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
|
|
|
115
114
|
}
|
|
116
115
|
|
|
117
116
|
if(swap.state===FromBtcLnAutoSwapState.TXS_SENT || swap.state===FromBtcLnAutoSwapState.COMMITED) {
|
|
117
|
+
this.swapsWithInflighHtlcs.add(swap);
|
|
118
|
+
|
|
118
119
|
const onchainStatus = await swapContract.getCommitStatus(signer.getAddress(), swap.data);
|
|
119
120
|
const state: FromBtcLnAutoSwapState = swap.state as FromBtcLnAutoSwapState;
|
|
120
121
|
if(onchainStatus.type===SwapCommitStateType.PAID) {
|
|
@@ -319,7 +320,6 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
|
|
|
319
320
|
if(invoiceData.metadata!=null) invoiceData.metadata.times.htlcReceived = Date.now();
|
|
320
321
|
|
|
321
322
|
const useToken = invoiceData.token;
|
|
322
|
-
const gasToken = invoiceData.gasToken;
|
|
323
323
|
|
|
324
324
|
let expiryTimeout: bigint;
|
|
325
325
|
try {
|
|
@@ -327,6 +327,7 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
|
|
|
327
327
|
expiryTimeout = await this.checkHtlcExpiry(invoice);
|
|
328
328
|
if(invoiceData.metadata!=null) invoiceData.metadata.times.htlcTimeoutCalculated = Date.now();
|
|
329
329
|
} catch (e) {
|
|
330
|
+
if(isDefinedRuntimeError(e) && invoiceData.metadata!=null) invoiceData.metadata.htlcReceiveError = e;
|
|
330
331
|
if(invoiceData.state===FromBtcLnAutoSwapState.CREATED) await this.cancelSwapAndInvoice(invoiceData);
|
|
331
332
|
throw e;
|
|
332
333
|
}
|
|
@@ -369,6 +370,14 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
|
|
|
369
370
|
private async offerHtlc(invoiceData: FromBtcLnAutoSwap) {
|
|
370
371
|
if(invoiceData.state!==FromBtcLnAutoSwapState.RECEIVED) return;
|
|
371
372
|
|
|
373
|
+
try {
|
|
374
|
+
this.checkTooManyInflightHtlcs();
|
|
375
|
+
} catch (e) {
|
|
376
|
+
if(isDefinedRuntimeError(e) && invoiceData.metadata!=null) invoiceData.metadata.htlcOfferError = e;
|
|
377
|
+
if(invoiceData.state===FromBtcLnAutoSwapState.RECEIVED) await this.cancelSwapAndInvoice(invoiceData);
|
|
378
|
+
throw e;
|
|
379
|
+
}
|
|
380
|
+
|
|
372
381
|
this.swapLogger.debug(invoiceData, "offerHtlc(): invoice: ", invoiceData.pr);
|
|
373
382
|
if(invoiceData.metadata!=null) invoiceData.metadata.times.offerHtlc = Date.now();
|
|
374
383
|
|
|
@@ -380,18 +389,34 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
|
|
|
380
389
|
//Create abort controller for parallel fetches
|
|
381
390
|
const abortController = new AbortController();
|
|
382
391
|
|
|
392
|
+
//Minimum reserve of native token to be kept in the wallet
|
|
393
|
+
const minNativeTokenReserve: bigint = this.config.minNativeBalances?.[invoiceData.chainIdentifier] ?? 0n;
|
|
394
|
+
|
|
383
395
|
//Pre-fetch data
|
|
384
396
|
const balancePrefetch: Promise<bigint> = this.getBalancePrefetch(invoiceData.chainIdentifier, useToken, abortController);
|
|
385
397
|
const gasTokenBalancePrefetch: Promise<bigint> = invoiceData.getTotalOutputGasAmount()===0n || useToken===gasToken ?
|
|
386
398
|
null : this.getBalancePrefetch(invoiceData.chainIdentifier, gasToken, abortController);
|
|
399
|
+
const nativeBalancePrefetch: Promise<bigint> = minNativeTokenReserve===0n ?
|
|
400
|
+
null : this.getBalancePrefetch(invoiceData.chainIdentifier, chainInterface.getNativeCurrencyAddress(), abortController, false);
|
|
387
401
|
|
|
388
402
|
if(await swapContract.getInitAuthorizationExpiry(invoiceData.data, invoiceData) < Date.now()) {
|
|
403
|
+
if(invoiceData.metadata!=null) invoiceData.metadata.htlcOfferError = "Init authorization expired, before being sent!";
|
|
389
404
|
if(invoiceData.state===FromBtcLnAutoSwapState.RECEIVED) {
|
|
390
405
|
await this.cancelSwapAndInvoice(invoiceData);
|
|
391
406
|
}
|
|
392
407
|
return false;
|
|
393
408
|
}
|
|
394
409
|
|
|
410
|
+
try {
|
|
411
|
+
await this.checkBalance(minNativeTokenReserve, nativeBalancePrefetch, abortController.signal);
|
|
412
|
+
} catch (e) {
|
|
413
|
+
if(!abortController.signal.aborted) {
|
|
414
|
+
if(isDefinedRuntimeError(e) && invoiceData.metadata!=null) invoiceData.metadata.htlcOfferError = "Not enough native balance!";
|
|
415
|
+
if(invoiceData.state===FromBtcLnAutoSwapState.RECEIVED) await this.cancelSwapAndInvoice(invoiceData);
|
|
416
|
+
}
|
|
417
|
+
throw e;
|
|
418
|
+
}
|
|
419
|
+
|
|
395
420
|
try {
|
|
396
421
|
//Check if we have enough liquidity to proceed
|
|
397
422
|
if(useToken===gasToken) {
|
|
@@ -403,6 +428,7 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
|
|
|
403
428
|
if(invoiceData.metadata!=null) invoiceData.metadata.times.offerHtlcChecked = Date.now();
|
|
404
429
|
} catch (e) {
|
|
405
430
|
if(!abortController.signal.aborted) {
|
|
431
|
+
if(isDefinedRuntimeError(e) && invoiceData.metadata!=null) invoiceData.metadata.htlcOfferError = e;
|
|
406
432
|
if(invoiceData.state===FromBtcLnAutoSwapState.RECEIVED) await this.cancelSwapAndInvoice(invoiceData);
|
|
407
433
|
}
|
|
408
434
|
throw e;
|
|
@@ -416,6 +442,16 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
|
|
|
416
442
|
}, true);
|
|
417
443
|
|
|
418
444
|
if(invoiceData.state===FromBtcLnAutoSwapState.RECEIVED) {
|
|
445
|
+
//Re-check the current HTLC count
|
|
446
|
+
try {
|
|
447
|
+
this.checkTooManyInflightHtlcs();
|
|
448
|
+
} catch (e) {
|
|
449
|
+
if(isDefinedRuntimeError(e) && invoiceData.metadata!=null) invoiceData.metadata.htlcOfferError = e;
|
|
450
|
+
if(invoiceData.state===FromBtcLnAutoSwapState.RECEIVED) await this.cancelSwapAndInvoice(invoiceData);
|
|
451
|
+
throw e;
|
|
452
|
+
}
|
|
453
|
+
this.swapLogger.debug(invoiceData, `offerHtlc(): Sending HTLC offer, current in flight HTLCs: ${this.swapsWithInflighHtlcs.size}, invoice: `, invoiceData.pr);
|
|
454
|
+
this.swapsWithInflighHtlcs.add(invoiceData);
|
|
419
455
|
//Setting the state variable is done outside the promise, so is done synchronously
|
|
420
456
|
await invoiceData.setState(FromBtcLnAutoSwapState.TXS_SENT);
|
|
421
457
|
await this.saveSwapData(invoiceData);
|
|
@@ -565,6 +601,14 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
|
|
|
565
601
|
}
|
|
566
602
|
}
|
|
567
603
|
|
|
604
|
+
private checkTooManyInflightHtlcs() {
|
|
605
|
+
if(this.config.maxInflightHtlcs==null) return;
|
|
606
|
+
if(this.swapsWithInflighHtlcs.size>=this.config.maxInflightHtlcs) throw {
|
|
607
|
+
code: 20401,
|
|
608
|
+
msg: "Too many in-flight HTLCs, retry later!"
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
568
612
|
startRestServer(restServer: Express) {
|
|
569
613
|
|
|
570
614
|
restServer.use(this.path+"/createInvoice", serverParamDecoder(10*1000));
|
|
@@ -653,6 +697,7 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
|
|
|
653
697
|
|
|
654
698
|
//Check request params
|
|
655
699
|
this.checkDescriptionHash(parsedBody.descriptionHash);
|
|
700
|
+
this.checkTooManyInflightHtlcs();
|
|
656
701
|
const fees = await this.AmountAssertions.preCheckFromBtcAmounts(this.type, request, requestedAmount, gasTokenAmount);
|
|
657
702
|
metadata.times.requestChecked = Date.now();
|
|
658
703
|
|
|
@@ -660,6 +705,9 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
|
|
|
660
705
|
const responseStream = res.responseStream;
|
|
661
706
|
const abortController = getAbortController(responseStream);
|
|
662
707
|
|
|
708
|
+
//Minimum reserve of native token to be kept in the wallet
|
|
709
|
+
const minNativeTokenReserve: bigint = this.config.minNativeBalances?.[chainIdentifier] ?? 0n;
|
|
710
|
+
|
|
663
711
|
//Pre-fetch data
|
|
664
712
|
const {
|
|
665
713
|
pricePrefetchPromise,
|
|
@@ -668,6 +716,8 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
|
|
|
668
716
|
const balancePrefetch: Promise<bigint> = this.getBalancePrefetch(chainIdentifier, useToken, abortController);
|
|
669
717
|
const gasTokenBalancePrefetch: Promise<bigint> = gasTokenAmount.amount===0n || useToken===gasToken ?
|
|
670
718
|
null : this.getBalancePrefetch(chainIdentifier, gasToken, abortController);
|
|
719
|
+
const nativeTokenBalancePrefetch: Promise<bigint> = minNativeTokenReserve===0n ?
|
|
720
|
+
null : this.getBalancePrefetch(chainIdentifier, chainInterface.getNativeCurrencyAddress(), abortController, false);
|
|
671
721
|
const channelsPrefetch: Promise<LightningNetworkChannel[]> = this.LightningAssertions.getChannelsPrefetch(abortController);
|
|
672
722
|
|
|
673
723
|
//Asynchronously send the node's public key to the client
|
|
@@ -693,6 +743,9 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
|
|
|
693
743
|
|
|
694
744
|
const totalBtcInput = amountBD + amountBDgas;
|
|
695
745
|
|
|
746
|
+
//Check if we have at least the minimum needed native balance
|
|
747
|
+
await this.checkBalance(minNativeTokenReserve, nativeTokenBalancePrefetch, abortController.signal);
|
|
748
|
+
|
|
696
749
|
//Check if we have enough funds to honor the request
|
|
697
750
|
if(useToken===gasToken) {
|
|
698
751
|
await this.checkBalance(totalInToken + totalInGasToken, balancePrefetch, abortController.signal);
|
|
@@ -843,5 +896,10 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
|
|
|
843
896
|
};
|
|
844
897
|
}
|
|
845
898
|
|
|
899
|
+
protected async removeSwapData(swap: FromBtcLnAutoSwap, ultimateState?: FromBtcLnAutoSwapState) {
|
|
900
|
+
this.swapsWithInflighHtlcs.delete(swap);
|
|
901
|
+
return super.removeSwapData(swap, ultimateState);
|
|
902
|
+
}
|
|
903
|
+
|
|
846
904
|
}
|
|
847
905
|
|
|
@@ -234,7 +234,7 @@ export class FromBtcLnTrusted extends SwapHandler<FromBtcLnTrustedSwap, FromBtcL
|
|
|
234
234
|
await this.storageManager.saveData(invoice.id, null, invoiceData);
|
|
235
235
|
await this.lightning.cancelHodlInvoice(invoice.id);
|
|
236
236
|
this.unsubscribeInvoice(invoice.id);
|
|
237
|
-
await this.removeSwapData(
|
|
237
|
+
await this.removeSwapData(invoiceData);
|
|
238
238
|
this.swapLogger.info(invoiceData, "htlcReceived(): transaction sending failed, refunding lightning: ", invoiceData.pr);
|
|
239
239
|
throw {
|
|
240
240
|
code: 20002,
|
|
@@ -267,7 +267,7 @@ export class FromBtcLnTrusted extends SwapHandler<FromBtcLnTrustedSwap, FromBtcL
|
|
|
267
267
|
await this.storageManager.saveData(invoice.id, null, invoiceData);
|
|
268
268
|
await this.lightning.cancelHodlInvoice(invoice.id);
|
|
269
269
|
this.unsubscribeInvoice(invoice.id);
|
|
270
|
-
await this.removeSwapData(
|
|
270
|
+
await this.removeSwapData(invoiceData);
|
|
271
271
|
this.swapLogger.info(invoiceData, "htlcReceived(): transaction reverted, refunding lightning: ", invoiceData.pr);
|
|
272
272
|
throw {
|
|
273
273
|
code: 20002,
|