@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
@@ -132,6 +132,13 @@ function transactionBytes (
132
132
  return Math.ceil(size);
133
133
  }
134
134
 
135
+ function numberOrNaN(v: number): number {
136
+ if (typeof v !== 'number') return NaN;
137
+ if (!isFinite(v)) return NaN;
138
+ if (v < 0) return NaN;
139
+ return v;
140
+ }
141
+
135
142
  function uintOrNaN(v: number): number {
136
143
  if (typeof v !== 'number') return NaN;
137
144
  if (!isFinite(v)) return NaN;
@@ -148,41 +155,73 @@ function sumOrNaN(range: {value: number}[]): number {
148
155
  return range.reduce((a, x) => a + uintOrNaN(x.value), 0);
149
156
  }
150
157
 
151
- function finalize(
152
- inputs: CoinselectTxInput[],
158
+ function finalize<T extends Omit<CoinselectTxInput, "txId" | "address" | "vout" | "outputScript">>(
159
+ inputs: T[],
153
160
  outputs: CoinselectTxOutput[],
154
161
  feeRate: number,
155
- changeType: CoinselectAddressTypes,
162
+ changeType: CoinselectAddressTypes | null,
156
163
  cpfpAddFee: number = 0
157
164
  ): {
158
- inputs?: CoinselectTxInput[],
165
+ inputs?: T[],
159
166
  outputs?: CoinselectTxOutput[],
167
+ effectiveFeeRate?: number,
160
168
  fee: number
161
169
  } {
162
- const bytesAccum = transactionBytes(inputs, outputs, changeType);
170
+ const bytesAccum = transactionBytes(inputs, outputs, changeType ?? undefined);
163
171
  logger.debug("finalize(): Transaction bytes: ", bytesAccum);
164
172
 
165
- const feeAfterExtraOutput = (feeRate * (bytesAccum + outputBytes({type: changeType}))) + cpfpAddFee;
166
- logger.debug("finalize(): TX fee after adding change output: ", feeAfterExtraOutput);
167
- const remainderAfterExtraOutput = Math.floor(sumOrNaN(inputs) - (sumOrNaN(outputs) + feeAfterExtraOutput));
168
- logger.debug("finalize(): Leaves change (changeType="+changeType+") value: ", remainderAfterExtraOutput);
173
+ if(changeType!=null) {
174
+ const feeAfterExtraOutput = (feeRate * (bytesAccum + outputBytes({type: changeType}))) + cpfpAddFee;
175
+ logger.debug("finalize(): TX fee after adding change output: ", feeAfterExtraOutput);
176
+ const remainderAfterExtraOutput = Math.floor(sumOrNaN(inputs) - (sumOrNaN(outputs) + feeAfterExtraOutput));
177
+ logger.debug("finalize(): Leaves change (changeType="+changeType+") value: ", remainderAfterExtraOutput);
169
178
 
170
- // is it worth a change output?
171
- if (remainderAfterExtraOutput >= dustThreshold({type: changeType})) {
172
- outputs = outputs.concat({ value: remainderAfterExtraOutput, type: changeType })
179
+ // is it worth a change output?
180
+ if (remainderAfterExtraOutput >= dustThreshold({type: changeType})) {
181
+ outputs = outputs.concat({ value: remainderAfterExtraOutput, type: changeType })
182
+ }
173
183
  }
174
184
 
175
185
  const fee = sumOrNaN(inputs) - sumOrNaN(outputs);
176
186
  logger.debug("finalize(): Re-calculated total fee: ", fee);
177
- if (!isFinite(fee)) return { fee: (feeRate * bytesAccum) + cpfpAddFee }
187
+ if (!isFinite(fee) || fee<0) return { fee: (feeRate * bytesAccum) + cpfpAddFee }
188
+
189
+ let txVSize = utils.transactionBytes(inputs, outputs);
190
+ let txFee = fee;
191
+ const cpfpSortedInputs = [...inputs].sort(
192
+ (a, b) => (b.cpfp?.txEffectiveFeeRate ?? 0) - (a.cpfp?.txEffectiveFeeRate ?? 0)
193
+ );
194
+ cpfpSortedInputs.forEach(input => {
195
+ if(input.cpfp==null) return;
196
+ const currentEffectiveFeeRate = txFee / txVSize;
197
+ if(currentEffectiveFeeRate > input.cpfp.txEffectiveFeeRate) {
198
+ txVSize += input.cpfp.txVsize;
199
+ txFee += input.cpfp.txVsize * input.cpfp.txEffectiveFeeRate;
200
+ }
201
+ }
202
+ );
178
203
 
179
204
  return {
180
205
  inputs: inputs,
181
206
  outputs: outputs,
207
+ effectiveFeeRate: txFee / txVSize,
182
208
  fee: fee
183
209
  }
184
210
  }
185
211
 
212
+ function isDetrimentalInput(
213
+ feeRate: number,
214
+ utxo: Omit<CoinselectTxInput, "txId" | "address" | "vout" | "outputScript">
215
+ ) {
216
+ const utxoBytes = utils.inputBytes(utxo);
217
+ const utxoFee = feeRate * utxoBytes;
218
+ let cpfpFee = 0;
219
+ if(utxo.cpfp!=null && utxo.cpfp.txEffectiveFeeRate<feeRate) cpfpFee = Math.ceil(utxo.cpfp.txVsize*(feeRate - utxo.cpfp.txEffectiveFeeRate));
220
+
221
+ // skip detrimental input
222
+ return utxoFee + cpfpFee > utxo.value;
223
+ }
224
+
186
225
  export const utils = {
187
226
  dustThreshold: dustThreshold,
188
227
  finalize: finalize,
@@ -191,5 +230,7 @@ export const utils = {
191
230
  sumOrNaN: sumOrNaN,
192
231
  sumForgiving: sumForgiving,
193
232
  transactionBytes: transactionBytes,
194
- uintOrNaN: uintOrNaN
233
+ uintOrNaN: uintOrNaN,
234
+ numberOrNaN: numberOrNaN,
235
+ isDetrimentalInput
195
236
  };
@@ -1,32 +1,14 @@
1
1
  import {coinSelect, maxSendable, CoinselectAddressTypes, CoinselectTxInput} from "../coinselect2";
2
2
  import {BTC_NETWORK, NETWORK, TEST_NETWORK} from "@scure/btc-signer/utils"
3
3
  import {p2wpkh, OutScript, Transaction, p2tr, Address} from "@scure/btc-signer";
4
- import {IBitcoinWallet} from "./IBitcoinWallet";
4
+ import {BitcoinWalletUtxo, BitcoinWalletUtxoBase, IBitcoinWallet} from "./IBitcoinWallet";
5
5
  import {Buffer} from "buffer";
6
6
  import {randomBytes} from "../../utils/Utils";
7
- import {toCoinselectAddressType, toOutputScript} from "../../utils/BitcoinUtils";
7
+ import {getDummyOutputScript, toCoinselectAddressType, toOutputScript} from "../../utils/BitcoinUtils";
8
8
  import {TransactionInputUpdate} from "@scure/btc-signer/psbt";
9
9
  import {getLogger} from "../../utils/Logger";
10
10
  import {BitcoinNetwork, BitcoinRpcWithAddressIndex} from "@atomiqlabs/base";
11
-
12
- /**
13
- * UTXO data structure for Bitcoin wallets
14
- *
15
- * @category Bitcoin
16
- */
17
- export type BitcoinWalletUtxo = {
18
- vout: number,
19
- txId: string,
20
- value: number,
21
- type: CoinselectAddressTypes,
22
- outputScript: Buffer,
23
- address: string,
24
- cpfp?: {
25
- txVsize: number,
26
- txEffectiveFeeRate: number
27
- },
28
- confirmed: boolean
29
- };
11
+ import {utils} from "../coinselect2/utils";
30
12
 
31
13
  /**
32
14
  * Identifies the address type of a Bitcoin address
@@ -200,15 +182,18 @@ export abstract class BitcoinWallet implements IBitcoinWallet {
200
182
  addressType: CoinselectAddressTypes,
201
183
  }[],
202
184
  psbt: Transaction,
203
- feeRate?: number
185
+ _feeRate?: number,
186
+ utxos?: BitcoinWalletUtxo[],
187
+ spendFully?: boolean
204
188
  ): Promise<{
205
189
  fee: number,
206
190
  psbt?: Transaction,
207
191
  inputAddressIndexes?: {[address: string]: number[]}
208
192
  }> {
209
- if(feeRate==null) feeRate = await this.getFeeRate();
193
+ const feeRate = _feeRate ?? await this.getFeeRate();
194
+ const utxoPool: BitcoinWalletUtxo[] = utxos ?? (await Promise.all(sendingAccounts.map(acc => this._getUtxoPool(acc.address, acc.addressType)))).flat();
210
195
 
211
- const utxoPool: BitcoinWalletUtxo[] = (await Promise.all(sendingAccounts.map(acc => this._getUtxoPool(acc.address, acc.addressType)))).flat();
196
+ if(spendFully && utxoPool==null) throw new Error("Cannot fully spend when no utxos are passed!");
212
197
 
213
198
  logger.debug("_fundPsbt(): fee rate: "+feeRate+" utxo pool: ", utxoPool);
214
199
 
@@ -247,15 +232,26 @@ export abstract class BitcoinWallet implements IBitcoinWallet {
247
232
  }
248
233
  logger.debug("_fundPsbt(): Coinselect targets: ", targets);
249
234
 
250
- let coinselectResult = coinSelect(utxoPool, targets, feeRate, sendingAccounts[0].addressType, requiredInputs);
235
+ let coinselectResult = spendFully
236
+ ? utils.finalize(requiredInputs.concat(utxoPool.filter(utxo => !utils.isDetrimentalInput(feeRate, utxo))), targets, feeRate, null)
237
+ : coinSelect(utxoPool, targets, feeRate, sendingAccounts[0].addressType, requiredInputs);
251
238
  logger.debug("_fundPsbt(): Coinselect result: ", coinselectResult);
252
239
 
253
- if(coinselectResult.inputs==null || coinselectResult.outputs==null) {
240
+ if(coinselectResult.inputs==null || coinselectResult.outputs==null || coinselectResult.effectiveFeeRate==null) {
254
241
  return {
255
242
  fee: coinselectResult.fee
256
243
  };
257
244
  }
258
245
 
246
+ if(spendFully && feeRate!=null) {
247
+ const maximumAllowedFeeRate = (1.5*feeRate) + 10;
248
+ if(coinselectResult.effectiveFeeRate > maximumAllowedFeeRate)
249
+ throw new Error(`Effective fee rate too high, feeRate: ${coinselectResult.effectiveFeeRate} sats/vB, maximum: ${maximumAllowedFeeRate} sats/vB!`);
250
+ const minimumAllowedFeeRate = 0.9*feeRate;
251
+ if(coinselectResult.effectiveFeeRate < minimumAllowedFeeRate)
252
+ throw new Error(`Effective fee rate too low, feeRate: ${coinselectResult.effectiveFeeRate} sats/vB, minimum: ${minimumAllowedFeeRate} sats/vB!`);
253
+ }
254
+
259
255
  // Remove in/outs that are already in the PSBT
260
256
  coinselectResult.inputs.splice(0, psbt.inputsLength);
261
257
  coinselectResult.outputs.splice(0, psbt.outputsLength);
@@ -346,16 +342,59 @@ export abstract class BitcoinWallet implements IBitcoinWallet {
346
342
  addressType: CoinselectAddressTypes,
347
343
  }[],
348
344
  psbt?: Transaction,
349
- feeRate?: number
345
+ feeRate?: number,
346
+ outputAddressType?: CoinselectAddressTypes,
347
+ utxoPool?: BitcoinWalletUtxoBase[]
350
348
  ): Promise<{
351
349
  balance: bigint,
352
350
  feeRate: number,
353
351
  totalFee: number
354
352
  }> {
355
353
  feeRate ??= await this.getFeeRate();
354
+ utxoPool ??= (await Promise.all(sendingAccounts.map(acc => this._getUtxoPool(acc.address, acc.addressType)))).flat();
355
+
356
+ return {
357
+ ...BitcoinWallet.getSpendableBalance(
358
+ utxoPool ?? (await Promise.all(sendingAccounts.map(acc => this._getUtxoPool(acc.address, acc.addressType)))).flat(),
359
+ feeRate ?? await this.getFeeRate(),
360
+ psbt,
361
+ outputAddressType
362
+ ),
363
+ feeRate
364
+ };
365
+ }
366
+
367
+ abstract sendTransaction(address: string, amount: bigint, feeRate?: number): Promise<string>;
368
+ abstract fundPsbt(psbt: Transaction, feeRate?: number, utxos?: BitcoinWalletUtxo[], spendFully?: boolean): Promise<Transaction>;
369
+ abstract signPsbt(psbt: Transaction, signInputs: number[]): Promise<Transaction>;
370
+
371
+ abstract getTransactionFee(address: string, amount: bigint, feeRate?: number): Promise<number>;
372
+ abstract getFundedPsbtFee(psbt: Transaction, feeRate?: number): Promise<number>;
373
+
374
+ abstract getReceiveAddress(): string;
375
+ abstract getBalance(): Promise<{
376
+ confirmedBalance: bigint,
377
+ unconfirmedBalance: bigint
378
+ }>;
379
+ abstract getSpendableBalance(psbt?: Transaction, feeRate?: number): Promise<{
380
+ balance: bigint,
381
+ feeRate: number,
382
+ totalFee: number
383
+ }>;
356
384
 
357
- const utxoPool: BitcoinWalletUtxo[] = (await Promise.all(sendingAccounts.map(acc => this._getUtxoPool(acc.address, acc.addressType)))).flat();
385
+ static bitcoinNetworkToObject(network: BitcoinNetwork): BTC_NETWORK {
386
+ return btcNetworkMapping[network];
387
+ }
358
388
 
389
+ static getSpendableBalance(
390
+ utxoPool: BitcoinWalletUtxoBase[],
391
+ feeRate: number,
392
+ psbt?: Transaction,
393
+ outputAddressType?: CoinselectAddressTypes
394
+ ): {
395
+ balance: bigint,
396
+ totalFee: number
397
+ } {
359
398
  const requiredInputs: CoinselectTxInput[] = [];
360
399
  if(psbt!=null) for(let i=0;i<psbt.inputsLength;i++) {
361
400
  const input = psbt.getInput(i);
@@ -387,42 +426,15 @@ export abstract class BitcoinWallet implements IBitcoinWallet {
387
426
  })
388
427
  }
389
428
 
390
- const target = OutScript.encode({
391
- type: "wsh",
392
- hash: randomBytes(32)
393
- });
394
-
395
- let coinselectResult = maxSendable(utxoPool, {script: Buffer.from(target), type: "p2wsh"}, feeRate, requiredInputs, additionalOutputs);
429
+ const target: Uint8Array = getDummyOutputScript(outputAddressType ?? "p2wsh");
430
+ let coinselectResult = maxSendable(utxoPool, {script: Buffer.from(target), type: outputAddressType ?? "p2wsh"}, feeRate, requiredInputs, additionalOutputs);
396
431
 
397
432
  logger.debug("_getSpendableBalance(): Max spendable result: ", coinselectResult);
398
433
 
399
434
  return {
400
- feeRate: feeRate,
401
435
  balance: BigInt(Math.floor(coinselectResult.value)),
402
436
  totalFee: coinselectResult.fee
403
437
  }
404
438
  }
405
439
 
406
- abstract sendTransaction(address: string, amount: bigint, feeRate?: number): Promise<string>;
407
- abstract fundPsbt(psbt: Transaction, feeRate?: number): Promise<Transaction>;
408
- abstract signPsbt(psbt: Transaction, signInputs: number[]): Promise<Transaction>;
409
-
410
- abstract getTransactionFee(address: string, amount: bigint, feeRate?: number): Promise<number>;
411
- abstract getFundedPsbtFee(psbt: Transaction, feeRate?: number): Promise<number>;
412
-
413
- abstract getReceiveAddress(): string;
414
- abstract getBalance(): Promise<{
415
- confirmedBalance: bigint,
416
- unconfirmedBalance: bigint
417
- }>;
418
- abstract getSpendableBalance(psbt?: Transaction, feeRate?: number): Promise<{
419
- balance: bigint,
420
- feeRate: number,
421
- totalFee: number
422
- }>;
423
-
424
- static bitcoinNetworkToObject(network: BitcoinNetwork): BTC_NETWORK {
425
- return btcNetworkMapping[network];
426
- }
427
-
428
440
  }
@@ -1,4 +1,33 @@
1
- import {Transaction} from "@scure/btc-signer";
1
+ import {Address, Transaction} from "@scure/btc-signer";
2
+ import {BTC_NETWORK} from "@scure/btc-signer/utils";
3
+ import {CoinselectAddressTypes} from "../coinselect2";
4
+
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
+ /**
25
+ * Base UTXO data structure used for maximum spendable balance calculation, doesn't contain all the fields necessary
26
+ * for constructing the full transaction.
27
+ *
28
+ * @category Bitcoin
29
+ */
30
+ export type BitcoinWalletUtxoBase = Omit<BitcoinWalletUtxo, "txId" | "vout" | "outputScript" | "address" | "confirmed">;
2
31
 
3
32
  /**
4
33
  * Type guard to check if an object implements {@link IBitcoinWallet}
@@ -39,8 +68,12 @@ export interface IBitcoinWallet {
39
68
  *
40
69
  * @param psbt PSBT to add the inputs to
41
70
  * @param feeRate Optional fee rate in sats/vB to use for the transaction
71
+ * @param utxos Pre-fetched list of UTXOs to spend from
72
+ * @param spendFully Instructs the wallet to spend all the passed UTXOs in the transaction without creating any
73
+ * change output, if the `feeRate` is passed, it will also enforce that the feeRate in sats/vB for the resulting
74
+ * transaction is not more than 50% and 10 sats/vB larger (considering also the CPFP adjustments)
42
75
  */
43
- fundPsbt(psbt: Transaction, feeRate?: number): Promise<Transaction>;
76
+ fundPsbt(psbt: Transaction, feeRate?: number, utxos?: BitcoinWalletUtxo[], spendFully?: boolean): Promise<Transaction>;
44
77
 
45
78
  /**
46
79
  * Signs inputs in the provided PSBT
@@ -90,10 +123,18 @@ export interface IBitcoinWallet {
90
123
  *
91
124
  * @param psbt A PSBT to which additional inputs from wallet's UTXO set will be added and fee estimated
92
125
  * @param feeRate Optional fee rate in sats/vB to use for the transaction
126
+ * @param outputAddressType Expected output address type, if known
127
+ * @param utxos Optional pre-fetched UTXOs
93
128
  */
94
- getSpendableBalance(psbt?: Transaction, feeRate?: number): Promise<{
129
+ getSpendableBalance(psbt?: Transaction, feeRate?: number, outputAddressType?: CoinselectAddressTypes, utxos?: BitcoinWalletUtxoBase[]): Promise<{
95
130
  balance: bigint,
96
131
  feeRate: number,
97
132
  totalFee: number
98
133
  }>;
134
+
135
+ /**
136
+ * Returns a list of available UTXOs for the wallet
137
+ */
138
+ getUtxoPool?(): Promise<BitcoinWalletUtxo[]>;
139
+
99
140
  }
@@ -8,6 +8,7 @@ import {HDKey} from "@scure/bip32";
8
8
  import {entropyToMnemonic, generateMnemonic, mnemonicToSeed} from "@scure/bip39";
9
9
  import {wordlist} from "@scure/bip39/wordlists/english.js";
10
10
  import {sha256} from "@noble/hashes/sha2";
11
+ import {BitcoinWalletUtxo, BitcoinWalletUtxoBase} from "./IBitcoinWallet";
11
12
 
12
13
  const logger = getLogger("SingleAddressBitcoinWallet: ");
13
14
 
@@ -88,8 +89,8 @@ export class SingleAddressBitcoinWallet extends BitcoinWallet {
88
89
  /**
89
90
  * @inheritDoc
90
91
  */
91
- async fundPsbt(inputPsbt: Transaction, feeRate?: number): Promise<Transaction> {
92
- const {psbt} = await super._fundPsbt(this.toBitcoinWalletAccounts(), inputPsbt, feeRate);
92
+ async fundPsbt(inputPsbt: Transaction, feeRate?: number, utxos?: BitcoinWalletUtxo[], spendFully?: boolean): Promise<Transaction> {
93
+ const {psbt} = await super._fundPsbt(this.toBitcoinWalletAccounts(), inputPsbt, feeRate, utxos, spendFully);
93
94
  if(psbt==null) {
94
95
  throw new Error("Not enough balance!");
95
96
  }
@@ -150,12 +151,19 @@ export class SingleAddressBitcoinWallet extends BitcoinWallet {
150
151
  /**
151
152
  * @inheritDoc
152
153
  */
153
- getSpendableBalance(psbt?: Transaction, feeRate?: number): Promise<{
154
+ getSpendableBalance(psbt?: Transaction, feeRate?: number, outputAddressType?: CoinselectAddressTypes, utxos?: BitcoinWalletUtxoBase[]): Promise<{
154
155
  balance: bigint,
155
156
  feeRate: number,
156
157
  totalFee: number
157
158
  }> {
158
- return this._getSpendableBalance([{address: this.address, addressType: this.addressType}], psbt, feeRate);
159
+ return this._getSpendableBalance([{address: this.address, addressType: this.addressType}], psbt, feeRate, outputAddressType, utxos);
160
+ }
161
+
162
+ /**
163
+ * @inheritDoc
164
+ */
165
+ async getUtxoPool(): Promise<BitcoinWalletUtxo[]> {
166
+ return this._getUtxoPool(this.address, this.addressType);
159
167
  }
160
168
 
161
169
  /**
@@ -136,13 +136,16 @@ export class UnifiedSwapEventListener<
136
136
 
137
137
  }
138
138
 
139
- listener?: EventListener<T["Data"]>;
140
- async start() {
139
+ private noAutomaticPoll?: boolean;
140
+ private listener?: EventListener<T["Data"]>;
141
+
142
+ async start(noAutomaticPoll?: boolean) {
141
143
  if(this.listener!=null) return;
142
144
  logger.info("start(): Starting unified swap event listener");
143
145
  await this.storage.init();
144
146
  logger.debug("start(): Storage initialized");
145
- await this.events.init();
147
+ await this.events.init(noAutomaticPoll);
148
+ this.noAutomaticPoll = noAutomaticPoll;
146
149
  logger.debug("start(): Events initialized");
147
150
  this.events.registerListener(this.listener = async (events) => {
148
151
  await this.processEvents(events);
@@ -151,6 +154,11 @@ export class UnifiedSwapEventListener<
151
154
  logger.info("start(): Successfully initiated the unified swap event listener!");
152
155
  }
153
156
 
157
+ poll(previousState: any): Promise<any> {
158
+ if(!this.noAutomaticPoll) throw new Error("Only supported when no automatic events polling is configured!");
159
+ return this.events.poll(previousState);
160
+ }
161
+
154
162
  stop(): Promise<void> {
155
163
  logger.info("stop(): Stopping unified swap event listener");
156
164
  if(this.listener!=null) this.events.unregisterListener(this.listener);
@@ -29,13 +29,15 @@ export function fetchWithTimeout(input: RequestInfo | URL, init: RequestInit & {
29
29
  * @param timeout Timeout (in milliseconds) for the request to conclude
30
30
  * @param abortSignal
31
31
  * @param allowNon200 Whether to allow non-200 status code HTTP responses
32
+ * @param headers
32
33
  * @throws {RequestError} if non 200 response code was returned or body cannot be parsed
33
34
  */
34
- export async function httpGet<T>(url: string, timeout?: number, abortSignal?: AbortSignal, allowNon200: boolean = false): Promise<T> {
35
+ export async function httpGet<T>(url: string, timeout?: number, abortSignal?: AbortSignal, allowNon200: boolean = false, headers: Record<string, string> = {}): Promise<T> {
35
36
  const init = {
36
37
  method: "GET",
37
38
  timeout,
38
- signal: abortSignal
39
+ signal: abortSignal,
40
+ headers
39
41
  };
40
42
 
41
43
  const response: Response = await fetchWithTimeout(url, init);
@@ -65,14 +67,18 @@ export async function httpGet<T>(url: string, timeout?: number, abortSignal?: Ab
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
- export async function httpPost<T>(url: string, body: any, timeout?: number, abortSignal?: AbortSignal): Promise<T> {
73
+ export async function httpPost<T>(url: string, body: any, timeout?: number, abortSignal?: AbortSignal, headers: Record<string, string> = {}): Promise<T> {
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
 
@@ -46,6 +46,7 @@ logger.info("Environment supports request stream: "+supportsRequestStreams);
46
46
  * @param timeout Timeout in millseconds for the request to succeed & all its response properties to resolve
47
47
  * @param signal Abort signal
48
48
  * @param streamRequest Whether the request should be streamed or not
49
+ * @param _headers
49
50
  * @throws {RequestError} When the response code is not 200
50
51
  */
51
52
  export async function streamingFetchPromise<T extends RequestSchema>(
@@ -54,12 +55,13 @@ export async function streamingFetchPromise<T extends RequestSchema>(
54
55
  schema: T,
55
56
  timeout?: number,
56
57
  signal?: AbortSignal,
57
- streamRequest?: boolean
58
+ streamRequest?: boolean,
59
+ _headers: Record<string, string> = {}
58
60
  ): Promise<RequestSchemaResultPromise<T>> {
59
61
  if(streamRequest==null) streamRequest = supportsRequestStreams;
60
62
  if(timeout!=null) signal = timeoutSignal(timeout, new Error("Network request timed out"), signal);
61
63
 
62
- const headers: Record<string, string> = {};
64
+ const headers = {..._headers};
63
65
  const init: RequestInit = {
64
66
  method: "POST",
65
67
  headers
package/src/index.ts CHANGED
@@ -132,6 +132,7 @@ export * from "./types/SwapStateInfo";
132
132
  export * from "./types/AmountData";
133
133
  export * from "./types/CustomPriceFunction";
134
134
  export * from "./types/SwapExecutionAction";
135
+ export * from "./types/SwapExecutionStep";
135
136
  export * from "./types/SwapWithSigner";
136
137
  export * from "./types/Token";
137
138
  export * from "./types/TokenAmount";
@@ -192,8 +192,14 @@ export class IntermediaryDiscovery extends EventEmitter {
192
192
  */
193
193
  private overrideNodeUrls?: string[];
194
194
 
195
+ /**
196
+ * @private
197
+ */
198
+ private lpApi: IntermediaryAPI;
199
+
195
200
  constructor(
196
201
  swapContracts: {[chainIdentifier: string]: {[contractVersion: string]: {swapContract: SwapContract, spvVaultContract: SpvVaultContract}}},
202
+ lpApi: IntermediaryAPI,
197
203
  registryUrl: string = REGISTRY_URL,
198
204
  nodeUrls?: string[],
199
205
  httpRequestTimeout?: number,
@@ -205,6 +211,7 @@ export class IntermediaryDiscovery extends EventEmitter {
205
211
  this.overrideNodeUrls = nodeUrls;
206
212
  this.httpRequestTimeout = httpRequestTimeout;
207
213
  this.maxWaitForOthersTimeout = maxWaitForOthersTimeout;
214
+ this.lpApi = lpApi;
208
215
  }
209
216
 
210
217
  /**
@@ -242,7 +249,7 @@ export class IntermediaryDiscovery extends EventEmitter {
242
249
  info: InfoHandlerResponseEnvelope
243
250
  }> {
244
251
  const response = await tryWithRetries(
245
- () => IntermediaryAPI.getIntermediaryInfo(url, this.httpRequestTimeout, abortSignal),
252
+ () => this.lpApi.getIntermediaryInfo(url, this.httpRequestTimeout, abortSignal),
246
253
  {maxRetries: 3, delay: 100, exponential: true},
247
254
  undefined,
248
255
  abortSignal,
@@ -489,7 +496,7 @@ export class IntermediaryDiscovery extends EventEmitter {
489
496
  /**
490
497
  * Returns swap candidates for a specific swap type & token address
491
498
  *
492
- * @remark Also filters the LPs based on supported swap versions
499
+ * @remarks Also filters the LPs based on supported swap versions
493
500
  *
494
501
  * @param chainIdentifier Chain identifier of the smart chain
495
502
  * @param swapType Swap protocol type