@atomiqlabs/lp-lib 16.0.2 → 16.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/dist/swaps/SwapHandler.d.ts +28 -0
  2. package/dist/swaps/SwapHandler.js +66 -0
  3. package/dist/swaps/escrow/frombtc_abstract/FromBtcAbs.d.ts +1 -0
  4. package/dist/swaps/escrow/frombtc_abstract/FromBtcAbs.js +4 -0
  5. package/dist/swaps/escrow/frombtcln_abstract/FromBtcLnAbs.d.ts +1 -0
  6. package/dist/swaps/escrow/frombtcln_abstract/FromBtcLnAbs.js +27 -5
  7. package/dist/swaps/escrow/frombtcln_autoinit/FromBtcLnAuto.d.ts +1 -7
  8. package/dist/swaps/escrow/frombtcln_autoinit/FromBtcLnAuto.js +7 -41
  9. package/dist/swaps/escrow/tobtc_abstract/ToBtcAbs.d.ts +1 -0
  10. package/dist/swaps/escrow/tobtc_abstract/ToBtcAbs.js +5 -0
  11. package/dist/swaps/escrow/tobtcln_abstract/ToBtcLnAbs.d.ts +2 -1
  12. package/dist/swaps/escrow/tobtcln_abstract/ToBtcLnAbs.js +23 -2
  13. package/dist/swaps/spv_vault_swap/SpvVaultSwapHandler.d.ts +1 -0
  14. package/dist/swaps/spv_vault_swap/SpvVaultSwapHandler.js +9 -0
  15. package/dist/swaps/trusted/frombtc_trusted/FromBtcTrusted.d.ts +1 -0
  16. package/dist/swaps/trusted/frombtc_trusted/FromBtcTrusted.js +12 -0
  17. package/dist/swaps/trusted/frombtcln_trusted/FromBtcLnTrusted.d.ts +1 -0
  18. package/dist/swaps/trusted/frombtcln_trusted/FromBtcLnTrusted.js +12 -0
  19. package/package.json +1 -1
  20. package/src/swaps/SwapHandler.ts +72 -1
  21. package/src/swaps/escrow/frombtc_abstract/FromBtcAbs.ts +5 -0
  22. package/src/swaps/escrow/frombtcln_abstract/FromBtcLnAbs.ts +24 -6
  23. package/src/swaps/escrow/frombtcln_autoinit/FromBtcLnAuto.ts +7 -46
  24. package/src/swaps/escrow/tobtc_abstract/ToBtcAbs.ts +6 -0
  25. package/src/swaps/escrow/tobtcln_abstract/ToBtcLnAbs.ts +23 -4
  26. package/src/swaps/spv_vault_swap/SpvVaultSwapHandler.ts +16 -3
  27. package/src/swaps/trusted/frombtc_trusted/FromBtcTrusted.ts +15 -0
  28. package/src/swaps/trusted/frombtcln_trusted/FromBtcLnTrusted.ts +15 -1
@@ -12,6 +12,7 @@ class FromBtcTrusted extends SwapHandler_1.SwapHandler {
12
12
  var _a;
13
13
  super(storageDirectory, path, chains, swapPricing);
14
14
  this.type = SwapHandler_1.SwapHandlerType.FROM_BTC_TRUSTED;
15
+ this.inflightSwapStates = new Set([FromBtcTrustedSwap_1.FromBtcTrustedSwapState.RECEIVED, FromBtcTrustedSwap_1.FromBtcTrustedSwapState.BTC_CONFIRMED, FromBtcTrustedSwap_1.FromBtcTrustedSwapState.SENT, FromBtcTrustedSwap_1.FromBtcTrustedSwapState.CONFIRMED]);
15
16
  this.subscriptions = new Map();
16
17
  this.doubleSpendWatchdogSwaps = new Set();
17
18
  this.refundedSwaps = new Map();
@@ -148,6 +149,13 @@ class FromBtcTrusted extends SwapHandler_1.SwapHandler {
148
149
  swap.txId = tx.txid;
149
150
  swap.vout = vout;
150
151
  this.subscriptions.delete(outputScript);
152
+ try {
153
+ this.checkTooManyInflightSwaps();
154
+ }
155
+ catch (e) {
156
+ await this.refundSwap(swap);
157
+ return;
158
+ }
151
159
  await swap.setState(FromBtcTrustedSwap_1.FromBtcTrustedSwapState.RECEIVED);
152
160
  await this.storageManager.saveData(swap.getIdentifierHash(), swap.getSequence(), swap);
153
161
  }
@@ -373,6 +381,7 @@ class FromBtcTrusted extends SwapHandler_1.SwapHandler {
373
381
  metadata
374
382
  };
375
383
  const useToken = parsedBody.token;
384
+ this.checkTooManyInflightSwaps();
376
385
  //Check request params
377
386
  const fees = await this.AmountAssertions.preCheckFromBtcAmounts(this.type, request, requestedAmount);
378
387
  metadata.times.requestChecked = Date.now();
@@ -390,6 +399,9 @@ class FromBtcTrusted extends SwapHandler_1.SwapHandler {
390
399
  abortController.abort(e);
391
400
  return null;
392
401
  });
402
+ const nativeBalancePrefetch = useToken === chainInterface.getNativeCurrencyAddress() ?
403
+ balancePrefetch : this.prefetchNativeBalanceIfNeeded(chainIdentifier, abortController);
404
+ await this.checkNativeBalance(chainIdentifier, nativeBalancePrefetch, abortController.signal);
393
405
  //Check valid amount specified (min/max)
394
406
  const { amountBD, swapFee, swapFeeInToken, totalInToken } = await this.AmountAssertions.checkFromBtcAmount(this.type, request, { ...requestedAmount, pricePrefetch: pricePrefetchPromise }, fees, abortController.signal);
395
407
  metadata.times.priceCalculated = Date.now();
@@ -21,6 +21,7 @@ export type FromBtcLnTrustedRequestType = {
21
21
  */
22
22
  export declare class FromBtcLnTrusted extends SwapHandler<FromBtcLnTrustedSwap, FromBtcLnTrustedSwapState> {
23
23
  readonly type = SwapHandlerType.FROM_BTCLN_TRUSTED;
24
+ readonly inflightSwapStates: Set<FromBtcLnTrustedSwapState>;
24
25
  activeSubscriptions: Map<string, AbortController>;
25
26
  processedTxIds: Map<string, string>;
26
27
  readonly config: SwapForGasServerConfig;
@@ -16,6 +16,7 @@ class FromBtcLnTrusted extends SwapHandler_1.SwapHandler {
16
16
  constructor(storageDirectory, path, chains, lightning, swapPricing, config) {
17
17
  super(storageDirectory, path, chains, swapPricing);
18
18
  this.type = SwapHandler_1.SwapHandlerType.FROM_BTCLN_TRUSTED;
19
+ this.inflightSwapStates = new Set([FromBtcLnTrustedSwap_1.FromBtcLnTrustedSwapState.RECEIVED, FromBtcLnTrustedSwap_1.FromBtcLnTrustedSwapState.SENT, FromBtcLnTrustedSwap_1.FromBtcLnTrustedSwapState.CONFIRMED]);
19
20
  this.activeSubscriptions = new Map();
20
21
  this.processedTxIds = new Map();
21
22
  this.lightning = lightning;
@@ -153,6 +154,13 @@ class FromBtcLnTrusted extends SwapHandler_1.SwapHandler {
153
154
  const { signer, chainInterface } = this.getChain(invoiceData.chainIdentifier);
154
155
  //Important to prevent race condition and issuing 2 signed init messages at the same time
155
156
  if (invoiceData.state === FromBtcLnTrustedSwap_1.FromBtcLnTrustedSwapState.CREATED) {
157
+ try {
158
+ this.checkTooManyInflightSwaps();
159
+ }
160
+ catch (e) {
161
+ await this.cancelSwapAndInvoice(invoiceData);
162
+ throw e;
163
+ }
156
164
  if (invoiceData.metadata != null)
157
165
  invoiceData.metadata.times.htlcReceived = Date.now();
158
166
  await invoiceData.setState(FromBtcLnTrustedSwap_1.FromBtcLnTrustedSwapState.RECEIVED);
@@ -344,6 +352,7 @@ class FromBtcLnTrusted extends SwapHandler_1.SwapHandler {
344
352
  metadata
345
353
  };
346
354
  const useToken = parsedBody.token;
355
+ this.checkTooManyInflightSwaps();
347
356
  //Check request params
348
357
  const fees = await this.AmountAssertions.preCheckFromBtcAmounts(this.type, request, requestedAmount);
349
358
  metadata.times.requestChecked = Date.now();
@@ -361,7 +370,10 @@ class FromBtcLnTrusted extends SwapHandler_1.SwapHandler {
361
370
  abortController.abort(e);
362
371
  return null;
363
372
  });
373
+ const nativeBalancePrefetch = useToken === chainInterface.getNativeCurrencyAddress() ?
374
+ balancePrefetch : this.prefetchNativeBalanceIfNeeded(chainIdentifier, abortController);
364
375
  const channelsPrefetch = this.LightningAssertions.getChannelsPrefetch(abortController);
376
+ await this.checkNativeBalance(chainIdentifier, nativeBalancePrefetch, abortController.signal);
365
377
  //Check valid amount specified (min/max)
366
378
  const { amountBD, swapFee, swapFeeInToken, totalInToken } = await this.AmountAssertions.checkFromBtcAmount(this.type, request, { ...requestedAmount, pricePrefetch: pricePrefetchPromise }, fees, abortController.signal);
367
379
  metadata.times.priceCalculated = Date.now();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atomiqlabs/lp-lib",
3
- "version": "16.0.2",
3
+ "version": "16.0.4",
4
4
  "description": "Main functionality implementation for atomiq LP node",
5
5
  "main": "./dist/index.js",
6
6
  "types:": "./dist/index.d.ts",
@@ -1,7 +1,6 @@
1
1
  import {Express, Request} from "express";
2
2
  import {ISwapPrice} from "../prices/ISwapPrice";
3
3
  import {
4
- ChainSwapType,
5
4
  ChainType
6
5
  } from "@atomiqlabs/base";
7
6
  import {SwapHandlerSwap} from "./SwapHandlerSwap";
@@ -35,6 +34,10 @@ export type SwapBaseConfig = {
35
34
  initAuthorizationTimeouts?: {
36
35
  [chainId: string]: number
37
36
  },
37
+ minNativeBalances?: {
38
+ [chainId: string]: bigint
39
+ },
40
+ maxInflightSwaps?: number,
38
41
  bitcoinBlocktime: bigint,
39
42
  baseFee: bigint,
40
43
  feePPM: bigint,
@@ -83,8 +86,11 @@ export abstract class SwapHandler<V extends SwapHandlerSwap<S> = SwapHandlerSwap
83
86
  readonly allowedTokens: {[chainId: string]: Set<string>};
84
87
  readonly swapPricing: ISwapPrice;
85
88
 
89
+ abstract readonly inflightSwapStates: Set<S>;
86
90
  abstract config: SwapBaseConfig;
87
91
 
92
+ inflightSwaps: Set<string> = new Set();
93
+
88
94
  logger: LoggerType = getLogger(() => "SwapHandler("+this.type+"): ");
89
95
 
90
96
  protected swapLogger = {
@@ -150,6 +156,7 @@ export abstract class SwapHandler<V extends SwapHandlerSwap<S> = SwapHandlerSwap
150
156
  await this.storageManager.removeData(hash, sequence);
151
157
  await this.storageManager.saveData(swap.getIdentifierHash(), swap.getSequence(), swap);
152
158
  }
159
+ if(this.inflightSwapStates.has(swap.state)) this.inflightSwaps.add(swap.getIdentifier());
153
160
  }
154
161
  }
155
162
 
@@ -172,6 +179,8 @@ export abstract class SwapHandler<V extends SwapHandlerSwap<S> = SwapHandlerSwap
172
179
  * @param ultimateState set the ultimate state of the swap before removing
173
180
  */
174
181
  protected async removeSwapData(swap: V, ultimateState?: S) {
182
+ this.inflightSwaps.delete(swap.getIdentifier());
183
+ this.logger.debug("removeSwapData(): Removing in-flight swap, current in-flight swaps: "+this.inflightSwaps.size);
175
184
  if(ultimateState!=null) await swap.setState(ultimateState);
176
185
  if(swap!=null) await PluginManager.swapRemove(swap);
177
186
  this.swapLogger.debug(swap, "removeSwapData(): removing swap final state: "+swap.state);
@@ -179,9 +188,71 @@ export abstract class SwapHandler<V extends SwapHandlerSwap<S> = SwapHandlerSwap
179
188
  }
180
189
 
181
190
  protected async saveSwapData(swap: V) {
191
+ if(this.inflightSwapStates.has(swap.state)) {
192
+ this.inflightSwaps.add(swap.getIdentifier());
193
+ this.logger.debug("removeSwapData(): Adding in-flight swap, current in-flight swaps: "+this.inflightSwaps.size);
194
+ } else {
195
+ this.inflightSwaps.delete(swap.getIdentifier());
196
+ this.logger.debug("removeSwapData(): Removing in-flight swap, current in-flight swaps: "+this.inflightSwaps.size);
197
+ }
182
198
  await this.storageManager.saveData(swap.getIdentifierHash(), swap.getSequence(), swap);
183
199
  }
184
200
 
201
+ /**
202
+ * Pre-fetches native balance to further check if we have enough reserve in a native token
203
+ *
204
+ * @param chainIdentifier
205
+ * @param abortController
206
+ * @protected
207
+ */
208
+ protected prefetchNativeBalanceIfNeeded(chainIdentifier: string, abortController: AbortController): Promise<bigint> | null {
209
+ const minNativeTokenReserve: bigint = this.config.minNativeBalances?.[chainIdentifier] ?? 0n;
210
+ if(minNativeTokenReserve===0n) return null;
211
+
212
+ const { chainInterface, signer} = this.getChain(chainIdentifier);
213
+
214
+ return chainInterface.getBalance(signer.getAddress(), chainInterface.getNativeCurrencyAddress()).catch(e => {
215
+ this.logger.error("getBalancePrefetch(): balancePrefetch error: ", e);
216
+ abortController.abort(e);
217
+ return null;
218
+ });
219
+ }
220
+
221
+ /**
222
+ * Checks if we have enough native balance to facilitate swaps
223
+ *
224
+ * @param chainIdentifier
225
+ * @param balancePrefetch
226
+ * @param signal
227
+ * @throws {DefinedRuntimeError} will throw an error if there are not enough funds in the vault
228
+ */
229
+ protected async checkNativeBalance(chainIdentifier: string, balancePrefetch: Promise<bigint>, signal: AbortSignal | null): Promise<void> {
230
+ if(signal!=null) signal.throwIfAborted();
231
+
232
+ const minNativeTokenReserve: bigint = this.config.minNativeBalances?.[chainIdentifier] ?? 0n;
233
+ if(minNativeTokenReserve===0n) return;
234
+ const balance = await balancePrefetch;
235
+
236
+ if(balance==null || balance < minNativeTokenReserve) {
237
+ throw {
238
+ code: 20012,
239
+ msg: "LP ran out of native token to cover gas fees"
240
+ };
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Checks whether there are too many swaps in-flight currently
246
+ * @private
247
+ */
248
+ protected checkTooManyInflightSwaps() {
249
+ if(this.config.maxInflightSwaps==null) return;
250
+ if(this.inflightSwaps.size>=this.config.maxInflightSwaps) throw {
251
+ code: 20013,
252
+ msg: "LP has too many in-flight swaps, retry later!"
253
+ }
254
+ }
255
+
185
256
  /**
186
257
  * Checks if we have enough balance of the token in the swap vault
187
258
  *
@@ -40,6 +40,7 @@ export type FromBtcRequestType = {
40
40
  export class FromBtcAbs extends FromBtcBaseSwapHandler<FromBtcSwapAbs, FromBtcSwapState> {
41
41
  readonly type = SwapHandlerType.FROM_BTC;
42
42
  readonly swapType = ChainSwapType.CHAIN;
43
+ readonly inflightSwapStates = new Set([FromBtcSwapState.COMMITED]);
43
44
 
44
45
  readonly config: FromBtcConfig & {swapTsCsvDelta: bigint};
45
46
 
@@ -303,6 +304,7 @@ export class FromBtcAbs extends FromBtcBaseSwapHandler<FromBtcSwapAbs, FromBtcSw
303
304
  const useToken = parsedBody.token;
304
305
 
305
306
  //Check request params
307
+ this.checkTooManyInflightSwaps();
306
308
  this.checkSequence(parsedBody.sequence);
307
309
  const fees = await this.AmountAssertions.preCheckFromBtcAmounts(this.type, request, requestedAmount);
308
310
  metadata.times.requestChecked = Date.now();
@@ -319,6 +321,7 @@ export class FromBtcAbs extends FromBtcBaseSwapHandler<FromBtcSwapAbs, FromBtcSw
319
321
  } = this.getFromBtcPricePrefetches(chainIdentifier, useToken, depositToken, abortController);
320
322
  const balancePrefetch: Promise<bigint> = this.getBalancePrefetch(chainIdentifier, useToken, abortController);
321
323
  const signDataPrefetchPromise: Promise<any> = this.getSignDataPrefetch(chainIdentifier, abortController, responseStream);
324
+ const nativeBalancePrefetch = this.prefetchNativeBalanceIfNeeded(chainIdentifier, abortController);
322
325
 
323
326
  const dummySwapData = await this.getDummySwapData(chainIdentifier, useToken, parsedBody.address);
324
327
  abortController.signal.throwIfAborted();
@@ -328,6 +331,8 @@ export class FromBtcAbs extends FromBtcBaseSwapHandler<FromBtcSwapAbs, FromBtcSw
328
331
  abortController
329
332
  );
330
333
 
334
+ await this.checkNativeBalance(chainIdentifier, nativeBalancePrefetch, abortController.signal);
335
+
331
336
  //Check valid amount specified (min/max)
332
337
  const {
333
338
  amountBD,
@@ -27,6 +27,7 @@ import {
27
27
  LightningNetworkInvoice
28
28
  } from "../../../wallets/ILightningWallet";
29
29
  import {LightningAssertions} from "../../assertions/LightningAssertions";
30
+ import {FromBtcLnAutoSwapState} from "../frombtcln_autoinit/FromBtcLnAutoSwap";
30
31
 
31
32
  export type FromBtcLnConfig = FromBtcBaseConfig & {
32
33
  invoiceTimeoutSeconds?: number,
@@ -49,6 +50,7 @@ export type FromBtcLnRequestType = {
49
50
  export class FromBtcLnAbs extends FromBtcBaseSwapHandler<FromBtcLnSwapAbs, FromBtcLnSwapState> {
50
51
  readonly type = SwapHandlerType.FROM_BTCLN;
51
52
  readonly swapType = ChainSwapType.HTLC;
53
+ readonly inflightSwapStates = new Set([FromBtcLnSwapState.RECEIVED, FromBtcLnSwapState.COMMITED, FromBtcLnSwapState.CLAIMED]);
52
54
 
53
55
  readonly config: FromBtcLnConfig;
54
56
  readonly lightning: ILightningWallet;
@@ -95,12 +97,6 @@ export class FromBtcLnAbs extends FromBtcBaseSwapHandler<FromBtcLnSwapAbs, FromB
95
97
  this.swapLogger.error(swap, "processPastSwap(state=CREATED): htlcReceived error", e);
96
98
  }
97
99
 
98
- // @ts-ignore Previous call (htlcReceived) mutates the state of the swap, so this is valid
99
- if(swap.state===FromBtcLnSwapState.CANCELED) {
100
- this.swapLogger.info(swap, "processPastSwap(state=CREATED): invoice CANCELED after htlcReceived(), cancelling, invoice: "+swap.pr);
101
- return "CANCEL";
102
- }
103
-
104
100
  return null;
105
101
  }
106
102
 
@@ -314,6 +310,14 @@ export class FromBtcLnAbs extends FromBtcBaseSwapHandler<FromBtcLnSwapAbs, FromB
314
310
  const useToken = invoiceData.token;
315
311
  const escrowAmount: bigint = invoiceData.totalTokens;
316
312
 
313
+ try {
314
+ this.checkTooManyInflightSwaps();
315
+ } catch (e) {
316
+ if(isDefinedRuntimeError(e) && invoiceData.metadata!=null) invoiceData.metadata.htlcReceiveError = e;
317
+ if(invoiceData.state===FromBtcLnSwapState.CREATED) await this.cancelSwapAndInvoice(invoiceData);
318
+ throw e;
319
+ }
320
+
317
321
  //Create abort controller for parallel fetches
318
322
  const abortController = new AbortController();
319
323
 
@@ -333,6 +337,7 @@ export class FromBtcLnAbs extends FromBtcBaseSwapHandler<FromBtcLnSwapAbs, FromB
333
337
  if(invoiceData.metadata!=null) invoiceData.metadata.times.htlcTimeoutCalculated = Date.now();
334
338
  } catch (e) {
335
339
  if(!abortController.signal.aborted) {
340
+ if(isDefinedRuntimeError(e) && invoiceData.metadata!=null) invoiceData.metadata.htlcReceiveError = e;
336
341
  if(invoiceData.state===FromBtcLnSwapState.CREATED) await this.cancelSwapAndInvoice(invoiceData);
337
342
  }
338
343
  throw e;
@@ -372,6 +377,15 @@ export class FromBtcLnAbs extends FromBtcBaseSwapHandler<FromBtcLnSwapAbs, FromB
372
377
 
373
378
  //Important to prevent race condition and issuing 2 signed init messages at the same time
374
379
  if(invoiceData.state===FromBtcLnSwapState.CREATED) {
380
+ //Re-check right before signing
381
+ try {
382
+ this.checkTooManyInflightSwaps();
383
+ } catch (e) {
384
+ if(isDefinedRuntimeError(e) && invoiceData.metadata!=null) invoiceData.metadata.htlcReceiveError = e;
385
+ if(invoiceData.state===FromBtcLnSwapState.CREATED) await this.cancelSwapAndInvoice(invoiceData);
386
+ throw e;
387
+ }
388
+
375
389
  invoiceData.data = payInvoiceObject;
376
390
  invoiceData.prefix = sigData.prefix;
377
391
  invoiceData.timeout = sigData.timeout;
@@ -622,6 +636,7 @@ export class FromBtcLnAbs extends FromBtcBaseSwapHandler<FromBtcLnSwapAbs, FromB
622
636
  const useToken = parsedBody.token;
623
637
 
624
638
  //Check request params
639
+ this.checkTooManyInflightSwaps();
625
640
  this.checkDescriptionHash(parsedBody.descriptionHash);
626
641
  const fees = await this.AmountAssertions.preCheckFromBtcAmounts(this.type, request, requestedAmount);
627
642
  metadata.times.requestChecked = Date.now();
@@ -638,6 +653,7 @@ export class FromBtcLnAbs extends FromBtcBaseSwapHandler<FromBtcLnSwapAbs, FromB
638
653
  } = this.getFromBtcPricePrefetches(chainIdentifier, useToken, depositToken, abortController);
639
654
  const balancePrefetch: Promise<bigint> = this.getBalancePrefetch(chainIdentifier, useToken, abortController);
640
655
  const channelsPrefetch: Promise<LightningNetworkChannel[]> = this.LightningAssertions.getChannelsPrefetch(abortController);
656
+ const nativeBalancePrefetch = this.prefetchNativeBalanceIfNeeded(chainIdentifier, abortController);
641
657
 
642
658
  const dummySwapData = await this.getDummySwapData(chainIdentifier, useToken, parsedBody.address, parsedBody.paymentHash);
643
659
  abortController.signal.throwIfAborted();
@@ -650,6 +666,8 @@ export class FromBtcLnAbs extends FromBtcBaseSwapHandler<FromBtcLnSwapAbs, FromB
650
666
  //Asynchronously send the node's public key to the client
651
667
  this.sendPublicKeyAsync(responseStream);
652
668
 
669
+ await this.checkNativeBalance(chainIdentifier, nativeBalancePrefetch, abortController.signal);
670
+
653
671
  //Check valid amount specified (min/max)
654
672
  const {
655
673
  amountBD,
@@ -21,10 +21,6 @@ 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
- },
28
24
  invoiceTimeoutSeconds?: number,
29
25
  minCltv: bigint,
30
26
  gracePeriod: bigint,
@@ -49,9 +45,9 @@ export type FromBtcLnAutoRequestType = {
49
45
  export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, FromBtcLnAutoSwapState> {
50
46
  readonly type = SwapHandlerType.FROM_BTCLN_AUTO;
51
47
  readonly swapType = ChainSwapType.HTLC;
48
+ readonly inflightSwapStates = new Set([FromBtcLnAutoSwapState.TXS_SENT, FromBtcLnAutoSwapState.COMMITED, FromBtcLnAutoSwapState.CLAIMED]);
52
49
 
53
50
  activeSubscriptions: Set<string> = new Set<string>();
54
- swapsWithInflighHtlcs: Set<FromBtcLnAutoSwap> = new Set();
55
51
 
56
52
  readonly config: FromBtcLnAutoConfig;
57
53
  readonly lightning: ILightningWallet;
@@ -114,8 +110,6 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
114
110
  }
115
111
 
116
112
  if(swap.state===FromBtcLnAutoSwapState.TXS_SENT || swap.state===FromBtcLnAutoSwapState.COMMITED) {
117
- this.swapsWithInflighHtlcs.add(swap);
118
-
119
113
  const onchainStatus = await swapContract.getCommitStatus(signer.getAddress(), swap.data);
120
114
  const state: FromBtcLnAutoSwapState = swap.state as FromBtcLnAutoSwapState;
121
115
  if(onchainStatus.type===SwapCommitStateType.PAID) {
@@ -371,7 +365,7 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
371
365
  if(invoiceData.state!==FromBtcLnAutoSwapState.RECEIVED) return;
372
366
 
373
367
  try {
374
- this.checkTooManyInflightHtlcs();
368
+ this.checkTooManyInflightSwaps();
375
369
  } catch (e) {
376
370
  if(isDefinedRuntimeError(e) && invoiceData.metadata!=null) invoiceData.metadata.htlcOfferError = e;
377
371
  if(invoiceData.state===FromBtcLnAutoSwapState.RECEIVED) await this.cancelSwapAndInvoice(invoiceData);
@@ -389,15 +383,10 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
389
383
  //Create abort controller for parallel fetches
390
384
  const abortController = new AbortController();
391
385
 
392
- //Minimum reserve of native token to be kept in the wallet
393
- const minNativeTokenReserve: bigint = this.config.minNativeBalances?.[invoiceData.chainIdentifier] ?? 0n;
394
-
395
386
  //Pre-fetch data
396
387
  const balancePrefetch: Promise<bigint> = this.getBalancePrefetch(invoiceData.chainIdentifier, useToken, abortController);
397
388
  const gasTokenBalancePrefetch: Promise<bigint> = invoiceData.getTotalOutputGasAmount()===0n || useToken===gasToken ?
398
389
  null : this.getBalancePrefetch(invoiceData.chainIdentifier, gasToken, abortController);
399
- const nativeBalancePrefetch: Promise<bigint> = minNativeTokenReserve===0n ?
400
- null : this.getBalancePrefetch(invoiceData.chainIdentifier, chainInterface.getNativeCurrencyAddress(), abortController, false);
401
390
 
402
391
  if(await swapContract.getInitAuthorizationExpiry(invoiceData.data, invoiceData) < Date.now()) {
403
392
  if(invoiceData.metadata!=null) invoiceData.metadata.htlcOfferError = "Init authorization expired, before being sent!";
@@ -407,16 +396,6 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
407
396
  return false;
408
397
  }
409
398
 
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
-
420
399
  try {
421
400
  //Check if we have enough liquidity to proceed
422
401
  if(useToken===gasToken) {
@@ -444,14 +423,13 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
444
423
  if(invoiceData.state===FromBtcLnAutoSwapState.RECEIVED) {
445
424
  //Re-check the current HTLC count
446
425
  try {
447
- this.checkTooManyInflightHtlcs();
426
+ this.checkTooManyInflightSwaps();
448
427
  } catch (e) {
449
428
  if(isDefinedRuntimeError(e) && invoiceData.metadata!=null) invoiceData.metadata.htlcOfferError = e;
450
429
  if(invoiceData.state===FromBtcLnAutoSwapState.RECEIVED) await this.cancelSwapAndInvoice(invoiceData);
451
430
  throw e;
452
431
  }
453
- this.swapLogger.debug(invoiceData, `offerHtlc(): Sending HTLC offer, current in flight HTLCs: ${this.swapsWithInflighHtlcs.size}, invoice: `, invoiceData.pr);
454
- this.swapsWithInflighHtlcs.add(invoiceData);
432
+ this.swapLogger.debug(invoiceData, `offerHtlc(): Sending HTLC offer, current in flight swaps: ${this.inflightSwaps.size}, invoice: `, invoiceData.pr);
455
433
  //Setting the state variable is done outside the promise, so is done synchronously
456
434
  await invoiceData.setState(FromBtcLnAutoSwapState.TXS_SENT);
457
435
  await this.saveSwapData(invoiceData);
@@ -601,14 +579,6 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
601
579
  }
602
580
  }
603
581
 
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
-
612
582
  startRestServer(restServer: Express) {
613
583
 
614
584
  restServer.use(this.path+"/createInvoice", serverParamDecoder(10*1000));
@@ -697,7 +667,7 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
697
667
 
698
668
  //Check request params
699
669
  this.checkDescriptionHash(parsedBody.descriptionHash);
700
- this.checkTooManyInflightHtlcs();
670
+ this.checkTooManyInflightSwaps();
701
671
  const fees = await this.AmountAssertions.preCheckFromBtcAmounts(this.type, request, requestedAmount, gasTokenAmount);
702
672
  metadata.times.requestChecked = Date.now();
703
673
 
@@ -705,9 +675,6 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
705
675
  const responseStream = res.responseStream;
706
676
  const abortController = getAbortController(responseStream);
707
677
 
708
- //Minimum reserve of native token to be kept in the wallet
709
- const minNativeTokenReserve: bigint = this.config.minNativeBalances?.[chainIdentifier] ?? 0n;
710
-
711
678
  //Pre-fetch data
712
679
  const {
713
680
  pricePrefetchPromise,
@@ -716,8 +683,7 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
716
683
  const balancePrefetch: Promise<bigint> = this.getBalancePrefetch(chainIdentifier, useToken, abortController);
717
684
  const gasTokenBalancePrefetch: Promise<bigint> = gasTokenAmount.amount===0n || useToken===gasToken ?
718
685
  null : this.getBalancePrefetch(chainIdentifier, gasToken, abortController);
719
- const nativeTokenBalancePrefetch: Promise<bigint> = minNativeTokenReserve===0n ?
720
- null : this.getBalancePrefetch(chainIdentifier, chainInterface.getNativeCurrencyAddress(), abortController, false);
686
+ const nativeTokenBalancePrefetch: Promise<bigint> = this.prefetchNativeBalanceIfNeeded(chainIdentifier, abortController);
721
687
  const channelsPrefetch: Promise<LightningNetworkChannel[]> = this.LightningAssertions.getChannelsPrefetch(abortController);
722
688
 
723
689
  //Asynchronously send the node's public key to the client
@@ -744,7 +710,7 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
744
710
  const totalBtcInput = amountBD + amountBDgas;
745
711
 
746
712
  //Check if we have at least the minimum needed native balance
747
- await this.checkBalance(minNativeTokenReserve, nativeTokenBalancePrefetch, abortController.signal);
713
+ await this.checkNativeBalance(chainIdentifier, nativeTokenBalancePrefetch, abortController.signal);
748
714
 
749
715
  //Check if we have enough funds to honor the request
750
716
  if(useToken===gasToken) {
@@ -896,10 +862,5 @@ export class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, Fro
896
862
  };
897
863
  }
898
864
 
899
- protected async removeSwapData(swap: FromBtcLnAutoSwap, ultimateState?: FromBtcLnAutoSwapState) {
900
- this.swapsWithInflighHtlcs.delete(swap);
901
- return super.removeSwapData(swap, ultimateState);
902
- }
903
-
904
865
  }
905
866
 
@@ -59,6 +59,7 @@ const MAX_PARALLEL_TX_PROCESSED = 10;
59
59
  export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState> {
60
60
  readonly type = SwapHandlerType.TO_BTC;
61
61
  readonly swapType = ChainSwapType.CHAIN_NONCED;
62
+ readonly inflightSwapStates = new Set([ToBtcSwapState.BTC_SENDING, ToBtcSwapState.BTC_SENT]);
62
63
 
63
64
  activeSubscriptions: {[txId: string]: ToBtcSwapAbs} = {};
64
65
  bitcoinRpc: BitcoinRpc<BtcBlock>;
@@ -358,6 +359,7 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
358
359
  // e.g. that 2 payouts share the same input and would effectively double-spend each other
359
360
  return this.bitcoin.execute(async () => {
360
361
  //Run checks
362
+ this.checkTooManyInflightSwaps();
361
363
  this.checkExpiresTooSoon(swap);
362
364
  if(swap.metadata!=null) swap.metadata.times.payCLTVChecked = Date.now();
363
365
 
@@ -672,6 +674,7 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
672
674
 
673
675
  const responseStream = res.responseStream;
674
676
 
677
+ this.checkTooManyInflightSwaps();
675
678
  this.checkNonceValid(parsedBody.nonce);
676
679
  this.checkConfirmationTarget(parsedBody.confirmationTarget);
677
680
  this.checkRequiredConfirmations(parsedBody.confirmations);
@@ -685,6 +688,9 @@ export class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState>
685
688
  const abortController = getAbortController(responseStream);
686
689
 
687
690
  const {pricePrefetchPromise, signDataPrefetchPromise} = this.getToBtcPrefetches(chainIdentifier, useToken, responseStream, abortController);
691
+ const nativeBalancePrefetch = this.prefetchNativeBalanceIfNeeded(chainIdentifier, abortController);
692
+
693
+ await this.checkNativeBalance(chainIdentifier, nativeBalancePrefetch, abortController.signal);
688
694
 
689
695
  const {
690
696
  amountBD,
@@ -86,11 +86,11 @@ export type ToBtcLnRequestType = {
86
86
  * Swap handler handling to BTCLN swaps using submarine swaps
87
87
  */
88
88
  export class ToBtcLnAbs extends ToBtcBaseSwapHandler<ToBtcLnSwapAbs, ToBtcLnSwapState> {
89
-
90
- activeSubscriptions: Set<string> = new Set<string>();
91
-
92
89
  readonly type = SwapHandlerType.TO_BTCLN;
93
90
  readonly swapType = ChainSwapType.HTLC;
91
+ readonly inflightSwapStates = new Set([ToBtcLnSwapState.COMMITED, ToBtcLnSwapState.PAID]);
92
+
93
+ activeSubscriptions: Set<string> = new Set<string>();
94
94
 
95
95
  readonly config: ToBtcLnConfig & {minTsSendCltv: bigint};
96
96
 
@@ -391,6 +391,18 @@ export class ToBtcLnAbs extends ToBtcBaseSwapHandler<ToBtcLnSwapAbs, ToBtcLnSwap
391
391
  }
392
392
 
393
393
  if(swap.state===ToBtcLnSwapState.SAVED) {
394
+ try {
395
+ this.checkTooManyInflightSwaps();
396
+ } catch (e) {
397
+ this.swapLogger.error(swap, "processInitialized(): checking too many inflight swaps error: ", e);
398
+ if(isDefinedRuntimeError(e)) {
399
+ if(swap.metadata!=null) swap.metadata.payError = e;
400
+ await swap.setState(ToBtcLnSwapState.NON_PAYABLE);
401
+ await this.saveSwapData(swap);
402
+ return;
403
+ } else throw e;
404
+ }
405
+
394
406
  await swap.setState(ToBtcLnSwapState.COMMITED);
395
407
  await this.saveSwapData(swap);
396
408
  try {
@@ -690,11 +702,13 @@ export class ToBtcLnAbs extends ToBtcBaseSwapHandler<ToBtcLnSwapAbs, ToBtcLnSwap
690
702
  };
691
703
  }
692
704
 
705
+ this.checkTooManyInflightSwaps();
706
+ const parsedAuth = this.checkExactInAuthorization(parsedBody.reqId);
707
+
693
708
  const responseStream = res.responseStream;
694
709
  const abortSignal = responseStream.getAbortSignal();
695
710
 
696
711
  //Check request params
697
- const parsedAuth = this.checkExactInAuthorization(parsedBody.reqId);
698
712
  const {parsedPR, halfConfidence} = await this.checkPaymentRequest(parsedAuth.chainIdentifier, parsedBody.pr);
699
713
  await this.checkPaymentRequestMatchesInitial(parsedBody.pr, parsedAuth);
700
714
 
@@ -836,6 +850,7 @@ export class ToBtcLnAbs extends ToBtcBaseSwapHandler<ToBtcLnSwapAbs, ToBtcLnSwap
836
850
  const currentTimestamp: bigint = BigInt(Math.floor(Date.now()/1000));
837
851
 
838
852
  //Check request params
853
+ this.checkTooManyInflightSwaps();
839
854
  this.checkAmount(parsedBody.amount, parsedBody.exactIn);
840
855
  this.checkMaxFee(parsedBody.maxFee);
841
856
  this.checkExpiry(parsedBody.expiryTimestamp, currentTimestamp);
@@ -854,11 +869,15 @@ export class ToBtcLnAbs extends ToBtcBaseSwapHandler<ToBtcLnSwapAbs, ToBtcLnSwap
854
869
 
855
870
  //Pre-fetch
856
871
  const {pricePrefetchPromise, signDataPrefetchPromise} = this.getToBtcPrefetches(chainIdentifier, useToken, responseStream, abortController);
872
+ const nativeBalancePrefetch = this.prefetchNativeBalanceIfNeeded(chainIdentifier, abortController);
857
873
 
858
874
  //Check if prior payment has been made
859
875
  await this.LightningAssertions.checkPriorPayment(parsedPR.id, abortController.signal);
860
876
  metadata.times.priorPaymentChecked = Date.now();
861
877
 
878
+ //Check if we still have enough native balance
879
+ await this.checkNativeBalance(chainIdentifier, nativeBalancePrefetch, abortController.signal);
880
+
862
881
  //Check amounts
863
882
  const {
864
883
  amountBD,