@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
@@ -73,6 +73,15 @@ function transactionBytes(inputs, outputs, changeType) {
73
73
  }
74
74
  return Math.ceil(size);
75
75
  }
76
+ function numberOrNaN(v) {
77
+ if (typeof v !== 'number')
78
+ return NaN;
79
+ if (!isFinite(v))
80
+ return NaN;
81
+ if (v < 0)
82
+ return NaN;
83
+ return v;
84
+ }
76
85
  function uintOrNaN(v) {
77
86
  if (typeof v !== 'number')
78
87
  return NaN;
@@ -91,26 +100,50 @@ function sumOrNaN(range) {
91
100
  return range.reduce((a, x) => a + uintOrNaN(x.value), 0);
92
101
  }
93
102
  function finalize(inputs, outputs, feeRate, changeType, cpfpAddFee = 0) {
94
- const bytesAccum = transactionBytes(inputs, outputs, changeType);
103
+ const bytesAccum = transactionBytes(inputs, outputs, changeType ?? undefined);
95
104
  logger.debug("finalize(): Transaction bytes: ", bytesAccum);
96
- const feeAfterExtraOutput = (feeRate * (bytesAccum + outputBytes({ type: changeType }))) + cpfpAddFee;
97
- logger.debug("finalize(): TX fee after adding change output: ", feeAfterExtraOutput);
98
- const remainderAfterExtraOutput = Math.floor(sumOrNaN(inputs) - (sumOrNaN(outputs) + feeAfterExtraOutput));
99
- logger.debug("finalize(): Leaves change (changeType=" + changeType + ") value: ", remainderAfterExtraOutput);
100
- // is it worth a change output?
101
- if (remainderAfterExtraOutput >= dustThreshold({ type: changeType })) {
102
- outputs = outputs.concat({ value: remainderAfterExtraOutput, type: changeType });
105
+ if (changeType != null) {
106
+ const feeAfterExtraOutput = (feeRate * (bytesAccum + outputBytes({ type: changeType }))) + cpfpAddFee;
107
+ logger.debug("finalize(): TX fee after adding change output: ", feeAfterExtraOutput);
108
+ const remainderAfterExtraOutput = Math.floor(sumOrNaN(inputs) - (sumOrNaN(outputs) + feeAfterExtraOutput));
109
+ logger.debug("finalize(): Leaves change (changeType=" + changeType + ") value: ", remainderAfterExtraOutput);
110
+ // is it worth a change output?
111
+ if (remainderAfterExtraOutput >= dustThreshold({ type: changeType })) {
112
+ outputs = outputs.concat({ value: remainderAfterExtraOutput, type: changeType });
113
+ }
103
114
  }
104
115
  const fee = sumOrNaN(inputs) - sumOrNaN(outputs);
105
116
  logger.debug("finalize(): Re-calculated total fee: ", fee);
106
- if (!isFinite(fee))
117
+ if (!isFinite(fee) || fee < 0)
107
118
  return { fee: (feeRate * bytesAccum) + cpfpAddFee };
119
+ let txVSize = exports.utils.transactionBytes(inputs, outputs);
120
+ let txFee = fee;
121
+ const cpfpSortedInputs = [...inputs].sort((a, b) => (b.cpfp?.txEffectiveFeeRate ?? 0) - (a.cpfp?.txEffectiveFeeRate ?? 0));
122
+ cpfpSortedInputs.forEach(input => {
123
+ if (input.cpfp == null)
124
+ return;
125
+ const currentEffectiveFeeRate = txFee / txVSize;
126
+ if (currentEffectiveFeeRate > input.cpfp.txEffectiveFeeRate) {
127
+ txVSize += input.cpfp.txVsize;
128
+ txFee += input.cpfp.txVsize * input.cpfp.txEffectiveFeeRate;
129
+ }
130
+ });
108
131
  return {
109
132
  inputs: inputs,
110
133
  outputs: outputs,
134
+ effectiveFeeRate: txFee / txVSize,
111
135
  fee: fee
112
136
  };
113
137
  }
138
+ function isDetrimentalInput(feeRate, utxo) {
139
+ const utxoBytes = exports.utils.inputBytes(utxo);
140
+ const utxoFee = feeRate * utxoBytes;
141
+ let cpfpFee = 0;
142
+ if (utxo.cpfp != null && utxo.cpfp.txEffectiveFeeRate < feeRate)
143
+ cpfpFee = Math.ceil(utxo.cpfp.txVsize * (feeRate - utxo.cpfp.txEffectiveFeeRate));
144
+ // skip detrimental input
145
+ return utxoFee + cpfpFee > utxo.value;
146
+ }
114
147
  exports.utils = {
115
148
  dustThreshold: dustThreshold,
116
149
  finalize: finalize,
@@ -119,5 +152,7 @@ exports.utils = {
119
152
  sumOrNaN: sumOrNaN,
120
153
  sumForgiving: sumForgiving,
121
154
  transactionBytes: transactionBytes,
122
- uintOrNaN: uintOrNaN
155
+ uintOrNaN: uintOrNaN,
156
+ numberOrNaN: numberOrNaN,
157
+ isDetrimentalInput
123
158
  };
@@ -1,29 +1,8 @@
1
- /// <reference types="node" />
2
- /// <reference types="node" />
3
1
  import { CoinselectAddressTypes } from "../coinselect2";
4
2
  import { BTC_NETWORK } from "@scure/btc-signer/utils";
5
3
  import { Transaction } from "@scure/btc-signer";
6
- import { IBitcoinWallet } from "./IBitcoinWallet";
7
- import { Buffer } from "buffer";
4
+ import { BitcoinWalletUtxo, BitcoinWalletUtxoBase, IBitcoinWallet } from "./IBitcoinWallet";
8
5
  import { BitcoinNetwork, BitcoinRpcWithAddressIndex } from "@atomiqlabs/base";
9
- /**
10
- * UTXO data structure for Bitcoin wallets
11
- *
12
- * @category Bitcoin
13
- */
14
- export type BitcoinWalletUtxo = {
15
- vout: number;
16
- txId: string;
17
- value: number;
18
- type: CoinselectAddressTypes;
19
- outputScript: Buffer;
20
- address: string;
21
- cpfp?: {
22
- txVsize: number;
23
- txEffectiveFeeRate: number;
24
- };
25
- confirmed: boolean;
26
- };
27
6
  /**
28
7
  * Identifies the address type of a Bitcoin address
29
8
  *
@@ -96,7 +75,7 @@ export declare abstract class BitcoinWallet implements IBitcoinWallet {
96
75
  pubkey: string;
97
76
  address: string;
98
77
  addressType: CoinselectAddressTypes;
99
- }[], psbt: Transaction, feeRate?: number): Promise<{
78
+ }[], psbt: Transaction, _feeRate?: number, utxos?: BitcoinWalletUtxo[], spendFully?: boolean): Promise<{
100
79
  fee: number;
101
80
  psbt?: Transaction;
102
81
  inputAddressIndexes?: {
@@ -106,13 +85,13 @@ export declare abstract class BitcoinWallet implements IBitcoinWallet {
106
85
  protected _getSpendableBalance(sendingAccounts: {
107
86
  address: string;
108
87
  addressType: CoinselectAddressTypes;
109
- }[], psbt?: Transaction, feeRate?: number): Promise<{
88
+ }[], psbt?: Transaction, feeRate?: number, outputAddressType?: CoinselectAddressTypes, utxoPool?: BitcoinWalletUtxoBase[]): Promise<{
110
89
  balance: bigint;
111
90
  feeRate: number;
112
91
  totalFee: number;
113
92
  }>;
114
93
  abstract sendTransaction(address: string, amount: bigint, feeRate?: number): Promise<string>;
115
- abstract fundPsbt(psbt: Transaction, feeRate?: number): Promise<Transaction>;
94
+ abstract fundPsbt(psbt: Transaction, feeRate?: number, utxos?: BitcoinWalletUtxo[], spendFully?: boolean): Promise<Transaction>;
116
95
  abstract signPsbt(psbt: Transaction, signInputs: number[]): Promise<Transaction>;
117
96
  abstract getTransactionFee(address: string, amount: bigint, feeRate?: number): Promise<number>;
118
97
  abstract getFundedPsbtFee(psbt: Transaction, feeRate?: number): Promise<number>;
@@ -127,4 +106,8 @@ export declare abstract class BitcoinWallet implements IBitcoinWallet {
127
106
  totalFee: number;
128
107
  }>;
129
108
  static bitcoinNetworkToObject(network: BitcoinNetwork): BTC_NETWORK;
109
+ static getSpendableBalance(utxoPool: BitcoinWalletUtxoBase[], feeRate: number, psbt?: Transaction, outputAddressType?: CoinselectAddressTypes): {
110
+ balance: bigint;
111
+ totalFee: number;
112
+ };
130
113
  }
@@ -5,10 +5,10 @@ const coinselect2_1 = require("../coinselect2");
5
5
  const utils_1 = require("@scure/btc-signer/utils");
6
6
  const btc_signer_1 = require("@scure/btc-signer");
7
7
  const buffer_1 = require("buffer");
8
- const Utils_1 = require("../../utils/Utils");
9
8
  const BitcoinUtils_1 = require("../../utils/BitcoinUtils");
10
9
  const Logger_1 = require("../../utils/Logger");
11
10
  const base_1 = require("@atomiqlabs/base");
11
+ const utils_2 = require("../coinselect2/utils");
12
12
  /**
13
13
  * Identifies the address type of a Bitcoin address
14
14
  *
@@ -135,10 +135,11 @@ class BitcoinWallet {
135
135
  });
136
136
  return this._fundPsbt(sendingAccounts, psbt, feeRate);
137
137
  }
138
- async _fundPsbt(sendingAccounts, psbt, feeRate) {
139
- if (feeRate == null)
140
- feeRate = await this.getFeeRate();
141
- const utxoPool = (await Promise.all(sendingAccounts.map(acc => this._getUtxoPool(acc.address, acc.addressType)))).flat();
138
+ async _fundPsbt(sendingAccounts, psbt, _feeRate, utxos, spendFully) {
139
+ const feeRate = _feeRate ?? await this.getFeeRate();
140
+ const utxoPool = utxos ?? (await Promise.all(sendingAccounts.map(acc => this._getUtxoPool(acc.address, acc.addressType)))).flat();
141
+ if (spendFully && utxoPool == null)
142
+ throw new Error("Cannot fully spend when no utxos are passed!");
142
143
  logger.debug("_fundPsbt(): fee rate: " + feeRate + " utxo pool: ", utxoPool);
143
144
  const accountPubkeys = {};
144
145
  sendingAccounts.forEach(acc => accountPubkeys[acc.address] = acc.pubkey);
@@ -177,13 +178,23 @@ class BitcoinWallet {
177
178
  });
178
179
  }
179
180
  logger.debug("_fundPsbt(): Coinselect targets: ", targets);
180
- let coinselectResult = (0, coinselect2_1.coinSelect)(utxoPool, targets, feeRate, sendingAccounts[0].addressType, requiredInputs);
181
+ let coinselectResult = spendFully
182
+ ? utils_2.utils.finalize(requiredInputs.concat(utxoPool.filter(utxo => !utils_2.utils.isDetrimentalInput(feeRate, utxo))), targets, feeRate, null)
183
+ : (0, coinselect2_1.coinSelect)(utxoPool, targets, feeRate, sendingAccounts[0].addressType, requiredInputs);
181
184
  logger.debug("_fundPsbt(): Coinselect result: ", coinselectResult);
182
- if (coinselectResult.inputs == null || coinselectResult.outputs == null) {
185
+ if (coinselectResult.inputs == null || coinselectResult.outputs == null || coinselectResult.effectiveFeeRate == null) {
183
186
  return {
184
187
  fee: coinselectResult.fee
185
188
  };
186
189
  }
190
+ if (spendFully && feeRate != null) {
191
+ const maximumAllowedFeeRate = (1.5 * feeRate) + 10;
192
+ if (coinselectResult.effectiveFeeRate > maximumAllowedFeeRate)
193
+ throw new Error(`Effective fee rate too high, feeRate: ${coinselectResult.effectiveFeeRate} sats/vB, maximum: ${maximumAllowedFeeRate} sats/vB!`);
194
+ const minimumAllowedFeeRate = 0.9 * feeRate;
195
+ if (coinselectResult.effectiveFeeRate < minimumAllowedFeeRate)
196
+ throw new Error(`Effective fee rate too low, feeRate: ${coinselectResult.effectiveFeeRate} sats/vB, minimum: ${minimumAllowedFeeRate} sats/vB!`);
197
+ }
187
198
  // Remove in/outs that are already in the PSBT
188
199
  coinselectResult.inputs.splice(0, psbt.inputsLength);
189
200
  coinselectResult.outputs.splice(0, psbt.outputsLength);
@@ -264,9 +275,18 @@ class BitcoinWallet {
264
275
  inputAddressIndexes
265
276
  };
266
277
  }
267
- async _getSpendableBalance(sendingAccounts, psbt, feeRate) {
278
+ async _getSpendableBalance(sendingAccounts, psbt, feeRate, outputAddressType, utxoPool) {
268
279
  feeRate ??= await this.getFeeRate();
269
- const utxoPool = (await Promise.all(sendingAccounts.map(acc => this._getUtxoPool(acc.address, acc.addressType)))).flat();
280
+ utxoPool ??= (await Promise.all(sendingAccounts.map(acc => this._getUtxoPool(acc.address, acc.addressType)))).flat();
281
+ return {
282
+ ...BitcoinWallet.getSpendableBalance(utxoPool ?? (await Promise.all(sendingAccounts.map(acc => this._getUtxoPool(acc.address, acc.addressType)))).flat(), feeRate ?? await this.getFeeRate(), psbt, outputAddressType),
283
+ feeRate
284
+ };
285
+ }
286
+ static bitcoinNetworkToObject(network) {
287
+ return btcNetworkMapping[network];
288
+ }
289
+ static getSpendableBalance(utxoPool, feeRate, psbt, outputAddressType) {
270
290
  const requiredInputs = [];
271
291
  if (psbt != null)
272
292
  for (let i = 0; i < psbt.inputsLength; i++) {
@@ -303,20 +323,13 @@ class BitcoinWallet {
303
323
  script: buffer_1.Buffer.from(output.script)
304
324
  });
305
325
  }
306
- const target = btc_signer_1.OutScript.encode({
307
- type: "wsh",
308
- hash: (0, Utils_1.randomBytes)(32)
309
- });
310
- let coinselectResult = (0, coinselect2_1.maxSendable)(utxoPool, { script: buffer_1.Buffer.from(target), type: "p2wsh" }, feeRate, requiredInputs, additionalOutputs);
326
+ const target = (0, BitcoinUtils_1.getDummyOutputScript)(outputAddressType ?? "p2wsh");
327
+ let coinselectResult = (0, coinselect2_1.maxSendable)(utxoPool, { script: buffer_1.Buffer.from(target), type: outputAddressType ?? "p2wsh" }, feeRate, requiredInputs, additionalOutputs);
311
328
  logger.debug("_getSpendableBalance(): Max spendable result: ", coinselectResult);
312
329
  return {
313
- feeRate: feeRate,
314
330
  balance: BigInt(Math.floor(coinselectResult.value)),
315
331
  totalFee: coinselectResult.fee
316
332
  };
317
333
  }
318
- static bitcoinNetworkToObject(network) {
319
- return btcNetworkMapping[network];
320
- }
321
334
  }
322
335
  exports.BitcoinWallet = BitcoinWallet;
@@ -1,4 +1,32 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
1
3
  import { Transaction } from "@scure/btc-signer";
4
+ import { CoinselectAddressTypes } from "../coinselect2";
5
+ /**
6
+ * UTXO data structure for Bitcoin wallets
7
+ *
8
+ * @category Bitcoin
9
+ */
10
+ export type BitcoinWalletUtxo = {
11
+ vout: number;
12
+ txId: string;
13
+ value: number;
14
+ type: CoinselectAddressTypes;
15
+ outputScript: Buffer;
16
+ address: string;
17
+ cpfp?: {
18
+ txVsize: number;
19
+ txEffectiveFeeRate: number;
20
+ };
21
+ confirmed: boolean;
22
+ };
23
+ /**
24
+ * Base UTXO data structure used for maximum spendable balance calculation, doesn't contain all the fields necessary
25
+ * for constructing the full transaction.
26
+ *
27
+ * @category Bitcoin
28
+ */
29
+ export type BitcoinWalletUtxoBase = Omit<BitcoinWalletUtxo, "txId" | "vout" | "outputScript" | "address" | "confirmed">;
2
30
  /**
3
31
  * Type guard to check if an object implements {@link IBitcoinWallet}
4
32
  *
@@ -25,8 +53,12 @@ export interface IBitcoinWallet {
25
53
  *
26
54
  * @param psbt PSBT to add the inputs to
27
55
  * @param feeRate Optional fee rate in sats/vB to use for the transaction
56
+ * @param utxos Pre-fetched list of UTXOs to spend from
57
+ * @param spendFully Instructs the wallet to spend all the passed UTXOs in the transaction without creating any
58
+ * change output, if the `feeRate` is passed, it will also enforce that the feeRate in sats/vB for the resulting
59
+ * transaction is not more than 50% and 10 sats/vB larger (considering also the CPFP adjustments)
28
60
  */
29
- fundPsbt(psbt: Transaction, feeRate?: number): Promise<Transaction>;
61
+ fundPsbt(psbt: Transaction, feeRate?: number, utxos?: BitcoinWalletUtxo[], spendFully?: boolean): Promise<Transaction>;
30
62
  /**
31
63
  * Signs inputs in the provided PSBT
32
64
  *
@@ -69,10 +101,16 @@ export interface IBitcoinWallet {
69
101
  *
70
102
  * @param psbt A PSBT to which additional inputs from wallet's UTXO set will be added and fee estimated
71
103
  * @param feeRate Optional fee rate in sats/vB to use for the transaction
104
+ * @param outputAddressType Expected output address type, if known
105
+ * @param utxos Optional pre-fetched UTXOs
72
106
  */
73
- getSpendableBalance(psbt?: Transaction, feeRate?: number): Promise<{
107
+ getSpendableBalance(psbt?: Transaction, feeRate?: number, outputAddressType?: CoinselectAddressTypes, utxos?: BitcoinWalletUtxoBase[]): Promise<{
74
108
  balance: bigint;
75
109
  feeRate: number;
76
110
  totalFee: number;
77
111
  }>;
112
+ /**
113
+ * Returns a list of available UTXOs for the wallet
114
+ */
115
+ getUtxoPool?(): Promise<BitcoinWalletUtxo[]>;
78
116
  }
@@ -6,6 +6,7 @@ import { Transaction } from "@scure/btc-signer";
6
6
  import { Buffer } from "buffer";
7
7
  import { BitcoinWallet } from "./BitcoinWallet";
8
8
  import { BitcoinNetwork, BitcoinRpcWithAddressIndex } from "@atomiqlabs/base";
9
+ import { BitcoinWalletUtxo, BitcoinWalletUtxoBase } from "./IBitcoinWallet";
9
10
  /**
10
11
  * Bitcoin wallet implementation deriving a single address from a WIF encoded private key
11
12
  *
@@ -37,7 +38,7 @@ export declare class SingleAddressBitcoinWallet extends BitcoinWallet {
37
38
  /**
38
39
  * @inheritDoc
39
40
  */
40
- fundPsbt(inputPsbt: Transaction, feeRate?: number): Promise<Transaction>;
41
+ fundPsbt(inputPsbt: Transaction, feeRate?: number, utxos?: BitcoinWalletUtxo[], spendFully?: boolean): Promise<Transaction>;
41
42
  /**
42
43
  * @inheritDoc
43
44
  */
@@ -68,11 +69,15 @@ export declare class SingleAddressBitcoinWallet extends BitcoinWallet {
68
69
  /**
69
70
  * @inheritDoc
70
71
  */
71
- getSpendableBalance(psbt?: Transaction, feeRate?: number): Promise<{
72
+ getSpendableBalance(psbt?: Transaction, feeRate?: number, outputAddressType?: CoinselectAddressTypes, utxos?: BitcoinWalletUtxoBase[]): Promise<{
72
73
  balance: bigint;
73
74
  feeRate: number;
74
75
  totalFee: number;
75
76
  }>;
77
+ /**
78
+ * @inheritDoc
79
+ */
80
+ getUtxoPool(): Promise<BitcoinWalletUtxo[]>;
76
81
  /**
77
82
  * Generates a new random private key WIF that can be used to instantiate the bitcoin wallet instance
78
83
  *
@@ -80,8 +80,8 @@ class SingleAddressBitcoinWallet extends BitcoinWallet_1.BitcoinWallet {
80
80
  /**
81
81
  * @inheritDoc
82
82
  */
83
- async fundPsbt(inputPsbt, feeRate) {
84
- const { psbt } = await super._fundPsbt(this.toBitcoinWalletAccounts(), inputPsbt, feeRate);
83
+ async fundPsbt(inputPsbt, feeRate, utxos, spendFully) {
84
+ const { psbt } = await super._fundPsbt(this.toBitcoinWalletAccounts(), inputPsbt, feeRate, utxos, spendFully);
85
85
  if (psbt == null) {
86
86
  throw new Error("Not enough balance!");
87
87
  }
@@ -133,8 +133,14 @@ class SingleAddressBitcoinWallet extends BitcoinWallet_1.BitcoinWallet {
133
133
  /**
134
134
  * @inheritDoc
135
135
  */
136
- getSpendableBalance(psbt, feeRate) {
137
- return this._getSpendableBalance([{ address: this.address, addressType: this.addressType }], psbt, feeRate);
136
+ getSpendableBalance(psbt, feeRate, outputAddressType, utxos) {
137
+ return this._getSpendableBalance([{ address: this.address, addressType: this.addressType }], psbt, feeRate, outputAddressType, utxos);
138
+ }
139
+ /**
140
+ * @inheritDoc
141
+ */
142
+ async getUtxoPool() {
143
+ return this._getUtxoPool(this.address, this.addressType);
138
144
  }
139
145
  /**
140
146
  * Generates a new random private key WIF that can be used to instantiate the bitcoin wallet instance
@@ -1,6 +1,5 @@
1
1
  import { ChainEvent, ChainType } from "@atomiqlabs/base";
2
2
  import { ISwap } from "../swaps/ISwap";
3
- import { EventListener } from "@atomiqlabs/base/src/events/ChainEvents";
4
3
  import { SwapType } from "../enums/SwapType";
5
4
  import { UnifiedSwapStorage } from "../storage/UnifiedSwapStorage";
6
5
  export type SwapEventListener<T extends ChainType, S extends ISwap<T>> = (event: ChainEvent<T["Data"]>, swap: S) => Promise<void>;
@@ -15,8 +14,10 @@ export declare class UnifiedSwapEventListener<T extends ChainType> {
15
14
  };
16
15
  constructor(unifiedStorage: UnifiedSwapStorage<T>, events: T["Events"]);
17
16
  processEvents(events: ChainEvent<T["Data"]>[]): Promise<void>;
18
- listener?: EventListener<T["Data"]>;
19
- start(): Promise<void>;
17
+ private noAutomaticPoll?;
18
+ private listener?;
19
+ start(noAutomaticPoll?: boolean): Promise<void>;
20
+ poll(previousState: any): Promise<any>;
20
21
  stop(): Promise<void>;
21
22
  registerListener<S extends ISwap<T>>(type: SwapType, listener: SwapEventListener<T, S>, reviver: new (val: any) => S): void;
22
23
  unregisterListener(type: SwapType): boolean;
@@ -96,13 +96,14 @@ class UnifiedSwapEventListener {
96
96
  }
97
97
  }
98
98
  }
99
- async start() {
99
+ async start(noAutomaticPoll) {
100
100
  if (this.listener != null)
101
101
  return;
102
102
  logger.info("start(): Starting unified swap event listener");
103
103
  await this.storage.init();
104
104
  logger.debug("start(): Storage initialized");
105
- await this.events.init();
105
+ await this.events.init(noAutomaticPoll);
106
+ this.noAutomaticPoll = noAutomaticPoll;
106
107
  logger.debug("start(): Events initialized");
107
108
  this.events.registerListener(this.listener = async (events) => {
108
109
  await this.processEvents(events);
@@ -110,6 +111,11 @@ class UnifiedSwapEventListener {
110
111
  });
111
112
  logger.info("start(): Successfully initiated the unified swap event listener!");
112
113
  }
114
+ poll(previousState) {
115
+ if (!this.noAutomaticPoll)
116
+ throw new Error("Only supported when no automatic events polling is configured!");
117
+ return this.events.poll(previousState);
118
+ }
113
119
  stop() {
114
120
  logger.info("stop(): Stopping unified swap event listener");
115
121
  if (this.listener != null)
@@ -13,15 +13,17 @@ export declare function fetchWithTimeout(input: RequestInfo | URL, init: Request
13
13
  * @param timeout Timeout (in milliseconds) for the request to conclude
14
14
  * @param abortSignal
15
15
  * @param allowNon200 Whether to allow non-200 status code HTTP responses
16
+ * @param headers
16
17
  * @throws {RequestError} if non 200 response code was returned or body cannot be parsed
17
18
  */
18
- export declare function httpGet<T>(url: string, timeout?: number, abortSignal?: AbortSignal, allowNon200?: boolean): Promise<T>;
19
+ export declare function httpGet<T>(url: string, timeout?: number, abortSignal?: AbortSignal, allowNon200?: boolean, headers?: Record<string, string>): Promise<T>;
19
20
  /**
20
21
  * Sends an HTTP POST request through a fetch API, handles non 200 response codes as errors
21
22
  * @param url Send request to this URL
22
23
  * @param body A HTTP request body to send to the server
23
24
  * @param timeout Timeout (in milliseconds) for the request to conclude
24
25
  * @param abortSignal
26
+ * @param headers
25
27
  * @throws {RequestError} if non 200 response code was returned
26
28
  */
27
- export declare function httpPost<T>(url: string, body: any, timeout?: number, abortSignal?: AbortSignal): Promise<T>;
29
+ export declare function httpPost<T>(url: string, body: any, timeout?: number, abortSignal?: AbortSignal, headers?: Record<string, string>): Promise<T>;
@@ -30,13 +30,15 @@ exports.fetchWithTimeout = fetchWithTimeout;
30
30
  * @param timeout Timeout (in milliseconds) for the request to conclude
31
31
  * @param abortSignal
32
32
  * @param allowNon200 Whether to allow non-200 status code HTTP responses
33
+ * @param headers
33
34
  * @throws {RequestError} if non 200 response code was returned or body cannot be parsed
34
35
  */
35
- async function httpGet(url, timeout, abortSignal, allowNon200 = false) {
36
+ async function httpGet(url, timeout, abortSignal, allowNon200 = false, headers = {}) {
36
37
  const init = {
37
38
  method: "GET",
38
39
  timeout,
39
- signal: abortSignal
40
+ signal: abortSignal,
41
+ headers
40
42
  };
41
43
  const response = await fetchWithTimeout(url, init);
42
44
  if (response.status !== 200) {
@@ -65,14 +67,18 @@ exports.httpGet = httpGet;
65
67
  * @param body A HTTP request body to send to the server
66
68
  * @param timeout Timeout (in milliseconds) for the request to conclude
67
69
  * @param abortSignal
70
+ * @param headers
68
71
  * @throws {RequestError} if non 200 response code was returned
69
72
  */
70
- async function httpPost(url, body, timeout, abortSignal) {
73
+ async function httpPost(url, body, timeout, abortSignal, headers = {}) {
71
74
  const init = {
72
75
  method: "POST",
73
76
  timeout,
74
77
  body: JSON.stringify(body),
75
- headers: { 'Content-Type': 'application/json' },
78
+ headers: {
79
+ ...headers,
80
+ 'Content-Type': 'application/json'
81
+ },
76
82
  signal: abortSignal
77
83
  };
78
84
  const response = timeout == null ? await fetch(url, init) : await fetchWithTimeout(url, init);
@@ -11,6 +11,7 @@ export type RequestBody = {
11
11
  * @param timeout Timeout in millseconds for the request to succeed & all its response properties to resolve
12
12
  * @param signal Abort signal
13
13
  * @param streamRequest Whether the request should be streamed or not
14
+ * @param _headers
14
15
  * @throws {RequestError} When the response code is not 200
15
16
  */
16
- export declare function streamingFetchPromise<T extends RequestSchema>(url: string, body: RequestBody, schema: T, timeout?: number, signal?: AbortSignal, streamRequest?: boolean): Promise<RequestSchemaResultPromise<T>>;
17
+ export declare function streamingFetchPromise<T extends RequestSchema>(url: string, body: RequestBody, schema: T, timeout?: number, signal?: AbortSignal, streamRequest?: boolean, _headers?: Record<string, string>): Promise<RequestSchemaResultPromise<T>>;
@@ -39,14 +39,15 @@ logger.info("Environment supports request stream: " + supportsRequestStreams);
39
39
  * @param timeout Timeout in millseconds for the request to succeed & all its response properties to resolve
40
40
  * @param signal Abort signal
41
41
  * @param streamRequest Whether the request should be streamed or not
42
+ * @param _headers
42
43
  * @throws {RequestError} When the response code is not 200
43
44
  */
44
- async function streamingFetchPromise(url, body, schema, timeout, signal, streamRequest) {
45
+ async function streamingFetchPromise(url, body, schema, timeout, signal, streamRequest, _headers = {}) {
45
46
  if (streamRequest == null)
46
47
  streamRequest = supportsRequestStreams;
47
48
  if (timeout != null)
48
49
  signal = (0, TimeoutUtils_1.timeoutSignal)(timeout, new Error("Network request timed out"), signal);
49
- const headers = {};
50
+ const headers = { ..._headers };
50
51
  const init = {
51
52
  method: "POST",
52
53
  headers
package/dist/index.d.ts CHANGED
@@ -78,6 +78,7 @@ export * from "./types/SwapStateInfo";
78
78
  export * from "./types/AmountData";
79
79
  export * from "./types/CustomPriceFunction";
80
80
  export * from "./types/SwapExecutionAction";
81
+ export * from "./types/SwapExecutionStep";
81
82
  export * from "./types/SwapWithSigner";
82
83
  export * from "./types/Token";
83
84
  export * from "./types/TokenAmount";
package/dist/index.js CHANGED
@@ -151,6 +151,7 @@ __exportStar(require("./types/SwapStateInfo"), exports);
151
151
  __exportStar(require("./types/AmountData"), exports);
152
152
  __exportStar(require("./types/CustomPriceFunction"), exports);
153
153
  __exportStar(require("./types/SwapExecutionAction"), exports);
154
+ __exportStar(require("./types/SwapExecutionStep"), exports);
154
155
  __exportStar(require("./types/SwapWithSigner"), exports);
155
156
  __exportStar(require("./types/Token"), exports);
156
157
  __exportStar(require("./types/TokenAmount"), exports);
@@ -3,6 +3,7 @@ import { Intermediary } from "./Intermediary";
3
3
  import { SwapType } from "../enums/SwapType";
4
4
  import { SpvVaultContract, SwapContract } from "@atomiqlabs/base";
5
5
  import { EventEmitter } from "events";
6
+ import { IntermediaryAPI } from "./apis/IntermediaryAPI";
6
7
  /**
7
8
  * Swap handler type mapping for intermediary communication
8
9
  *
@@ -109,6 +110,10 @@ export declare class IntermediaryDiscovery extends EventEmitter {
109
110
  * @private
110
111
  */
111
112
  private overrideNodeUrls?;
113
+ /**
114
+ * @private
115
+ */
116
+ private lpApi;
112
117
  constructor(swapContracts: {
113
118
  [chainIdentifier: string]: {
114
119
  [contractVersion: string]: {
@@ -116,7 +121,7 @@ export declare class IntermediaryDiscovery extends EventEmitter {
116
121
  spvVaultContract: SpvVaultContract;
117
122
  };
118
123
  };
119
- }, registryUrl?: string, nodeUrls?: string[], httpRequestTimeout?: number, maxWaitForOthersTimeout?: number);
124
+ }, lpApi: IntermediaryAPI, registryUrl?: string, nodeUrls?: string[], httpRequestTimeout?: number, maxWaitForOthersTimeout?: number);
120
125
  /**
121
126
  * Fetches the URLs of swap intermediaries from registry or from a pre-defined array of node urls
122
127
  *
@@ -193,7 +198,7 @@ export declare class IntermediaryDiscovery extends EventEmitter {
193
198
  /**
194
199
  * Returns swap candidates for a specific swap type & token address
195
200
  *
196
- * @remark Also filters the LPs based on supported swap versions
201
+ * @remarks Also filters the LPs based on supported swap versions
197
202
  *
198
203
  * @param chainIdentifier Chain identifier of the smart chain
199
204
  * @param swapType Swap protocol type
@@ -6,7 +6,6 @@ const SwapType_1 = require("../enums/SwapType");
6
6
  const events_1 = require("events");
7
7
  const buffer_1 = require("buffer");
8
8
  const Utils_1 = require("../utils/Utils");
9
- const IntermediaryAPI_1 = require("./apis/IntermediaryAPI");
10
9
  const Logger_1 = require("../utils/Logger");
11
10
  const HttpUtils_1 = require("../http/HttpUtils");
12
11
  const RetryUtils_1 = require("../utils/RetryUtils");
@@ -94,7 +93,7 @@ const DEFAULT_CHAIN = "SOLANA";
94
93
  * @category LPs
95
94
  */
96
95
  class IntermediaryDiscovery extends events_1.EventEmitter {
97
- constructor(swapContracts, registryUrl = REGISTRY_URL, nodeUrls, httpRequestTimeout, maxWaitForOthersTimeout) {
96
+ constructor(swapContracts, lpApi, registryUrl = REGISTRY_URL, nodeUrls, httpRequestTimeout, maxWaitForOthersTimeout) {
98
97
  super();
99
98
  /**
100
99
  * A current list of active intermediaries
@@ -105,6 +104,7 @@ class IntermediaryDiscovery extends events_1.EventEmitter {
105
104
  this.overrideNodeUrls = nodeUrls;
106
105
  this.httpRequestTimeout = httpRequestTimeout;
107
106
  this.maxWaitForOthersTimeout = maxWaitForOthersTimeout;
107
+ this.lpApi = lpApi;
108
108
  }
109
109
  /**
110
110
  * Fetches the URLs of swap intermediaries from registry or from a pre-defined array of node urls
@@ -129,7 +129,7 @@ class IntermediaryDiscovery extends events_1.EventEmitter {
129
129
  * @param abortSignal
130
130
  */
131
131
  async getNodeInfo(url, abortSignal) {
132
- const response = await (0, RetryUtils_1.tryWithRetries)(() => IntermediaryAPI_1.IntermediaryAPI.getIntermediaryInfo(url, this.httpRequestTimeout, abortSignal), { maxRetries: 3, delay: 100, exponential: true }, undefined, abortSignal, "debug");
132
+ const response = await (0, RetryUtils_1.tryWithRetries)(() => this.lpApi.getIntermediaryInfo(url, this.httpRequestTimeout, abortSignal), { maxRetries: 3, delay: 100, exponential: true }, undefined, abortSignal, "debug");
133
133
  abortSignal?.throwIfAborted();
134
134
  const promises = [];
135
135
  const addresses = {};
@@ -365,7 +365,7 @@ class IntermediaryDiscovery extends events_1.EventEmitter {
365
365
  /**
366
366
  * Returns swap candidates for a specific swap type & token address
367
367
  *
368
- * @remark Also filters the LPs based on supported swap versions
368
+ * @remarks Also filters the LPs based on supported swap versions
369
369
  *
370
370
  * @param chainIdentifier Chain identifier of the smart chain
371
371
  * @param swapType Swap protocol type