@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.
- package/api/index.d.ts +1 -0
- package/api/index.js +3 -0
- package/dist/ApiList.d.ts +37 -0
- package/dist/ApiList.js +30 -0
- package/dist/api/ApiEndpoints.d.ts +393 -0
- package/dist/api/ApiEndpoints.js +2 -0
- package/dist/api/ApiParser.d.ts +10 -0
- package/dist/api/ApiParser.js +134 -0
- package/dist/api/ApiTypes.d.ts +157 -0
- package/dist/api/ApiTypes.js +75 -0
- package/dist/api/SerializedAction.d.ts +40 -0
- package/dist/api/SerializedAction.js +59 -0
- package/dist/api/SwapperApi.d.ts +50 -0
- package/dist/api/SwapperApi.js +431 -0
- package/dist/api/index.d.ts +5 -0
- package/dist/api/index.js +24 -0
- package/dist/bitcoin/coinselect2/accumulative.d.ts +1 -0
- package/dist/bitcoin/coinselect2/accumulative.js +1 -1
- package/dist/bitcoin/coinselect2/blackjack.d.ts +1 -0
- package/dist/bitcoin/coinselect2/blackjack.js +1 -1
- package/dist/bitcoin/coinselect2/index.d.ts +3 -2
- package/dist/bitcoin/coinselect2/index.js +2 -2
- package/dist/bitcoin/coinselect2/utils.d.ts +7 -2
- package/dist/bitcoin/coinselect2/utils.js +45 -10
- package/dist/bitcoin/wallet/BitcoinWallet.d.ts +8 -25
- package/dist/bitcoin/wallet/BitcoinWallet.js +31 -18
- package/dist/bitcoin/wallet/IBitcoinWallet.d.ts +40 -2
- package/dist/bitcoin/wallet/SingleAddressBitcoinWallet.d.ts +7 -2
- package/dist/bitcoin/wallet/SingleAddressBitcoinWallet.js +10 -4
- package/dist/events/UnifiedSwapEventListener.d.ts +4 -3
- package/dist/events/UnifiedSwapEventListener.js +8 -2
- package/dist/http/HttpUtils.d.ts +4 -2
- package/dist/http/HttpUtils.js +10 -4
- package/dist/http/paramcoders/client/StreamingFetchPromise.d.ts +2 -1
- package/dist/http/paramcoders/client/StreamingFetchPromise.js +3 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/intermediaries/IntermediaryDiscovery.d.ts +7 -2
- package/dist/intermediaries/IntermediaryDiscovery.js +4 -4
- package/dist/intermediaries/apis/IntermediaryAPI.d.ts +182 -15
- package/dist/intermediaries/apis/IntermediaryAPI.js +192 -31
- package/dist/intermediaries/auth/SignedKeyBasedAuth.d.ts +14 -0
- package/dist/intermediaries/auth/SignedKeyBasedAuth.js +68 -0
- package/dist/storage/IUnifiedStorage.d.ts +45 -3
- package/dist/storage/UnifiedSwapStorage.d.ts +8 -2
- package/dist/storage/UnifiedSwapStorage.js +46 -8
- package/dist/swapper/Swapper.d.ts +77 -4
- package/dist/swapper/Swapper.js +117 -25
- package/dist/swapper/SwapperUtils.d.ts +18 -2
- package/dist/swapper/SwapperUtils.js +39 -1
- package/dist/swaps/ISwap.d.ts +70 -9
- package/dist/swaps/ISwap.js +28 -6
- package/dist/swaps/ISwapWrapper.d.ts +11 -1
- package/dist/swaps/ISwapWrapper.js +23 -3
- package/dist/swaps/escrow_swaps/IEscrowSwap.d.ts +1 -1
- package/dist/swaps/escrow_swaps/IEscrowSwap.js +4 -2
- package/dist/swaps/escrow_swaps/IEscrowSwapWrapper.d.ts +2 -1
- package/dist/swaps/escrow_swaps/IEscrowSwapWrapper.js +2 -2
- package/dist/swaps/escrow_swaps/frombtc/IFromBTCLNWrapper.d.ts +3 -1
- package/dist/swaps/escrow_swaps/frombtc/IFromBTCLNWrapper.js +3 -2
- package/dist/swaps/escrow_swaps/frombtc/ln/FromBTCLNSwap.d.ts +47 -31
- package/dist/swaps/escrow_swaps/frombtc/ln/FromBTCLNSwap.js +201 -67
- package/dist/swaps/escrow_swaps/frombtc/ln/FromBTCLNWrapper.d.ts +3 -1
- package/dist/swaps/escrow_swaps/frombtc/ln/FromBTCLNWrapper.js +6 -6
- package/dist/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoSwap.d.ts +82 -15
- package/dist/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoSwap.js +304 -98
- package/dist/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoWrapper.d.ts +3 -1
- package/dist/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoWrapper.js +6 -6
- package/dist/swaps/escrow_swaps/frombtc/onchain/FromBTCSwap.d.ts +75 -42
- package/dist/swaps/escrow_swaps/frombtc/onchain/FromBTCSwap.js +424 -87
- package/dist/swaps/escrow_swaps/frombtc/onchain/FromBTCWrapper.d.ts +3 -1
- package/dist/swaps/escrow_swaps/frombtc/onchain/FromBTCWrapper.js +7 -7
- package/dist/swaps/escrow_swaps/tobtc/IToBTCSwap.d.ts +54 -11
- package/dist/swaps/escrow_swaps/tobtc/IToBTCSwap.js +214 -41
- package/dist/swaps/escrow_swaps/tobtc/ln/ToBTCLNWrapper.d.ts +2 -1
- package/dist/swaps/escrow_swaps/tobtc/ln/ToBTCLNWrapper.js +7 -8
- package/dist/swaps/escrow_swaps/tobtc/onchain/ToBTCWrapper.d.ts +3 -1
- package/dist/swaps/escrow_swaps/tobtc/onchain/ToBTCWrapper.js +5 -5
- package/dist/swaps/spv_swaps/SpvFromBTCSwap.d.ts +85 -22
- package/dist/swaps/spv_swaps/SpvFromBTCSwap.js +299 -56
- package/dist/swaps/spv_swaps/SpvFromBTCWrapper.d.ts +41 -7
- package/dist/swaps/spv_swaps/SpvFromBTCWrapper.js +183 -58
- package/dist/swaps/trusted/ln/LnForGasSwap.d.ts +53 -12
- package/dist/swaps/trusted/ln/LnForGasSwap.js +163 -49
- package/dist/swaps/trusted/ln/LnForGasWrapper.js +1 -2
- package/dist/swaps/trusted/onchain/OnchainForGasSwap.d.ts +14 -13
- package/dist/swaps/trusted/onchain/OnchainForGasSwap.js +30 -47
- package/dist/swaps/trusted/onchain/OnchainForGasWrapper.d.ts +3 -1
- package/dist/swaps/trusted/onchain/OnchainForGasWrapper.js +4 -4
- package/dist/types/SwapExecutionAction.d.ts +141 -34
- package/dist/types/SwapExecutionAction.js +104 -0
- package/dist/types/SwapExecutionStep.d.ts +144 -0
- package/dist/types/SwapExecutionStep.js +87 -0
- package/dist/types/TokenAmount.d.ts +6 -0
- package/dist/types/TokenAmount.js +26 -1
- package/dist/utils/BitcoinUtils.d.ts +4 -0
- package/dist/utils/BitcoinUtils.js +73 -1
- package/dist/utils/BitcoinWalletUtils.d.ts +2 -2
- package/dist/utils/Utils.d.ts +3 -1
- package/dist/utils/Utils.js +7 -1
- package/package.json +7 -4
- package/src/api/ApiEndpoints.ts +427 -0
- package/src/api/ApiParser.ts +138 -0
- package/src/api/ApiTypes.ts +229 -0
- package/src/api/SerializedAction.ts +97 -0
- package/src/api/SwapperApi.ts +545 -0
- package/src/api/index.ts +5 -0
- package/src/bitcoin/coinselect2/accumulative.ts +2 -1
- package/src/bitcoin/coinselect2/blackjack.ts +2 -1
- package/src/bitcoin/coinselect2/index.ts +5 -4
- package/src/bitcoin/coinselect2/utils.ts +55 -14
- package/src/bitcoin/wallet/BitcoinWallet.ts +69 -57
- package/src/bitcoin/wallet/IBitcoinWallet.ts +44 -3
- package/src/bitcoin/wallet/SingleAddressBitcoinWallet.ts +12 -4
- package/src/events/UnifiedSwapEventListener.ts +11 -3
- package/src/http/HttpUtils.ts +10 -4
- package/src/http/paramcoders/client/StreamingFetchPromise.ts +4 -2
- package/src/index.ts +1 -0
- package/src/intermediaries/IntermediaryDiscovery.ts +9 -2
- package/src/intermediaries/apis/IntermediaryAPI.ts +335 -35
- package/src/intermediaries/auth/SignedKeyBasedAuth.ts +69 -0
- package/src/storage/IUnifiedStorage.ts +45 -4
- package/src/storage/UnifiedSwapStorage.ts +42 -8
- package/src/swapper/Swapper.ts +165 -24
- package/src/swapper/SwapperUtils.ts +42 -2
- package/src/swaps/ISwap.ts +88 -16
- package/src/swaps/ISwapWrapper.ts +28 -3
- package/src/swaps/escrow_swaps/IEscrowSwap.ts +5 -3
- package/src/swaps/escrow_swaps/IEscrowSwapWrapper.ts +3 -1
- package/src/swaps/escrow_swaps/frombtc/IFromBTCLNWrapper.ts +4 -1
- package/src/swaps/escrow_swaps/frombtc/ln/FromBTCLNSwap.ts +264 -67
- package/src/swaps/escrow_swaps/frombtc/ln/FromBTCLNWrapper.ts +6 -4
- package/src/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoSwap.ts +390 -89
- package/src/swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoWrapper.ts +6 -4
- package/src/swaps/escrow_swaps/frombtc/onchain/FromBTCSwap.ts +548 -94
- package/src/swaps/escrow_swaps/frombtc/onchain/FromBTCWrapper.ts +7 -5
- package/src/swaps/escrow_swaps/tobtc/IToBTCSwap.ts +276 -45
- package/src/swaps/escrow_swaps/tobtc/ln/ToBTCLNWrapper.ts +7 -6
- package/src/swaps/escrow_swaps/tobtc/onchain/ToBTCWrapper.ts +5 -3
- package/src/swaps/spv_swaps/SpvFromBTCSwap.ts +413 -64
- package/src/swaps/spv_swaps/SpvFromBTCWrapper.ts +239 -61
- package/src/swaps/trusted/ln/LnForGasSwap.ts +211 -47
- package/src/swaps/trusted/ln/LnForGasWrapper.ts +1 -2
- package/src/swaps/trusted/onchain/OnchainForGasSwap.ts +32 -51
- package/src/swaps/trusted/onchain/OnchainForGasWrapper.ts +5 -3
- package/src/types/SwapExecutionAction.ts +266 -43
- package/src/types/SwapExecutionStep.ts +224 -0
- package/src/types/TokenAmount.ts +36 -2
- package/src/utils/BitcoinUtils.ts +73 -0
- package/src/utils/BitcoinWalletUtils.ts +2 -2
- package/src/utils/Utils.ts +10 -1
- 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:
|
|
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?:
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
391
|
-
|
|
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
|
-
|
|
140
|
-
|
|
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);
|
package/src/http/HttpUtils.ts
CHANGED
|
@@ -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: {
|
|
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
|
|
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
|
-
() =>
|
|
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
|
-
* @
|
|
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
|