@atomiqlabs/sdk 8.7.7 → 8.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/api/index.d.ts +1 -0
  2. package/api/index.js +3 -0
  3. package/dist/ApiList.d.ts +37 -0
  4. package/dist/ApiList.js +30 -0
  5. package/dist/api/ApiEndpoints.d.ts +393 -0
  6. package/dist/api/ApiEndpoints.js +2 -0
  7. package/dist/api/ApiParser.d.ts +10 -0
  8. package/dist/api/ApiParser.js +134 -0
  9. package/dist/api/ApiTypes.d.ts +157 -0
  10. package/dist/api/ApiTypes.js +75 -0
  11. package/dist/api/SerializedAction.d.ts +40 -0
  12. package/dist/api/SerializedAction.js +59 -0
  13. package/dist/api/SwapperApi.d.ts +50 -0
  14. package/dist/api/SwapperApi.js +431 -0
  15. package/dist/api/index.d.ts +5 -0
  16. package/dist/api/index.js +24 -0
  17. package/dist/bitcoin/coinselect2/accumulative.d.ts +1 -0
  18. package/dist/bitcoin/coinselect2/accumulative.js +1 -1
  19. package/dist/bitcoin/coinselect2/blackjack.d.ts +1 -0
  20. package/dist/bitcoin/coinselect2/blackjack.js +1 -1
  21. package/dist/bitcoin/coinselect2/index.d.ts +3 -2
  22. package/dist/bitcoin/coinselect2/index.js +2 -2
  23. package/dist/bitcoin/coinselect2/utils.d.ts +7 -2
  24. package/dist/bitcoin/coinselect2/utils.js +45 -10
  25. package/dist/bitcoin/wallet/BitcoinWallet.d.ts +8 -25
  26. package/dist/bitcoin/wallet/BitcoinWallet.js +31 -18
  27. package/dist/bitcoin/wallet/IBitcoinWallet.d.ts +40 -2
  28. package/dist/bitcoin/wallet/SingleAddressBitcoinWallet.d.ts +7 -2
  29. package/dist/bitcoin/wallet/SingleAddressBitcoinWallet.js +10 -4
  30. package/dist/events/UnifiedSwapEventListener.d.ts +4 -3
  31. package/dist/events/UnifiedSwapEventListener.js +8 -2
  32. package/dist/http/HttpUtils.d.ts +4 -2
  33. package/dist/http/HttpUtils.js +10 -4
  34. package/dist/http/paramcoders/client/StreamingFetchPromise.d.ts +2 -1
  35. package/dist/http/paramcoders/client/StreamingFetchPromise.js +3 -2
  36. package/dist/index.d.ts +1 -0
  37. package/dist/index.js +1 -0
  38. package/dist/intermediaries/IntermediaryDiscovery.d.ts +7 -2
  39. package/dist/intermediaries/IntermediaryDiscovery.js +4 -4
  40. package/dist/intermediaries/apis/IntermediaryAPI.d.ts +182 -15
  41. package/dist/intermediaries/apis/IntermediaryAPI.js +192 -31
  42. package/dist/intermediaries/auth/SignedKeyBasedAuth.d.ts +14 -0
  43. package/dist/intermediaries/auth/SignedKeyBasedAuth.js +68 -0
  44. package/dist/storage/IUnifiedStorage.d.ts +45 -3
  45. package/dist/storage/UnifiedSwapStorage.d.ts +8 -2
  46. package/dist/storage/UnifiedSwapStorage.js +46 -8
  47. package/dist/swapper/Swapper.d.ts +77 -4
  48. package/dist/swapper/Swapper.js +117 -25
  49. package/dist/swapper/SwapperUtils.d.ts +18 -2
  50. package/dist/swapper/SwapperUtils.js +39 -1
  51. package/dist/swaps/ISwap.d.ts +70 -9
  52. package/dist/swaps/ISwap.js +28 -6
  53. package/dist/swaps/ISwapWrapper.d.ts +11 -1
  54. package/dist/swaps/ISwapWrapper.js +23 -3
  55. package/dist/swaps/escrow_swaps/IEscrowSwap.d.ts +1 -1
  56. package/dist/swaps/escrow_swaps/IEscrowSwap.js +4 -2
  57. package/dist/swaps/escrow_swaps/IEscrowSwapWrapper.d.ts +2 -1
  58. package/dist/swaps/escrow_swaps/IEscrowSwapWrapper.js +2 -2
  59. package/dist/swaps/escrow_swaps/frombtc/IFromBTCLNWrapper.d.ts +3 -1
  60. package/dist/swaps/escrow_swaps/frombtc/IFromBTCLNWrapper.js +3 -2
  61. package/dist/swaps/escrow_swaps/frombtc/ln/FromBTCLNSwap.d.ts +47 -31
  62. package/dist/swaps/escrow_swaps/frombtc/ln/FromBTCLNSwap.js +201 -67
  63. package/dist/swaps/escrow_swaps/frombtc/ln/FromBTCLNWrapper.d.ts +3 -1
  64. package/dist/swaps/escrow_swaps/frombtc/ln/FromBTCLNWrapper.js +6 -6
  65. package/dist/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoSwap.d.ts +82 -15
  66. package/dist/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoSwap.js +304 -98
  67. package/dist/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoWrapper.d.ts +3 -1
  68. package/dist/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoWrapper.js +6 -6
  69. package/dist/swaps/escrow_swaps/frombtc/onchain/FromBTCSwap.d.ts +75 -42
  70. package/dist/swaps/escrow_swaps/frombtc/onchain/FromBTCSwap.js +424 -87
  71. package/dist/swaps/escrow_swaps/frombtc/onchain/FromBTCWrapper.d.ts +3 -1
  72. package/dist/swaps/escrow_swaps/frombtc/onchain/FromBTCWrapper.js +7 -7
  73. package/dist/swaps/escrow_swaps/tobtc/IToBTCSwap.d.ts +54 -11
  74. package/dist/swaps/escrow_swaps/tobtc/IToBTCSwap.js +214 -41
  75. package/dist/swaps/escrow_swaps/tobtc/ln/ToBTCLNWrapper.d.ts +2 -1
  76. package/dist/swaps/escrow_swaps/tobtc/ln/ToBTCLNWrapper.js +7 -8
  77. package/dist/swaps/escrow_swaps/tobtc/onchain/ToBTCWrapper.d.ts +3 -1
  78. package/dist/swaps/escrow_swaps/tobtc/onchain/ToBTCWrapper.js +5 -5
  79. package/dist/swaps/spv_swaps/SpvFromBTCSwap.d.ts +85 -22
  80. package/dist/swaps/spv_swaps/SpvFromBTCSwap.js +299 -56
  81. package/dist/swaps/spv_swaps/SpvFromBTCWrapper.d.ts +41 -7
  82. package/dist/swaps/spv_swaps/SpvFromBTCWrapper.js +183 -58
  83. package/dist/swaps/trusted/ln/LnForGasSwap.d.ts +53 -12
  84. package/dist/swaps/trusted/ln/LnForGasSwap.js +163 -49
  85. package/dist/swaps/trusted/ln/LnForGasWrapper.js +1 -2
  86. package/dist/swaps/trusted/onchain/OnchainForGasSwap.d.ts +14 -13
  87. package/dist/swaps/trusted/onchain/OnchainForGasSwap.js +30 -47
  88. package/dist/swaps/trusted/onchain/OnchainForGasWrapper.d.ts +3 -1
  89. package/dist/swaps/trusted/onchain/OnchainForGasWrapper.js +4 -4
  90. package/dist/types/SwapExecutionAction.d.ts +141 -34
  91. package/dist/types/SwapExecutionAction.js +104 -0
  92. package/dist/types/SwapExecutionStep.d.ts +144 -0
  93. package/dist/types/SwapExecutionStep.js +87 -0
  94. package/dist/types/TokenAmount.d.ts +6 -0
  95. package/dist/types/TokenAmount.js +26 -1
  96. package/dist/utils/BitcoinUtils.d.ts +4 -0
  97. package/dist/utils/BitcoinUtils.js +73 -1
  98. package/dist/utils/BitcoinWalletUtils.d.ts +2 -2
  99. package/dist/utils/Utils.d.ts +3 -1
  100. package/dist/utils/Utils.js +7 -1
  101. package/package.json +7 -4
  102. package/src/api/ApiEndpoints.ts +427 -0
  103. package/src/api/ApiParser.ts +138 -0
  104. package/src/api/ApiTypes.ts +229 -0
  105. package/src/api/SerializedAction.ts +97 -0
  106. package/src/api/SwapperApi.ts +545 -0
  107. package/src/api/index.ts +5 -0
  108. package/src/bitcoin/coinselect2/accumulative.ts +2 -1
  109. package/src/bitcoin/coinselect2/blackjack.ts +2 -1
  110. package/src/bitcoin/coinselect2/index.ts +5 -4
  111. package/src/bitcoin/coinselect2/utils.ts +55 -14
  112. package/src/bitcoin/wallet/BitcoinWallet.ts +69 -57
  113. package/src/bitcoin/wallet/IBitcoinWallet.ts +44 -3
  114. package/src/bitcoin/wallet/SingleAddressBitcoinWallet.ts +12 -4
  115. package/src/events/UnifiedSwapEventListener.ts +11 -3
  116. package/src/http/HttpUtils.ts +10 -4
  117. package/src/http/paramcoders/client/StreamingFetchPromise.ts +4 -2
  118. package/src/index.ts +1 -0
  119. package/src/intermediaries/IntermediaryDiscovery.ts +9 -2
  120. package/src/intermediaries/apis/IntermediaryAPI.ts +335 -35
  121. package/src/intermediaries/auth/SignedKeyBasedAuth.ts +69 -0
  122. package/src/storage/IUnifiedStorage.ts +45 -4
  123. package/src/storage/UnifiedSwapStorage.ts +42 -8
  124. package/src/swapper/Swapper.ts +165 -24
  125. package/src/swapper/SwapperUtils.ts +42 -2
  126. package/src/swaps/ISwap.ts +88 -16
  127. package/src/swaps/ISwapWrapper.ts +28 -3
  128. package/src/swaps/escrow_swaps/IEscrowSwap.ts +5 -3
  129. package/src/swaps/escrow_swaps/IEscrowSwapWrapper.ts +3 -1
  130. package/src/swaps/escrow_swaps/frombtc/IFromBTCLNWrapper.ts +4 -1
  131. package/src/swaps/escrow_swaps/frombtc/ln/FromBTCLNSwap.ts +264 -67
  132. package/src/swaps/escrow_swaps/frombtc/ln/FromBTCLNWrapper.ts +6 -4
  133. package/src/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoSwap.ts +390 -89
  134. package/src/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoWrapper.ts +6 -4
  135. package/src/swaps/escrow_swaps/frombtc/onchain/FromBTCSwap.ts +548 -94
  136. package/src/swaps/escrow_swaps/frombtc/onchain/FromBTCWrapper.ts +7 -5
  137. package/src/swaps/escrow_swaps/tobtc/IToBTCSwap.ts +276 -45
  138. package/src/swaps/escrow_swaps/tobtc/ln/ToBTCLNWrapper.ts +7 -6
  139. package/src/swaps/escrow_swaps/tobtc/onchain/ToBTCWrapper.ts +5 -3
  140. package/src/swaps/spv_swaps/SpvFromBTCSwap.ts +413 -64
  141. package/src/swaps/spv_swaps/SpvFromBTCWrapper.ts +239 -61
  142. package/src/swaps/trusted/ln/LnForGasSwap.ts +211 -47
  143. package/src/swaps/trusted/ln/LnForGasWrapper.ts +1 -2
  144. package/src/swaps/trusted/onchain/OnchainForGasSwap.ts +32 -51
  145. package/src/swaps/trusted/onchain/OnchainForGasWrapper.ts +5 -3
  146. package/src/types/SwapExecutionAction.ts +266 -43
  147. package/src/types/SwapExecutionStep.ts +224 -0
  148. package/src/types/TokenAmount.ts +36 -2
  149. package/src/utils/BitcoinUtils.ts +73 -0
  150. package/src/utils/BitcoinWalletUtils.ts +2 -2
  151. package/src/utils/Utils.ts +10 -1
  152. package/src/intermediaries/apis/TrustedIntermediaryAPI.ts +0 -258
@@ -22,9 +22,14 @@ import {ISwapPrice} from "../../prices/abstract/ISwapPrice";
22
22
  import {EventEmitter} from "events";
23
23
  import {Intermediary} from "../../intermediaries/Intermediary";
24
24
  import {extendAbortController, mapArrayToObject, randomBytes, throwIfUndefined} from "../../utils/Utils";
25
- import {fromOutputScript, toCoinselectAddressType, toOutputScript} from "../../utils/BitcoinUtils";
25
+ import {
26
+ fromOutputScript,
27
+ getDummyOutputScript,
28
+ toCoinselectAddressType,
29
+ toOutputScript
30
+ } from "../../utils/BitcoinUtils";
26
31
  import {IntermediaryAPI, SpvFromBTCPrepareResponseType} from "../../intermediaries/apis/IntermediaryAPI";
27
- import {RequestError} from "../../errors/RequestError";
32
+ import {OutOfBoundsError, RequestError} from "../../errors/RequestError";
28
33
  import {IntermediaryError} from "../../errors/IntermediaryError";
29
34
  import {CoinselectAddressTypes} from "../../bitcoin/coinselect2";
30
35
  import {OutScript, Transaction} from "@scure/btc-signer";
@@ -33,8 +38,10 @@ import {IClaimableSwapWrapper} from "../IClaimableSwapWrapper";
33
38
  import {AmountData} from "../../types/AmountData";
34
39
  import {tryWithRetries} from "../../utils/RetryUtils";
35
40
  import {AllOptional} from "../../utils/TypeUtils";
36
- import {fromHumanReadableString} from "../../utils/TokenUtils";
37
41
  import {UserError} from "../../errors/UserError";
42
+ import {BitcoinWalletUtxo, BitcoinWalletUtxoBase, IBitcoinWallet} from "../../bitcoin/wallet/IBitcoinWallet";
43
+ import {utils} from "../../bitcoin/coinselect2/utils";
44
+ import {BitcoinWallet} from "../../bitcoin/wallet/BitcoinWallet";
38
45
 
39
46
  export type SpvFromBTCOptions = {
40
47
  /**
@@ -75,6 +82,16 @@ export type SpvFromBTCOptions = {
75
82
  * whitelist-only wallets
76
83
  */
77
84
  stickyAddress?: boolean,
85
+ /**
86
+ * A bitcoin wallet UTXOs to fully use as an input for this swap, use this option along with passing `amount` as
87
+ * `undefined` when you want to swap the full BTC balance of the wallet in a single swap
88
+ */
89
+ sourceWalletUtxos?: BitcoinWalletUtxoBase[] | Promise<BitcoinWalletUtxoBase[]>,
90
+ /**
91
+ * Bitcoin fee rate to use when deriving `maxAllowedBitcoinFeeRate` and when calculating the input amount based
92
+ * on the `sourceWalletUtxos`
93
+ */
94
+ bitcoinFeeRate?: Promise<number> | number,
78
95
 
79
96
  /**
80
97
  * @deprecated Use `maxAllowedBitcoinFeeRate` instead!
@@ -94,6 +111,9 @@ export type SpvFromBTCWrapperOptions = ISwapWrapperOptions & {
94
111
 
95
112
  export type SpvFromBTCTypeDefinition<T extends ChainType> = SwapTypeDefinition<T, SpvFromBTCWrapper<T>, SpvFromBTCSwap<T>>;
96
113
 
114
+ export const REQUIRED_SPV_SWAP_VAULT_ADDRESS_TYPE: CoinselectAddressTypes = "p2tr";
115
+ export const REQUIRED_SPV_SWAP_LP_ADDRESS_TYPE: CoinselectAddressTypes = "p2wpkh";
116
+
97
117
  /**
98
118
  * New spv vault (UTXO-controlled vault) based swaps for Bitcoin -> Smart chain swaps not requiring
99
119
  * any initiation on the destination chain, and with the added possibility for the user to receive
@@ -205,6 +225,7 @@ export class SpvFromBTCWrapper<
205
225
  * @param versionedContracts
206
226
  * @param versionedSynchronizer
207
227
  * @param btcRpc Bitcoin RPC which also supports getting transactions by txoHash
228
+ * @param lpApi
208
229
  * @param options
209
230
  * @param events Instance to use for emitting events
210
231
  */
@@ -228,11 +249,12 @@ export class SpvFromBTCWrapper<
228
249
  }
229
250
  },
230
251
  btcRpc: BitcoinRpcWithAddressIndex<any>,
252
+ lpApi: IntermediaryAPI,
231
253
  options?: AllOptional<SpvFromBTCWrapperOptions>,
232
254
  events?: EventEmitter<{swapState: [ISwap]}>
233
255
  ) {
234
256
  super(
235
- chainIdentifier, unifiedStorage, unifiedChainEvents, chain, prices, tokens,
257
+ chainIdentifier, unifiedStorage, unifiedChainEvents, chain, prices, tokens, lpApi,
236
258
  {
237
259
  ...options,
238
260
  bitcoinNetwork: options?.bitcoinNetwork ?? TEST_NETWORK,
@@ -347,20 +369,16 @@ export class SpvFromBTCWrapper<
347
369
  *
348
370
  * @param amountData
349
371
  * @param options Options as passed to the swap creation function
350
- * @param pricePrefetch
351
- * @param nativeTokenPricePrefetch
352
372
  * @param abortController
353
373
  * @param contractVersion
354
374
  * @private
355
375
  */
356
- private async preFetchCallerFeeShare(
357
- amountData: AmountData,
376
+ private async preFetchCallerFeeInNativeToken(
377
+ amountData: {amount?: bigint},
358
378
  options: {
359
379
  unsafeZeroWatchtowerFee: boolean,
360
380
  feeSafetyFactor: number
361
381
  },
362
- pricePrefetch: Promise<bigint | undefined>,
363
- nativeTokenPricePrefetch: Promise<bigint | undefined> | undefined,
364
382
  abortController: AbortController,
365
383
  contractVersion: string
366
384
  ): Promise<bigint | undefined> {
@@ -372,16 +390,12 @@ export class SpvFromBTCWrapper<
372
390
  feePerBlock,
373
391
  btcRelayData,
374
392
  currentBtcBlock,
375
- claimFeeRate,
376
- nativeTokenPrice
393
+ claimFeeRate
377
394
  ] = await Promise.all([
378
395
  this.btcRelay(contractVersion).getFeePerBlock(),
379
396
  this.btcRelay(contractVersion).getTipData(),
380
397
  this._btcRpc.getTipHeight(),
381
- this._contract(contractVersion).getClaimFee(this._chain.randomAddress()),
382
- nativeTokenPricePrefetch ?? (amountData.token===this._chain.getNativeCurrencyAddress() ?
383
- pricePrefetch :
384
- this._prices.preFetchPrice(this.chainIdentifier, this._chain.getNativeCurrencyAddress(), abortController.signal))
398
+ this._contract(contractVersion).getClaimFee(this._chain.randomAddress())
385
399
  ]);
386
400
 
387
401
  if(btcRelayData==null) throw new Error("Btc relay doesn't seem to be initialized!");
@@ -394,33 +408,65 @@ export class SpvFromBTCWrapper<
394
408
  (claimFeeRate * BigInt(this._options.maxTransactionsDelta))
395
409
  ) * BigInt(Math.floor(options.feeSafetyFactor*1000000)) / 1_000_000n;
396
410
 
397
- let payoutAmount: bigint;
398
- if(amountData.exactIn) {
399
- //Convert input amount in BTC to
400
- const amountInNativeToken = await this._prices.getFromBtcSwapAmount(this.chainIdentifier, amountData.amount, this._chain.getNativeCurrencyAddress(), abortController.signal, nativeTokenPrice);
401
- payoutAmount = amountInNativeToken - totalFeeInNativeToken;
402
- } else {
403
- if(amountData.token===this._chain.getNativeCurrencyAddress()) {
404
- //Both amounts in same currency
405
- payoutAmount = amountData.amount;
406
- } else {
407
- //Need to convert both to native currency
408
- const btcAmount = await this._prices.getToBtcSwapAmount(this.chainIdentifier, amountData.amount, amountData.token, abortController.signal, await pricePrefetch);
409
- payoutAmount = await this._prices.getFromBtcSwapAmount(this.chainIdentifier, btcAmount, this._chain.getNativeCurrencyAddress(), abortController.signal, nativeTokenPrice);
410
- }
411
- }
412
-
413
- this.logger.debug("preFetchCallerFeeShare(): Caller fee in native token: "+totalFeeInNativeToken.toString(10)+" total payout in native token: "+payoutAmount.toString(10));
414
-
415
- const callerFeeShare = ((totalFeeInNativeToken * 100_000n) + payoutAmount - 1n) / payoutAmount; //Make sure to round up here
416
- if(callerFeeShare < 0n) return 0n;
417
- if(callerFeeShare >= 2n**20n) return 2n**20n - 1n;
418
- return callerFeeShare;
411
+ return totalFeeInNativeToken;
419
412
  } catch (e) {
420
413
  abortController.abort(e);
421
414
  }
422
415
  }
423
416
 
417
+ /**
418
+ * Pre-fetches caller (watchtower) bounty data for the swap. Doesn't throw, instead returns null and aborts the
419
+ * provided abortController
420
+ *
421
+ * @param amountPrefetch
422
+ * @param totalFeeInNativeTokenPrefetch
423
+ * @param amountData
424
+ * @param options Options as passed to the swap creation function
425
+ * @param pricePrefetch
426
+ * @param nativeTokenPricePrefetch
427
+ * @param abortSignal
428
+ * @private
429
+ */
430
+ private async computeCallerFeeShare(
431
+ amountPrefetch: Promise<bigint | undefined>,
432
+ totalFeeInNativeTokenPrefetch: Promise<bigint | undefined>,
433
+ amountData: {exactIn: boolean, token: string},
434
+ options: {unsafeZeroWatchtowerFee: boolean},
435
+ pricePrefetch: Promise<bigint | undefined>,
436
+ nativeTokenPricePrefetch: Promise<bigint | undefined> | undefined,
437
+ abortSignal?: AbortSignal
438
+ ): Promise<bigint> {
439
+ if(options.unsafeZeroWatchtowerFee) return 0n;
440
+
441
+ const amount = await throwIfUndefined(amountPrefetch, "Cannot get swap amount!");
442
+ if(amount===0n) return 0n;
443
+
444
+ const totalFeeInNativeToken = await throwIfUndefined(totalFeeInNativeTokenPrefetch, "Cannot get total fee in native token!");
445
+ const nativeTokenPrice = await nativeTokenPricePrefetch;
446
+
447
+ let payoutAmount: bigint;
448
+ if(amountData.exactIn) {
449
+ //Convert input amount in BTC to
450
+ const amountInNativeToken = await this._prices.getFromBtcSwapAmount(this.chainIdentifier, amount, this._chain.getNativeCurrencyAddress(), abortSignal, nativeTokenPrice);
451
+ payoutAmount = amountInNativeToken - totalFeeInNativeToken;
452
+ } else {
453
+ if(amountData.token===this._chain.getNativeCurrencyAddress()) {
454
+ //Both amounts in same currency
455
+ payoutAmount = amount;
456
+ } else {
457
+ //Need to convert both to native currency
458
+ const btcAmount = await this._prices.getToBtcSwapAmount(this.chainIdentifier, amount, amountData.token, abortSignal, await pricePrefetch);
459
+ payoutAmount = await this._prices.getFromBtcSwapAmount(this.chainIdentifier, btcAmount, this._chain.getNativeCurrencyAddress(), abortSignal, nativeTokenPrice);
460
+ }
461
+ }
462
+
463
+ this.logger.debug("computeCallerFeeShare(): Caller fee in native token: "+totalFeeInNativeToken.toString(10)+" total payout in native token: "+payoutAmount.toString(10));
464
+
465
+ const callerFeeShare = ((totalFeeInNativeToken * 100_000n) + payoutAmount - 1n) / payoutAmount; //Make sure to round up here
466
+ if(callerFeeShare < 0n) return 0n;
467
+ if(callerFeeShare >= 2n**20n) return 2n**20n - 1n;
468
+ return callerFeeShare;
469
+ }
424
470
 
425
471
  /**
426
472
  * Verifies response returned from intermediary
@@ -430,7 +476,8 @@ export class SpvFromBTCWrapper<
430
476
  * @param lp Intermediary
431
477
  * @param options Options as passed to the swap creation function
432
478
  * @param callerFeeShare
433
- * @param bitcoinFeeRatePromise Maximum accepted fee rate from the LPs
479
+ * @param maxBitcoinFeeRatePromise Maximum accepted fee rate from the LPs
480
+ * @param bitcoinFeeRatePromise
434
481
  * @param abortSignal
435
482
  * @private
436
483
  * @throws {IntermediaryError} in case the response is invalid
@@ -440,16 +487,18 @@ export class SpvFromBTCWrapper<
440
487
  amountData: AmountData,
441
488
  lp: Intermediary,
442
489
  options: {
443
- gasAmount: bigint
490
+ gasAmount: bigint,
491
+ sourceWalletUtxos?: Promise<BitcoinWalletUtxoBase[]>
444
492
  },
445
493
  callerFeeShare: bigint,
446
- bitcoinFeeRatePromise: Promise<number | undefined>,
494
+ maxBitcoinFeeRatePromise: Promise<number | undefined>,
495
+ bitcoinFeeRatePromise: Promise<number | undefined> | undefined,
447
496
  abortSignal: AbortSignal
448
497
  ): Promise<{
449
498
  vault: T["SpvVaultData"],
450
499
  vaultUtxoValue: number
451
500
  }> {
452
- const btcFeeRate = await throwIfUndefined(bitcoinFeeRatePromise, "Bitcoin fee rate promise failed!");
501
+ const btcFeeRate = await throwIfUndefined(maxBitcoinFeeRatePromise, "Bitcoin fee rate promise failed!");
453
502
  abortSignal.throwIfAborted();
454
503
  if(btcFeeRate!=null && resp.btcFeeRate > btcFeeRate) throw new IntermediaryError(`Required bitcoin fee rate returned from the LP is too high! Maximum accepted: ${btcFeeRate} sats/vB, required by LP: ${resp.btcFeeRate} sats/vB`);
455
504
 
@@ -459,11 +508,13 @@ export class SpvFromBTCWrapper<
459
508
  let vaultScript: Uint8Array;
460
509
  let vaultAddressType: CoinselectAddressTypes;
461
510
  let btcAddressScript: Uint8Array;
511
+ let btcAddressType: CoinselectAddressTypes;
462
512
  //Ensure valid btc addresses returned
463
513
  try {
464
514
  vaultScript = toOutputScript(this._options.bitcoinNetwork, resp.vaultBtcAddress);
465
515
  vaultAddressType = toCoinselectAddressType(vaultScript);
466
516
  btcAddressScript = toOutputScript(this._options.bitcoinNetwork, resp.btcAddress);
517
+ btcAddressType = toCoinselectAddressType(btcAddressScript);
467
518
  } catch (e) {
468
519
  throw new IntermediaryError("Invalid btc address data returned", e);
469
520
  }
@@ -473,7 +524,8 @@ export class SpvFromBTCWrapper<
473
524
  resp.vaultId < 0n || //Ensure vaultId is not negative
474
525
  vaultScript==null || //Make sure vault script is parsable and of known type
475
526
  btcAddressScript==null || //Make sure btc address script is parsable and of known type
476
- vaultAddressType==="p2pkh" || vaultAddressType==="p2sh-p2wpkh" || //Constrain the vault script type to witness types
527
+ btcAddressType!==REQUIRED_SPV_SWAP_LP_ADDRESS_TYPE || //Constrain the btc address script type
528
+ vaultAddressType!==REQUIRED_SPV_SWAP_VAULT_ADDRESS_TYPE || //Constrain the vault script type
477
529
  decodedUtxo.length!==2 || decodedUtxo[0].length!==64 || isNaN(parseInt(decodedUtxo[1])) || //Check valid UTXO
478
530
  resp.btcFeeRate < 1 || resp.btcFeeRate > 10000 //Sanity check on the returned BTC fee rate
479
531
  ) throw new IntermediaryError("Invalid vault data returned!");
@@ -517,7 +569,22 @@ export class SpvFromBTCWrapper<
517
569
 
518
570
  //Amounts - make sure the amounts match
519
571
  if(amountData.exactIn) {
520
- if(resp.btcAmount !== amountData.amount) throw new IntermediaryError("Invalid amount returned");
572
+ if(!resp.usedUtxoInputCalculation) {
573
+ //Legacy calculation
574
+ if(resp.btcAmount !== amountData.amount) throw new IntermediaryError("Invalid amount returned");
575
+ } else {
576
+ //Implies the raw UTXOs were passed for amount derivation
577
+ //Verify the derivation was done correctly
578
+ if(options.sourceWalletUtxos==null) throw new IntermediaryError("Invalid usedUtxoInputCalcuation return value");
579
+ if(bitcoinFeeRatePromise==null) throw new Error("bitcoinFeeRatePromise must be passed for UTXO-based input amount calculation checks");
580
+ const walletUtxos = await options.sourceWalletUtxos;
581
+ const bitcoinFeeRate = await throwIfUndefined(bitcoinFeeRatePromise, "Failed to fetch bitcoin fee rate!");
582
+ const {balance} = BitcoinWallet.getSpendableBalance(
583
+ walletUtxos, Math.max(resp.btcFeeRate, bitcoinFeeRate),
584
+ this.getDummySwapPsbt(options.gasAmount!==0n), REQUIRED_SPV_SWAP_LP_ADDRESS_TYPE
585
+ );
586
+ if(resp.btcAmount !== balance) throw new IntermediaryError(`Invalid amount returned, expected: ${balance.toString(10)}, got: ${resp.btcAmount.toString(10)}`);
587
+ }
521
588
  } else {
522
589
  //Check the difference between amount adjusted due to scaling to raw amount
523
590
  const adjustedAmount = amountData.amount / tokenData[0].multiplier * tokenData[0].multiplier;
@@ -613,6 +680,72 @@ export class SpvFromBTCWrapper<
613
680
  };
614
681
  }
615
682
 
683
+ private async amountPrefetch(
684
+ amountData: {token: string, exactIn: boolean, amount?: bigint},
685
+ bitcoinFeeRatePromise: Promise<number | undefined>,
686
+ walletUtxosPromise: Promise<BitcoinWalletUtxoBase[]> | undefined,
687
+ includeGas: boolean,
688
+ abortController: AbortController
689
+ ): Promise<bigint | undefined> {
690
+ if(amountData.amount!=null) return amountData.amount;
691
+ try {
692
+ const bitcoinFeeRate = await throwIfUndefined(bitcoinFeeRatePromise, "Cannot fetch Bitcoin fee rate!");
693
+ if(walletUtxosPromise==null) throw new UserError("Cannot use empty amount without passing UTXOs!");
694
+ const walletUtxos = await walletUtxosPromise;
695
+ if(walletUtxos.length===0)
696
+ throw new UserError("Wallet doesn't have any BTC balance");
697
+ const spendableBalance = await BitcoinWallet.getSpendableBalance(
698
+ walletUtxos, bitcoinFeeRate,
699
+ this.getDummySwapPsbt(includeGas), REQUIRED_SPV_SWAP_LP_ADDRESS_TYPE
700
+ );
701
+ return spendableBalance.balance;
702
+ } catch (e) {
703
+ abortController.abort(e);
704
+ }
705
+ }
706
+
707
+ private bitcoinFeeRatePrefetch(
708
+ options: {
709
+ maxAllowedBitcoinFeeRate: number,
710
+ sourceWalletUtxos?: Promise<BitcoinWalletUtxoBase[]>,
711
+ bitcoinFeeRate?: Promise<number>
712
+ },
713
+ abortController: AbortController
714
+ ) {
715
+ let bitcoinFeeRatePromise: Promise<number | undefined> | undefined;
716
+ if(options?.sourceWalletUtxos!=null) {
717
+ if(options.bitcoinFeeRate!=null) {
718
+ bitcoinFeeRatePromise = options.bitcoinFeeRate.then(value => {
719
+ if(options.maxAllowedBitcoinFeeRate!=Infinity && options.maxAllowedBitcoinFeeRate<value)
720
+ throw new Error("Passed `maxAllowedBitcoinFeeRate` cannot be lower than `bitcoinFeeRate`");
721
+ return value;
722
+ });
723
+ } else {
724
+ bitcoinFeeRatePromise = this._btcRpc.getFeeRate().then(value => {
725
+ if(options.maxAllowedBitcoinFeeRate!=Infinity && value > options.maxAllowedBitcoinFeeRate) return options.maxAllowedBitcoinFeeRate;
726
+ return value;
727
+ });
728
+ }
729
+ bitcoinFeeRatePromise = bitcoinFeeRatePromise.catch(e => {
730
+ abortController.abort(e);
731
+ return undefined;
732
+ });
733
+ }
734
+ const maxBitcoinFeeRatePromise: Promise<number | undefined> = options.maxAllowedBitcoinFeeRate!=Infinity
735
+ ? Promise.resolve(options.maxAllowedBitcoinFeeRate)
736
+ : throwIfUndefined(bitcoinFeeRatePromise ?? options.bitcoinFeeRate ?? this._btcRpc.getFeeRate())
737
+ .then(x => this._options.maxBtcFeeOffset + (x*this._options.maxBtcFeeMultiplier))
738
+ .catch(e => {
739
+ abortController.abort(e);
740
+ return undefined;
741
+ });
742
+
743
+ return {
744
+ bitcoinFeeRatePromise,
745
+ maxBitcoinFeeRatePromise
746
+ }
747
+ }
748
+
616
749
  /**
617
750
  * Returns a newly created Bitcoin -> Smart chain swap using the SPV vault (UTXO-controlled vault) swap protocol,
618
751
  * with the passed amount. Also allows specifying additional "gas drop" native token that the receipient receives
@@ -627,7 +760,7 @@ export class SpvFromBTCWrapper<
627
760
  */
628
761
  public create(
629
762
  recipient: string,
630
- amountData: AmountData,
763
+ amountData: { amount?: bigint, token: string, exactIn: boolean },
631
764
  lps: Intermediary[],
632
765
  options?: SpvFromBTCOptions,
633
766
  additionalParams?: Record<string, any>,
@@ -640,7 +773,13 @@ export class SpvFromBTCWrapper<
640
773
  gasAmount: this.parseGasAmount(options?.gasAmount),
641
774
  unsafeZeroWatchtowerFee: options?.unsafeZeroWatchtowerFee ?? false,
642
775
  feeSafetyFactor: options?.feeSafetyFactor ?? 1.25,
643
- maxAllowedBitcoinFeeRate: options?.maxAllowedBitcoinFeeRate ?? options?.maxAllowedNetworkFeeRate ?? Infinity
776
+ maxAllowedBitcoinFeeRate: options?.maxAllowedBitcoinFeeRate ?? options?.maxAllowedNetworkFeeRate ?? Infinity,
777
+ sourceWalletUtxos: options?.sourceWalletUtxos==undefined
778
+ ? undefined
779
+ : options?.sourceWalletUtxos instanceof Promise ? options.sourceWalletUtxos : Promise.resolve(options.sourceWalletUtxos),
780
+ bitcoinFeeRate: options?.bitcoinFeeRate==undefined
781
+ ? undefined
782
+ : options?.bitcoinFeeRate instanceof Promise ? options.bitcoinFeeRate : Promise.resolve(options.bitcoinFeeRate),
644
783
  };
645
784
 
646
785
  if(
@@ -652,6 +791,13 @@ export class SpvFromBTCWrapper<
652
791
  )
653
792
  ) throw new UserError("Cannot specify `gasAmount` for swaps to a native token!");
654
793
 
794
+ if(amountData.amount==null && options?.sourceWalletUtxos==null)
795
+ throw new UserError("Source wallet UTXOs need to be passed when amount is null!");
796
+ if(amountData.amount==null && !amountData.exactIn)
797
+ throw new UserError("Amount can be null only for exactIn swaps!");
798
+ if(amountData.amount!=null && options?.sourceWalletUtxos!=null)
799
+ throw new UserError("Source wallet UTXOs cannot be passed while specifying an input amount!");
800
+
655
801
  const lpVersions = Intermediary.getContractVersionsForLps(this.chainIdentifier, lps);
656
802
 
657
803
  const _abortController = extendAbortController(abortSignal);
@@ -663,14 +809,12 @@ export class SpvFromBTCWrapper<
663
809
  undefined :
664
810
  this.preFetchPrice({token: nativeTokenAddress}, _abortController.signal);
665
811
  const callerFeePrefetchPromise = mapArrayToObject(lpVersions, (contractVersion: string) => {
666
- return this.preFetchCallerFeeShare(amountData, _options, pricePrefetchPromise, gasTokenPricePrefetchPromise, _abortController, contractVersion);
812
+ return this.preFetchCallerFeeInNativeToken(amountData, _options, _abortController, contractVersion);
667
813
  });
668
- const bitcoinFeeRatePromise: Promise<number | undefined> = _options.maxAllowedBitcoinFeeRate!=Infinity ?
669
- Promise.resolve(_options.maxAllowedBitcoinFeeRate) :
670
- this._btcRpc.getFeeRate().then(x => this._options.maxBtcFeeOffset + (x*this._options.maxBtcFeeMultiplier)).catch(e => {
671
- _abortController.abort(e);
672
- return undefined;
673
- });
814
+ const {maxBitcoinFeeRatePromise, bitcoinFeeRatePromise} = this.bitcoinFeeRatePrefetch(_options, _abortController);
815
+ const amountPromise = this.amountPrefetch(
816
+ amountData, maxBitcoinFeeRatePromise, _options.sourceWalletUtxos, _options.gasAmount!==0n, _abortController
817
+ );
674
818
 
675
819
  return lps.map(lp => {
676
820
  return {
@@ -680,21 +824,41 @@ export class SpvFromBTCWrapper<
680
824
  const version = lp.getContractVersion(this.chainIdentifier);
681
825
 
682
826
  const abortController = extendAbortController(_abortController.signal);
827
+ const callerFeeRatePromise = this.computeCallerFeeShare(
828
+ amountPromise,
829
+ callerFeePrefetchPromise[version],
830
+ amountData,
831
+ _options,
832
+ pricePrefetchPromise,
833
+ gasTokenPricePrefetchPromise,
834
+ abortController.signal
835
+ );
683
836
 
684
837
  try {
685
838
  const resp = await tryWithRetries(async(retryCount: number) => {
686
- return await IntermediaryAPI.prepareSpvFromBTC(
839
+ return await this._lpApi.prepareSpvFromBTC(
687
840
  this.chainIdentifier, lp.url,
688
841
  {
689
842
  address: recipient,
690
- amount: amountData.amount,
843
+ amount: throwIfUndefined(amountPromise, "Failed to compute swap amount"),
691
844
  token: amountData.token.toString(),
692
845
  exactOut: !amountData.exactIn,
693
846
  gasToken: nativeTokenAddress,
694
847
  gasAmount: _options.gasAmount,
695
- callerFeeRate: throwIfUndefined(callerFeePrefetchPromise[version], "Caller fee prefetch failed!"),
848
+ callerFeeRate: throwIfUndefined(callerFeeRatePromise, "Caller fee prefetch failed!"),
696
849
  frontingFeeRate: 0n,
697
850
  stickyAddress: options?.stickyAddress,
851
+ amountUtxos: _options.sourceWalletUtxos!=null
852
+ ? _options.sourceWalletUtxos.then(utxos => {
853
+ if(utxos.length===0) return undefined;
854
+ return utxos.map(utxo => ({
855
+ value: utxo.value,
856
+ vSize: utils.inputBytes({type: utxo.type}),
857
+ cpfp: utxo.cpfp==null ? undefined : {effectiveVSize: utxo.cpfp?.txVsize, effectiveFeeRate: utxo.cpfp?.txEffectiveFeeRate}
858
+ }));
859
+ })
860
+ : undefined,
861
+ amountFeeRate: bitcoinFeeRatePromise,
698
862
  additionalParams
699
863
  },
700
864
  this._options.postRequestTimeout, abortController.signal, retryCount>0 ? false : undefined
@@ -703,7 +867,8 @@ export class SpvFromBTCWrapper<
703
867
 
704
868
  this.logger.debug("create("+lp.url+"): LP response: ", resp)
705
869
 
706
- const callerFeeShare = (await callerFeePrefetchPromise[version])!;
870
+ const callerFeeShare = await callerFeeRatePromise;
871
+ const amount = await throwIfUndefined(amountPromise);
707
872
 
708
873
  const [
709
874
  pricingInfo,
@@ -722,9 +887,16 @@ export class SpvFromBTCWrapper<
722
887
  resp.totalGas * (100_000n + callerFeeShare) / 100_000n,
723
888
  nativeTokenAddress, {swapFeeBtc: resp.gasSwapFeeBtc}, gasTokenPricePrefetchPromise, usdPricePrefetchPromise, abortController.signal
724
889
  ),
725
- this.verifyReturnedData(resp, amountData, lp, _options, callerFeeShare, bitcoinFeeRatePromise, abortController.signal)
890
+ this.verifyReturnedData(
891
+ resp,
892
+ {...amountData, amount},
893
+ lp, _options, callerFeeShare, maxBitcoinFeeRatePromise, bitcoinFeeRatePromise, abortController.signal
894
+ )
726
895
  ]);
727
896
 
897
+ let minimumBtcFeeRate: number = resp.btcFeeRate;
898
+ if(bitcoinFeeRatePromise!=null) minimumBtcFeeRate = Math.max(minimumBtcFeeRate, await throwIfUndefined(bitcoinFeeRatePromise));
899
+
728
900
  const swapInit: SpvFromBTCSwapInit = {
729
901
  pricingInfo,
730
902
  url: lp.url,
@@ -749,7 +921,7 @@ export class SpvFromBTCWrapper<
749
921
  btcAmount: resp.btcAmount,
750
922
  btcAmountSwap: resp.btcAmountSwap,
751
923
  btcAmountGas: resp.btcAmountGas,
752
- minimumBtcFeeRate: resp.btcFeeRate,
924
+ minimumBtcFeeRate,
753
925
 
754
926
  outputTotalSwap: resp.total,
755
927
  outputSwapToken: amountData.token,
@@ -765,13 +937,19 @@ export class SpvFromBTCWrapper<
765
937
 
766
938
  genesisSmartChainBlockHeight: await throwIfUndefined(
767
939
  finalizedBlockHeightPrefetchPromise,
768
- "Finalize block height promise failed!"
940
+ "Network finalized blockheight pre-fetch failed!"
769
941
  ),
770
942
  contractVersion: version
771
943
  };
772
944
  const quote = new SpvFromBTCSwap<T>(this, swapInit);
773
945
  return quote;
774
946
  } catch (e) {
947
+ if(e instanceof OutOfBoundsError) {
948
+ const amountResult = await amountPromise.catch(() => undefined);
949
+ if(_options.sourceWalletUtxos!=null && amountResult!=null && amountResult<=0n) {
950
+ e = new UserError("Wallet doesn't have enough BTC balance to cover transaction fees");
951
+ }
952
+ }
775
953
  abortController.abort(e);
776
954
  throw e;
777
955
  }
@@ -902,7 +1080,7 @@ export class SpvFromBTCWrapper<
902
1080
  allowUnknownOutputs: true
903
1081
  });
904
1082
 
905
- const randomVaultOutScript = OutScript.encode({type: "tr", pubkey: Buffer.from("0101010101010101010101010101010101010101010101010101010101010101", "hex")});
1083
+ const randomVaultOutScript = getDummyOutputScript(REQUIRED_SPV_SWAP_VAULT_ADDRESS_TYPE);
906
1084
 
907
1085
  psbt.addInput({
908
1086
  txid: randomBytes(32),