@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
@@ -30,6 +30,10 @@ export type SwapBaseConfig = {
30
30
  initAuthorizationTimeouts?: {
31
31
  [chainId: string]: number;
32
32
  };
33
+ minNativeBalances?: {
34
+ [chainId: string]: bigint;
35
+ };
36
+ maxInflightSwaps?: number;
33
37
  bitcoinBlocktime: bigint;
34
38
  baseFee: bigint;
35
39
  feePPM: bigint;
@@ -76,7 +80,9 @@ export declare abstract class SwapHandler<V extends SwapHandlerSwap<S> = SwapHan
76
80
  [chainId: string]: Set<string>;
77
81
  };
78
82
  readonly swapPricing: ISwapPrice;
83
+ abstract readonly inflightSwapStates: Set<S>;
79
84
  abstract config: SwapBaseConfig;
85
+ inflightSwaps: Set<string>;
80
86
  logger: LoggerType;
81
87
  protected swapLogger: {
82
88
  debug: (swap: SwapHandlerSwap, msg: string, ...args: any) => void;
@@ -114,6 +120,28 @@ export declare abstract class SwapHandler<V extends SwapHandlerSwap<S> = SwapHan
114
120
  */
115
121
  protected removeSwapData(swap: V, ultimateState?: S): Promise<void>;
116
122
  protected saveSwapData(swap: V): Promise<void>;
123
+ /**
124
+ * Pre-fetches native balance to further check if we have enough reserve in a native token
125
+ *
126
+ * @param chainIdentifier
127
+ * @param abortController
128
+ * @protected
129
+ */
130
+ protected prefetchNativeBalanceIfNeeded(chainIdentifier: string, abortController: AbortController): Promise<bigint> | null;
131
+ /**
132
+ * Checks if we have enough native balance to facilitate swaps
133
+ *
134
+ * @param chainIdentifier
135
+ * @param balancePrefetch
136
+ * @param signal
137
+ * @throws {DefinedRuntimeError} will throw an error if there are not enough funds in the vault
138
+ */
139
+ protected checkNativeBalance(chainIdentifier: string, balancePrefetch: Promise<bigint>, signal: AbortSignal | null): Promise<void>;
140
+ /**
141
+ * Checks whether there are too many swaps in-flight currently
142
+ * @private
143
+ */
144
+ protected checkTooManyInflightSwaps(): void;
117
145
  /**
118
146
  * Checks if we have enough balance of the token in the swap vault
119
147
  *
@@ -19,6 +19,7 @@ var SwapHandlerType;
19
19
  */
20
20
  class SwapHandler {
21
21
  constructor(storageDirectory, path, chainsData, swapPricing) {
22
+ this.inflightSwaps = new Set();
22
23
  this.logger = (0, Utils_1.getLogger)(() => "SwapHandler(" + this.type + "): ");
23
24
  this.swapLogger = {
24
25
  debug: (swap, msg, ...args) => this.logger.debug(swap.getIdentifier() + ": " + msg, ...args),
@@ -65,6 +66,8 @@ class SwapHandler {
65
66
  await this.storageManager.removeData(hash, sequence);
66
67
  await this.storageManager.saveData(swap.getIdentifierHash(), swap.getSequence(), swap);
67
68
  }
69
+ if (this.inflightSwapStates.has(swap.state))
70
+ this.inflightSwaps.add(swap.getIdentifier());
68
71
  }
69
72
  }
70
73
  /**
@@ -74,6 +77,8 @@ class SwapHandler {
74
77
  * @param ultimateState set the ultimate state of the swap before removing
75
78
  */
76
79
  async removeSwapData(swap, ultimateState) {
80
+ this.inflightSwaps.delete(swap.getIdentifier());
81
+ this.logger.debug("removeSwapData(): Removing in-flight swap, current in-flight swaps: " + this.inflightSwaps.size);
77
82
  if (ultimateState != null)
78
83
  await swap.setState(ultimateState);
79
84
  if (swap != null)
@@ -82,8 +87,69 @@ class SwapHandler {
82
87
  await this.storageManager.removeData(swap.getIdentifierHash(), swap.getSequence());
83
88
  }
84
89
  async saveSwapData(swap) {
90
+ if (this.inflightSwapStates.has(swap.state)) {
91
+ this.inflightSwaps.add(swap.getIdentifier());
92
+ this.logger.debug("removeSwapData(): Adding in-flight swap, current in-flight swaps: " + this.inflightSwaps.size);
93
+ }
94
+ else {
95
+ this.inflightSwaps.delete(swap.getIdentifier());
96
+ this.logger.debug("removeSwapData(): Removing in-flight swap, current in-flight swaps: " + this.inflightSwaps.size);
97
+ }
85
98
  await this.storageManager.saveData(swap.getIdentifierHash(), swap.getSequence(), swap);
86
99
  }
100
+ /**
101
+ * Pre-fetches native balance to further check if we have enough reserve in a native token
102
+ *
103
+ * @param chainIdentifier
104
+ * @param abortController
105
+ * @protected
106
+ */
107
+ prefetchNativeBalanceIfNeeded(chainIdentifier, abortController) {
108
+ const minNativeTokenReserve = this.config.minNativeBalances?.[chainIdentifier] ?? 0n;
109
+ if (minNativeTokenReserve === 0n)
110
+ return null;
111
+ const { chainInterface, signer } = this.getChain(chainIdentifier);
112
+ return chainInterface.getBalance(signer.getAddress(), chainInterface.getNativeCurrencyAddress()).catch(e => {
113
+ this.logger.error("getBalancePrefetch(): balancePrefetch error: ", e);
114
+ abortController.abort(e);
115
+ return null;
116
+ });
117
+ }
118
+ /**
119
+ * Checks if we have enough native balance to facilitate swaps
120
+ *
121
+ * @param chainIdentifier
122
+ * @param balancePrefetch
123
+ * @param signal
124
+ * @throws {DefinedRuntimeError} will throw an error if there are not enough funds in the vault
125
+ */
126
+ async checkNativeBalance(chainIdentifier, balancePrefetch, signal) {
127
+ if (signal != null)
128
+ signal.throwIfAborted();
129
+ const minNativeTokenReserve = this.config.minNativeBalances?.[chainIdentifier] ?? 0n;
130
+ if (minNativeTokenReserve === 0n)
131
+ return;
132
+ const balance = await balancePrefetch;
133
+ if (balance == null || balance < minNativeTokenReserve) {
134
+ throw {
135
+ code: 20012,
136
+ msg: "LP ran out of native token to cover gas fees"
137
+ };
138
+ }
139
+ }
140
+ /**
141
+ * Checks whether there are too many swaps in-flight currently
142
+ * @private
143
+ */
144
+ checkTooManyInflightSwaps() {
145
+ if (this.config.maxInflightSwaps == null)
146
+ return;
147
+ if (this.inflightSwaps.size >= this.config.maxInflightSwaps)
148
+ throw {
149
+ code: 20013,
150
+ msg: "LP has too many in-flight swaps, retry later!"
151
+ };
152
+ }
87
153
  /**
88
154
  * Checks if we have enough balance of the token in the swap vault
89
155
  *
@@ -23,6 +23,7 @@ export type FromBtcRequestType = {
23
23
  export declare class FromBtcAbs extends FromBtcBaseSwapHandler<FromBtcSwapAbs, FromBtcSwapState> {
24
24
  readonly type = SwapHandlerType.FROM_BTC;
25
25
  readonly swapType = ChainSwapType.CHAIN;
26
+ readonly inflightSwapStates: Set<FromBtcSwapState>;
26
27
  readonly config: FromBtcConfig & {
27
28
  swapTsCsvDelta: bigint;
28
29
  };
@@ -18,6 +18,7 @@ class FromBtcAbs extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
18
18
  super(storageDirectory, path, chains, swapPricing, config);
19
19
  this.type = SwapHandler_1.SwapHandlerType.FROM_BTC;
20
20
  this.swapType = base_1.ChainSwapType.CHAIN;
21
+ this.inflightSwapStates = new Set([FromBtcSwapAbs_1.FromBtcSwapState.COMMITED]);
21
22
  this.bitcoin = bitcoin;
22
23
  this.config = {
23
24
  ...config,
@@ -225,6 +226,7 @@ class FromBtcAbs extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
225
226
  };
226
227
  const useToken = parsedBody.token;
227
228
  //Check request params
229
+ this.checkTooManyInflightSwaps();
228
230
  this.checkSequence(parsedBody.sequence);
229
231
  const fees = await this.AmountAssertions.preCheckFromBtcAmounts(this.type, request, requestedAmount);
230
232
  metadata.times.requestChecked = Date.now();
@@ -235,9 +237,11 @@ class FromBtcAbs extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
235
237
  const { pricePrefetchPromise, gasTokenPricePrefetchPromise, depositTokenPricePrefetchPromise } = this.getFromBtcPricePrefetches(chainIdentifier, useToken, depositToken, abortController);
236
238
  const balancePrefetch = this.getBalancePrefetch(chainIdentifier, useToken, abortController);
237
239
  const signDataPrefetchPromise = this.getSignDataPrefetch(chainIdentifier, abortController, responseStream);
240
+ const nativeBalancePrefetch = this.prefetchNativeBalanceIfNeeded(chainIdentifier, abortController);
238
241
  const dummySwapData = await this.getDummySwapData(chainIdentifier, useToken, parsedBody.address);
239
242
  abortController.signal.throwIfAborted();
240
243
  const baseSDPromise = this.getBaseSecurityDepositPrefetch(chainIdentifier, dummySwapData, depositToken, gasTokenPricePrefetchPromise, depositTokenPricePrefetchPromise, abortController);
244
+ await this.checkNativeBalance(chainIdentifier, nativeBalancePrefetch, abortController.signal);
241
245
  //Check valid amount specified (min/max)
242
246
  const { amountBD, swapFee, swapFeeInToken, totalInToken, securityDepositApyPPM, securityDepositBaseMultiplierPPM } = await this.AmountAssertions.checkFromBtcAmount(this.type, request, { ...requestedAmount, pricePrefetch: pricePrefetchPromise }, fees, abortController.signal);
243
247
  metadata.times.priceCalculated = Date.now();
@@ -26,6 +26,7 @@ export type FromBtcLnRequestType = {
26
26
  export declare class FromBtcLnAbs extends FromBtcBaseSwapHandler<FromBtcLnSwapAbs, FromBtcLnSwapState> {
27
27
  readonly type = SwapHandlerType.FROM_BTCLN;
28
28
  readonly swapType = ChainSwapType.HTLC;
29
+ readonly inflightSwapStates: Set<FromBtcLnSwapState>;
29
30
  readonly config: FromBtcLnConfig;
30
31
  readonly lightning: ILightningWallet;
31
32
  readonly LightningAssertions: LightningAssertions;
@@ -19,6 +19,7 @@ class FromBtcLnAbs extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
19
19
  super(storageDirectory, path, chains, swapPricing, config);
20
20
  this.type = SwapHandler_1.SwapHandlerType.FROM_BTCLN;
21
21
  this.swapType = base_1.ChainSwapType.HTLC;
22
+ this.inflightSwapStates = new Set([FromBtcLnSwapAbs_1.FromBtcLnSwapState.RECEIVED, FromBtcLnSwapAbs_1.FromBtcLnSwapState.COMMITED, FromBtcLnSwapAbs_1.FromBtcLnSwapState.CLAIMED]);
22
23
  this.config = config;
23
24
  this.config.invoiceTimeoutSeconds = this.config.invoiceTimeoutSeconds || 90;
24
25
  this.lightning = lightning;
@@ -48,11 +49,6 @@ class FromBtcLnAbs extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
48
49
  catch (e) {
49
50
  this.swapLogger.error(swap, "processPastSwap(state=CREATED): htlcReceived error", e);
50
51
  }
51
- // @ts-ignore Previous call (htlcReceived) mutates the state of the swap, so this is valid
52
- if (swap.state === FromBtcLnSwapAbs_1.FromBtcLnSwapState.CANCELED) {
53
- this.swapLogger.info(swap, "processPastSwap(state=CREATED): invoice CANCELED after htlcReceived(), cancelling, invoice: " + swap.pr);
54
- return "CANCEL";
55
- }
56
52
  return null;
57
53
  }
58
54
  if (swap.state === FromBtcLnSwapAbs_1.FromBtcLnSwapState.RECEIVED || swap.state === FromBtcLnSwapAbs_1.FromBtcLnSwapState.COMMITED) {
@@ -248,6 +244,16 @@ class FromBtcLnAbs extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
248
244
  invoiceData.metadata.times.htlcReceived = Date.now();
249
245
  const useToken = invoiceData.token;
250
246
  const escrowAmount = invoiceData.totalTokens;
247
+ try {
248
+ this.checkTooManyInflightSwaps();
249
+ }
250
+ catch (e) {
251
+ if ((0, Utils_1.isDefinedRuntimeError)(e) && invoiceData.metadata != null)
252
+ invoiceData.metadata.htlcReceiveError = e;
253
+ if (invoiceData.state === FromBtcLnSwapAbs_1.FromBtcLnSwapState.CREATED)
254
+ await this.cancelSwapAndInvoice(invoiceData);
255
+ throw e;
256
+ }
251
257
  //Create abort controller for parallel fetches
252
258
  const abortController = new AbortController();
253
259
  //Pre-fetch data
@@ -267,6 +273,8 @@ class FromBtcLnAbs extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
267
273
  }
268
274
  catch (e) {
269
275
  if (!abortController.signal.aborted) {
276
+ if ((0, Utils_1.isDefinedRuntimeError)(e) && invoiceData.metadata != null)
277
+ invoiceData.metadata.htlcReceiveError = e;
270
278
  if (invoiceData.state === FromBtcLnSwapAbs_1.FromBtcLnSwapState.CREATED)
271
279
  await this.cancelSwapAndInvoice(invoiceData);
272
280
  }
@@ -285,6 +293,17 @@ class FromBtcLnAbs extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
285
293
  invoiceData.metadata.times.htlcSwapSigned = Date.now();
286
294
  //Important to prevent race condition and issuing 2 signed init messages at the same time
287
295
  if (invoiceData.state === FromBtcLnSwapAbs_1.FromBtcLnSwapState.CREATED) {
296
+ //Re-check right before signing
297
+ try {
298
+ this.checkTooManyInflightSwaps();
299
+ }
300
+ catch (e) {
301
+ if ((0, Utils_1.isDefinedRuntimeError)(e) && invoiceData.metadata != null)
302
+ invoiceData.metadata.htlcReceiveError = e;
303
+ if (invoiceData.state === FromBtcLnSwapAbs_1.FromBtcLnSwapState.CREATED)
304
+ await this.cancelSwapAndInvoice(invoiceData);
305
+ throw e;
306
+ }
288
307
  invoiceData.data = payInvoiceObject;
289
308
  invoiceData.prefix = sigData.prefix;
290
309
  invoiceData.timeout = sigData.timeout;
@@ -500,6 +519,7 @@ class FromBtcLnAbs extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
500
519
  };
501
520
  const useToken = parsedBody.token;
502
521
  //Check request params
522
+ this.checkTooManyInflightSwaps();
503
523
  this.checkDescriptionHash(parsedBody.descriptionHash);
504
524
  const fees = await this.AmountAssertions.preCheckFromBtcAmounts(this.type, request, requestedAmount);
505
525
  metadata.times.requestChecked = Date.now();
@@ -510,11 +530,13 @@ class FromBtcLnAbs extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
510
530
  const { pricePrefetchPromise, gasTokenPricePrefetchPromise, depositTokenPricePrefetchPromise } = this.getFromBtcPricePrefetches(chainIdentifier, useToken, depositToken, abortController);
511
531
  const balancePrefetch = this.getBalancePrefetch(chainIdentifier, useToken, abortController);
512
532
  const channelsPrefetch = this.LightningAssertions.getChannelsPrefetch(abortController);
533
+ const nativeBalancePrefetch = this.prefetchNativeBalanceIfNeeded(chainIdentifier, abortController);
513
534
  const dummySwapData = await this.getDummySwapData(chainIdentifier, useToken, parsedBody.address, parsedBody.paymentHash);
514
535
  abortController.signal.throwIfAborted();
515
536
  const baseSDPromise = this.getBaseSecurityDepositPrefetch(chainIdentifier, dummySwapData, depositToken, gasTokenPricePrefetchPromise, depositTokenPricePrefetchPromise, abortController);
516
537
  //Asynchronously send the node's public key to the client
517
538
  this.sendPublicKeyAsync(responseStream);
539
+ await this.checkNativeBalance(chainIdentifier, nativeBalancePrefetch, abortController.signal);
518
540
  //Check valid amount specified (min/max)
519
541
  const { amountBD, swapFee, swapFeeInToken, totalInToken, securityDepositApyPPM, securityDepositBaseMultiplierPPM } = await this.AmountAssertions.checkFromBtcAmount(this.type, request, { ...requestedAmount, pricePrefetch: pricePrefetchPromise }, fees, abortController.signal);
520
542
  metadata.times.priceCalculated = Date.now();
@@ -8,10 +8,6 @@ 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
- };
15
11
  invoiceTimeoutSeconds?: number;
16
12
  minCltv: bigint;
17
13
  gracePeriod: bigint;
@@ -36,8 +32,8 @@ export type FromBtcLnAutoRequestType = {
36
32
  export declare class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoSwap, FromBtcLnAutoSwapState> {
37
33
  readonly type = SwapHandlerType.FROM_BTCLN_AUTO;
38
34
  readonly swapType = ChainSwapType.HTLC;
35
+ readonly inflightSwapStates: Set<FromBtcLnAutoSwapState>;
39
36
  activeSubscriptions: Set<string>;
40
- swapsWithInflighHtlcs: Set<FromBtcLnAutoSwap>;
41
37
  readonly config: FromBtcLnAutoConfig;
42
38
  readonly lightning: ILightningWallet;
43
39
  readonly LightningAssertions: LightningAssertions;
@@ -110,9 +106,7 @@ export declare class FromBtcLnAuto extends FromBtcBaseSwapHandler<FromBtcLnAutoS
110
106
  * @returns the fetched lightning invoice
111
107
  */
112
108
  private checkInvoiceStatus;
113
- private checkTooManyInflightHtlcs;
114
109
  startRestServer(restServer: Express): void;
115
110
  init(): Promise<void>;
116
111
  getInfoData(): any;
117
- protected removeSwapData(swap: FromBtcLnAutoSwap, ultimateState?: FromBtcLnAutoSwapState): Promise<void>;
118
112
  }
@@ -19,8 +19,8 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
19
19
  super(storageDirectory, path, chains, swapPricing, config);
20
20
  this.type = SwapHandler_1.SwapHandlerType.FROM_BTCLN_AUTO;
21
21
  this.swapType = base_1.ChainSwapType.HTLC;
22
+ this.inflightSwapStates = new Set([FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.TXS_SENT, FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.COMMITED, FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.CLAIMED]);
22
23
  this.activeSubscriptions = new Set();
23
- this.swapsWithInflighHtlcs = new Set();
24
24
  this.config = config;
25
25
  this.config.invoiceTimeoutSeconds = this.config.invoiceTimeoutSeconds || 90;
26
26
  this.lightning = lightning;
@@ -64,7 +64,6 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
64
64
  return null;
65
65
  }
66
66
  if (swap.state === FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.TXS_SENT || swap.state === FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.COMMITED) {
67
- this.swapsWithInflighHtlcs.add(swap);
68
67
  const onchainStatus = await swapContract.getCommitStatus(signer.getAddress(), swap.data);
69
68
  const state = swap.state;
70
69
  if (onchainStatus.type === base_1.SwapCommitStateType.PAID) {
@@ -285,7 +284,7 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
285
284
  if (invoiceData.state !== FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.RECEIVED)
286
285
  return;
287
286
  try {
288
- this.checkTooManyInflightHtlcs();
287
+ this.checkTooManyInflightSwaps();
289
288
  }
290
289
  catch (e) {
291
290
  if ((0, Utils_1.isDefinedRuntimeError)(e) && invoiceData.metadata != null)
@@ -302,14 +301,10 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
302
301
  const { swapContract, signer, chainInterface } = this.getChain(invoiceData.chainIdentifier);
303
302
  //Create abort controller for parallel fetches
304
303
  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;
307
304
  //Pre-fetch data
308
305
  const balancePrefetch = this.getBalancePrefetch(invoiceData.chainIdentifier, useToken, abortController);
309
306
  const gasTokenBalancePrefetch = invoiceData.getTotalOutputGasAmount() === 0n || useToken === gasToken ?
310
307
  null : this.getBalancePrefetch(invoiceData.chainIdentifier, gasToken, abortController);
311
- const nativeBalancePrefetch = minNativeTokenReserve === 0n ?
312
- null : this.getBalancePrefetch(invoiceData.chainIdentifier, chainInterface.getNativeCurrencyAddress(), abortController, false);
313
308
  if (await swapContract.getInitAuthorizationExpiry(invoiceData.data, invoiceData) < Date.now()) {
314
309
  if (invoiceData.metadata != null)
315
310
  invoiceData.metadata.htlcOfferError = "Init authorization expired, before being sent!";
@@ -318,18 +313,6 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
318
313
  }
319
314
  return false;
320
315
  }
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
- }
333
316
  try {
334
317
  //Check if we have enough liquidity to proceed
335
318
  if (useToken === gasToken) {
@@ -360,7 +343,7 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
360
343
  if (invoiceData.state === FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.RECEIVED) {
361
344
  //Re-check the current HTLC count
362
345
  try {
363
- this.checkTooManyInflightHtlcs();
346
+ this.checkTooManyInflightSwaps();
364
347
  }
365
348
  catch (e) {
366
349
  if ((0, Utils_1.isDefinedRuntimeError)(e) && invoiceData.metadata != null)
@@ -369,8 +352,7 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
369
352
  await this.cancelSwapAndInvoice(invoiceData);
370
353
  throw e;
371
354
  }
372
- this.swapLogger.debug(invoiceData, `offerHtlc(): Sending HTLC offer, current in flight HTLCs: ${this.swapsWithInflighHtlcs.size}, invoice: `, invoiceData.pr);
373
- this.swapsWithInflighHtlcs.add(invoiceData);
355
+ this.swapLogger.debug(invoiceData, `offerHtlc(): Sending HTLC offer, current in flight swaps: ${this.inflightSwaps.size}, invoice: `, invoiceData.pr);
374
356
  //Setting the state variable is done outside the promise, so is done synchronously
375
357
  await invoiceData.setState(FromBtcLnAutoSwap_1.FromBtcLnAutoSwapState.TXS_SENT);
376
358
  await this.saveSwapData(invoiceData);
@@ -511,15 +493,6 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
511
493
  return invoice;
512
494
  }
513
495
  }
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
- }
523
496
  startRestServer(restServer) {
524
497
  restServer.use(this.path + "/createInvoice", (0, ServerParamDecoder_1.serverParamDecoder)(10 * 1000));
525
498
  restServer.post(this.path + "/createInvoice", (0, Utils_1.expressHandlerWrapper)(async (req, res) => {
@@ -600,21 +573,18 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
600
573
  const gasToken = parsedBody.gasToken;
601
574
  //Check request params
602
575
  this.checkDescriptionHash(parsedBody.descriptionHash);
603
- this.checkTooManyInflightHtlcs();
576
+ this.checkTooManyInflightSwaps();
604
577
  const fees = await this.AmountAssertions.preCheckFromBtcAmounts(this.type, request, requestedAmount, gasTokenAmount);
605
578
  metadata.times.requestChecked = Date.now();
606
579
  //Create abortController for parallel prefetches
607
580
  const responseStream = res.responseStream;
608
581
  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;
611
582
  //Pre-fetch data
612
583
  const { pricePrefetchPromise, gasTokenPricePrefetchPromise } = this.getFromBtcPricePrefetches(chainIdentifier, useToken, gasToken, abortController);
613
584
  const balancePrefetch = this.getBalancePrefetch(chainIdentifier, useToken, abortController);
614
585
  const gasTokenBalancePrefetch = gasTokenAmount.amount === 0n || useToken === gasToken ?
615
586
  null : this.getBalancePrefetch(chainIdentifier, gasToken, abortController);
616
- const nativeTokenBalancePrefetch = minNativeTokenReserve === 0n ?
617
- null : this.getBalancePrefetch(chainIdentifier, chainInterface.getNativeCurrencyAddress(), abortController, false);
587
+ const nativeTokenBalancePrefetch = this.prefetchNativeBalanceIfNeeded(chainIdentifier, abortController);
618
588
  const channelsPrefetch = this.LightningAssertions.getChannelsPrefetch(abortController);
619
589
  //Asynchronously send the node's public key to the client
620
590
  this.sendPublicKeyAsync(responseStream);
@@ -623,7 +593,7 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
623
593
  metadata.times.priceCalculated = Date.now();
624
594
  const totalBtcInput = amountBD + amountBDgas;
625
595
  //Check if we have at least the minimum needed native balance
626
- await this.checkBalance(minNativeTokenReserve, nativeTokenBalancePrefetch, abortController.signal);
596
+ await this.checkNativeBalance(chainIdentifier, nativeTokenBalancePrefetch, abortController.signal);
627
597
  //Check if we have enough funds to honor the request
628
598
  if (useToken === gasToken) {
629
599
  await this.checkBalance(totalInToken + totalInGasToken, balancePrefetch, abortController.signal);
@@ -734,9 +704,5 @@ class FromBtcLnAuto extends FromBtcBaseSwapHandler_1.FromBtcBaseSwapHandler {
734
704
  gasTokens: mappedDict
735
705
  };
736
706
  }
737
- async removeSwapData(swap, ultimateState) {
738
- this.swapsWithInflighHtlcs.delete(swap);
739
- return super.removeSwapData(swap, ultimateState);
740
- }
741
707
  }
742
708
  exports.FromBtcLnAuto = FromBtcLnAuto;
@@ -32,6 +32,7 @@ export type ToBtcRequestType = {
32
32
  export declare class ToBtcAbs extends ToBtcBaseSwapHandler<ToBtcSwapAbs, ToBtcSwapState> {
33
33
  readonly type = SwapHandlerType.TO_BTC;
34
34
  readonly swapType = ChainSwapType.CHAIN_NONCED;
35
+ readonly inflightSwapStates: Set<ToBtcSwapState>;
35
36
  activeSubscriptions: {
36
37
  [txId: string]: ToBtcSwapAbs;
37
38
  };
@@ -21,6 +21,7 @@ class ToBtcAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
21
21
  super(storageDirectory, path, chainData, swapPricing, config);
22
22
  this.type = SwapHandler_1.SwapHandlerType.TO_BTC;
23
23
  this.swapType = base_1.ChainSwapType.CHAIN_NONCED;
24
+ this.inflightSwapStates = new Set([ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENDING, ToBtcSwapAbs_1.ToBtcSwapState.BTC_SENT]);
24
25
  this.activeSubscriptions = {};
25
26
  this.bitcoinRpc = bitcoinRpc;
26
27
  this.bitcoin = bitcoin;
@@ -277,6 +278,7 @@ class ToBtcAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
277
278
  // e.g. that 2 payouts share the same input and would effectively double-spend each other
278
279
  return this.bitcoin.execute(async () => {
279
280
  //Run checks
281
+ this.checkTooManyInflightSwaps();
280
282
  this.checkExpiresTooSoon(swap);
281
283
  if (swap.metadata != null)
282
284
  swap.metadata.times.payCLTVChecked = Date.now();
@@ -563,6 +565,7 @@ class ToBtcAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
563
565
  };
564
566
  const useToken = parsedBody.token;
565
567
  const responseStream = res.responseStream;
568
+ this.checkTooManyInflightSwaps();
566
569
  this.checkNonceValid(parsedBody.nonce);
567
570
  this.checkConfirmationTarget(parsedBody.confirmationTarget);
568
571
  this.checkRequiredConfirmations(parsedBody.confirmations);
@@ -573,6 +576,8 @@ class ToBtcAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
573
576
  //Initialize abort controller for the parallel async operations
574
577
  const abortController = (0, Utils_1.getAbortController)(responseStream);
575
578
  const { pricePrefetchPromise, signDataPrefetchPromise } = this.getToBtcPrefetches(chainIdentifier, useToken, responseStream, abortController);
579
+ const nativeBalancePrefetch = this.prefetchNativeBalanceIfNeeded(chainIdentifier, abortController);
580
+ await this.checkNativeBalance(chainIdentifier, nativeBalancePrefetch, abortController.signal);
576
581
  const { amountBD, networkFeeData, totalInToken, swapFee, swapFeeInToken, networkFeeInToken } = await this.AmountAssertions.checkToBtcAmount(this.type, request, { ...requestedAmount, pricePrefetch: pricePrefetchPromise }, fees, async (amount) => {
577
582
  metadata.times.amountsChecked = Date.now();
578
583
  const resp = await this.checkAndGetNetworkFee(parsedBody.address, amount);
@@ -55,9 +55,10 @@ export type ToBtcLnRequestType = {
55
55
  * Swap handler handling to BTCLN swaps using submarine swaps
56
56
  */
57
57
  export declare class ToBtcLnAbs extends ToBtcBaseSwapHandler<ToBtcLnSwapAbs, ToBtcLnSwapState> {
58
- activeSubscriptions: Set<string>;
59
58
  readonly type = SwapHandlerType.TO_BTCLN;
60
59
  readonly swapType = ChainSwapType.HTLC;
60
+ readonly inflightSwapStates: Set<ToBtcLnSwapState>;
61
+ activeSubscriptions: Set<string>;
61
62
  readonly config: ToBtcLnConfig & {
62
63
  minTsSendCltv: bigint;
63
64
  };
@@ -18,9 +18,10 @@ const LightningAssertions_1 = require("../../assertions/LightningAssertions");
18
18
  class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
19
19
  constructor(storageDirectory, path, chainData, lightning, swapPricing, config) {
20
20
  super(storageDirectory, path, chainData, swapPricing, config);
21
- this.activeSubscriptions = new Set();
22
21
  this.type = SwapHandler_1.SwapHandlerType.TO_BTCLN;
23
22
  this.swapType = base_1.ChainSwapType.HTLC;
23
+ this.inflightSwapStates = new Set([ToBtcLnSwapAbs_1.ToBtcLnSwapState.COMMITED, ToBtcLnSwapAbs_1.ToBtcLnSwapState.PAID]);
24
+ this.activeSubscriptions = new Set();
24
25
  this.exactInAuths = {};
25
26
  this.lightning = lightning;
26
27
  this.LightningAssertions = new LightningAssertions_1.LightningAssertions(this.logger, lightning);
@@ -288,6 +289,21 @@ class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
288
289
  }
289
290
  }
290
291
  if (swap.state === ToBtcLnSwapAbs_1.ToBtcLnSwapState.SAVED) {
292
+ try {
293
+ this.checkTooManyInflightSwaps();
294
+ }
295
+ catch (e) {
296
+ this.swapLogger.error(swap, "processInitialized(): checking too many inflight swaps error: ", e);
297
+ if ((0, Utils_1.isDefinedRuntimeError)(e)) {
298
+ if (swap.metadata != null)
299
+ swap.metadata.payError = e;
300
+ await swap.setState(ToBtcLnSwapAbs_1.ToBtcLnSwapState.NON_PAYABLE);
301
+ await this.saveSwapData(swap);
302
+ return;
303
+ }
304
+ else
305
+ throw e;
306
+ }
291
307
  await swap.setState(ToBtcLnSwapAbs_1.ToBtcLnSwapState.COMMITED);
292
308
  await this.saveSwapData(swap);
293
309
  try {
@@ -553,10 +569,11 @@ class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
553
569
  msg: "Invalid request body"
554
570
  };
555
571
  }
572
+ this.checkTooManyInflightSwaps();
573
+ const parsedAuth = this.checkExactInAuthorization(parsedBody.reqId);
556
574
  const responseStream = res.responseStream;
557
575
  const abortSignal = responseStream.getAbortSignal();
558
576
  //Check request params
559
- const parsedAuth = this.checkExactInAuthorization(parsedBody.reqId);
560
577
  const { parsedPR, halfConfidence } = await this.checkPaymentRequest(parsedAuth.chainIdentifier, parsedBody.pr);
561
578
  await this.checkPaymentRequestMatchesInitial(parsedBody.pr, parsedAuth);
562
579
  const metadata = parsedAuth.metadata;
@@ -650,6 +667,7 @@ class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
650
667
  const responseStream = res.responseStream;
651
668
  const currentTimestamp = BigInt(Math.floor(Date.now() / 1000));
652
669
  //Check request params
670
+ this.checkTooManyInflightSwaps();
653
671
  this.checkAmount(parsedBody.amount, parsedBody.exactIn);
654
672
  this.checkMaxFee(parsedBody.maxFee);
655
673
  this.checkExpiry(parsedBody.expiryTimestamp, currentTimestamp);
@@ -666,9 +684,12 @@ class ToBtcLnAbs extends ToBtcBaseSwapHandler_1.ToBtcBaseSwapHandler {
666
684
  const abortController = (0, Utils_1.getAbortController)(responseStream);
667
685
  //Pre-fetch
668
686
  const { pricePrefetchPromise, signDataPrefetchPromise } = this.getToBtcPrefetches(chainIdentifier, useToken, responseStream, abortController);
687
+ const nativeBalancePrefetch = this.prefetchNativeBalanceIfNeeded(chainIdentifier, abortController);
669
688
  //Check if prior payment has been made
670
689
  await this.LightningAssertions.checkPriorPayment(parsedPR.id, abortController.signal);
671
690
  metadata.times.priorPaymentChecked = Date.now();
691
+ //Check if we still have enough native balance
692
+ await this.checkNativeBalance(chainIdentifier, nativeBalancePrefetch, abortController.signal);
672
693
  //Check amounts
673
694
  const { amountBD, networkFeeData, totalInToken, swapFee, swapFeeInToken, networkFeeInToken } = await this.AmountAssertions.checkToBtcAmount(this.type, request, { ...requestedAmount, pricePrefetch: pricePrefetchPromise }, fees, async (amountBD) => {
674
695
  //Check if we have enough liquidity to process the swap
@@ -32,6 +32,7 @@ export type SpvVaultPostQuote = {
32
32
  };
33
33
  export declare class SpvVaultSwapHandler extends SwapHandler<SpvVaultSwap, SpvVaultSwapState> {
34
34
  readonly type = SwapHandlerType.FROM_BTC_SPV;
35
+ readonly inflightSwapStates: Set<SpvVaultSwapState>;
35
36
  readonly bitcoin: IBitcoinWallet;
36
37
  readonly bitcoinRpc: BitcoinRpc<BtcBlock>;
37
38
  readonly vaultSigner: ISpvVaultSigner;
@@ -19,6 +19,7 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
19
19
  constructor(storageDirectory, vaultStorage, path, chainsData, swapPricing, bitcoin, bitcoinRpc, spvVaultSigner, config) {
20
20
  super(storageDirectory, path, chainsData, swapPricing);
21
21
  this.type = SwapHandler_1.SwapHandlerType.FROM_BTC_SPV;
22
+ this.inflightSwapStates = new Set([SpvVaultSwap_1.SpvVaultSwapState.SIGNED, SpvVaultSwap_1.SpvVaultSwapState.SENT, SpvVaultSwap_1.SpvVaultSwapState.BTC_CONFIRMED]);
22
23
  this.btcTxIdIndex = new Map();
23
24
  this.bitcoinRpc = bitcoinRpc;
24
25
  this.bitcoin = bitcoin;
@@ -246,6 +247,7 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
246
247
  };
247
248
  const useToken = parsedBody.token;
248
249
  const gasToken = parsedBody.gasToken;
250
+ this.checkTooManyInflightSwaps();
249
251
  //Check request params
250
252
  const fees = await this.AmountAssertions.preCheckFromBtcAmounts(this.type, request, requestedAmount, gasTokenAmount);
251
253
  metadata.times.requestChecked = Date.now();
@@ -254,6 +256,8 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
254
256
  const abortController = (0, Utils_1.getAbortController)(responseStream);
255
257
  //Pre-fetch data
256
258
  const { pricePrefetchPromise, gasTokenPricePrefetchPromise } = this.getPricePrefetches(chainIdentifier, useToken, gasToken, abortController);
259
+ const nativeBalancePrefetch = this.prefetchNativeBalanceIfNeeded(chainIdentifier, abortController);
260
+ await this.checkNativeBalance(chainIdentifier, nativeBalancePrefetch, abortController.signal);
257
261
  //Check valid amount specified (min/max)
258
262
  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 });
259
263
  metadata.times.priceCalculated = Date.now();
@@ -339,6 +343,7 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
339
343
  const metadata = swap.metadata;
340
344
  metadata.times ?? (metadata.times = {});
341
345
  metadata.times.requestReceived = requestReceived;
346
+ this.checkTooManyInflightSwaps();
342
347
  const vault = await this.Vaults.getVault(swap.chainIdentifier, swap.vaultOwner, swap.vaultId);
343
348
  if (vault == null || !vault.isReady()) {
344
349
  throw {
@@ -452,6 +457,8 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
452
457
  msg: "Invalid quote ID, not found or expired!"
453
458
  };
454
459
  }
460
+ //Double check in-flight swap count
461
+ this.checkTooManyInflightSwaps();
455
462
  swap.btcTxId = signedTx.id;
456
463
  swap.state = SpvVaultSwap_1.SpvVaultSwapState.SIGNED;
457
464
  swap.sending = true;
@@ -480,6 +487,8 @@ class SpvVaultSwapHandler extends SwapHandler_1.SwapHandler {
480
487
  swap.sending = false;
481
488
  vault.removeWithdrawal(data);
482
489
  await this.Vaults.saveVault(vault);
490
+ if ((0, Utils_1.isDefinedRuntimeError)(e) && swap.metadata != null)
491
+ swap.metadata.postQuoteError = e;
483
492
  await this.removeSwapData(swap, SpvVaultSwap_1.SpvVaultSwapState.FAILED);
484
493
  throw e;
485
494
  }
@@ -20,6 +20,7 @@ export type FromBtcTrustedRequestType = {
20
20
  };
21
21
  export declare class FromBtcTrusted extends SwapHandler<FromBtcTrustedSwap, FromBtcTrustedSwapState> {
22
22
  readonly type = SwapHandlerType.FROM_BTC_TRUSTED;
23
+ readonly inflightSwapStates: Set<FromBtcTrustedSwapState>;
23
24
  readonly config: FromBtcTrustedConfig;
24
25
  readonly bitcoin: IBitcoinWallet;
25
26
  readonly bitcoinRpc: BitcoinRpc<BtcBlock>;