@atomiqlabs/sdk 8.7.7 → 8.8.3
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/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/intermediaries/apis/IntermediaryAPI.d.ts +11 -1
- package/dist/intermediaries/apis/IntermediaryAPI.js +18 -3
- package/dist/swapper/Swapper.d.ts +41 -1
- package/dist/swapper/Swapper.js +63 -7
- package/dist/swaps/spv_swaps/SpvFromBTCSwap.d.ts +9 -3
- package/dist/swaps/spv_swaps/SpvFromBTCSwap.js +9 -5
- package/dist/swaps/spv_swaps/SpvFromBTCWrapper.d.ts +38 -6
- package/dist/swaps/spv_swaps/SpvFromBTCWrapper.js +178 -53
- package/dist/utils/BitcoinUtils.d.ts +2 -0
- package/dist/utils/BitcoinUtils.js +40 -1
- package/dist/utils/BitcoinWalletUtils.d.ts +2 -2
- package/package.json +1 -1
- 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/intermediaries/apis/IntermediaryAPI.ts +21 -5
- package/src/swapper/Swapper.ts +79 -7
- package/src/swaps/spv_swaps/SpvFromBTCSwap.ts +20 -7
- package/src/swaps/spv_swaps/SpvFromBTCWrapper.ts +234 -58
- package/src/utils/BitcoinUtils.ts +41 -0
- package/src/utils/BitcoinWalletUtils.ts +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SpvFromBTCWrapper = void 0;
|
|
3
|
+
exports.SpvFromBTCWrapper = exports.REQUIRED_SPV_SWAP_LP_ADDRESS_TYPE = exports.REQUIRED_SPV_SWAP_VAULT_ADDRESS_TYPE = void 0;
|
|
4
4
|
const ISwapWrapper_1 = require("../ISwapWrapper");
|
|
5
5
|
const base_1 = require("@atomiqlabs/base");
|
|
6
6
|
const SpvFromBTCSwap_1 = require("./SpvFromBTCSwap");
|
|
@@ -15,6 +15,10 @@ const IntermediaryError_1 = require("../../errors/IntermediaryError");
|
|
|
15
15
|
const btc_signer_1 = require("@scure/btc-signer");
|
|
16
16
|
const RetryUtils_1 = require("../../utils/RetryUtils");
|
|
17
17
|
const UserError_1 = require("../../errors/UserError");
|
|
18
|
+
const utils_2 = require("../../bitcoin/coinselect2/utils");
|
|
19
|
+
const BitcoinWallet_1 = require("../../bitcoin/wallet/BitcoinWallet");
|
|
20
|
+
exports.REQUIRED_SPV_SWAP_VAULT_ADDRESS_TYPE = "p2tr";
|
|
21
|
+
exports.REQUIRED_SPV_SWAP_LP_ADDRESS_TYPE = "p2wpkh";
|
|
18
22
|
/**
|
|
19
23
|
* New spv vault (UTXO-controlled vault) based swaps for Bitcoin -> Smart chain swaps not requiring
|
|
20
24
|
* any initiation on the destination chain, and with the added possibility for the user to receive
|
|
@@ -209,26 +213,21 @@ class SpvFromBTCWrapper extends ISwapWrapper_1.ISwapWrapper {
|
|
|
209
213
|
*
|
|
210
214
|
* @param amountData
|
|
211
215
|
* @param options Options as passed to the swap creation function
|
|
212
|
-
* @param pricePrefetch
|
|
213
|
-
* @param nativeTokenPricePrefetch
|
|
214
216
|
* @param abortController
|
|
215
217
|
* @param contractVersion
|
|
216
218
|
* @private
|
|
217
219
|
*/
|
|
218
|
-
async
|
|
220
|
+
async preFetchCallerFeeInNativeToken(amountData, options, abortController, contractVersion) {
|
|
219
221
|
if (options.unsafeZeroWatchtowerFee)
|
|
220
222
|
return 0n;
|
|
221
223
|
if (amountData.amount === 0n)
|
|
222
224
|
return 0n;
|
|
223
225
|
try {
|
|
224
|
-
const [feePerBlock, btcRelayData, currentBtcBlock, claimFeeRate
|
|
226
|
+
const [feePerBlock, btcRelayData, currentBtcBlock, claimFeeRate] = await Promise.all([
|
|
225
227
|
this.btcRelay(contractVersion).getFeePerBlock(),
|
|
226
228
|
this.btcRelay(contractVersion).getTipData(),
|
|
227
229
|
this._btcRpc.getTipHeight(),
|
|
228
|
-
this._contract(contractVersion).getClaimFee(this._chain.randomAddress())
|
|
229
|
-
nativeTokenPricePrefetch ?? (amountData.token === this._chain.getNativeCurrencyAddress() ?
|
|
230
|
-
pricePrefetch :
|
|
231
|
-
this._prices.preFetchPrice(this.chainIdentifier, this._chain.getNativeCurrencyAddress(), abortController.signal))
|
|
230
|
+
this._contract(contractVersion).getClaimFee(this._chain.randomAddress())
|
|
232
231
|
]);
|
|
233
232
|
if (btcRelayData == null)
|
|
234
233
|
throw new Error("Btc relay doesn't seem to be initialized!");
|
|
@@ -236,35 +235,58 @@ class SpvFromBTCWrapper extends ISwapWrapper_1.ISwapWrapper {
|
|
|
236
235
|
const blockDelta = Math.max(currentBtcBlock - currentBtcRelayBlock + this._options.maxConfirmations, 0);
|
|
237
236
|
const totalFeeInNativeToken = ((BigInt(blockDelta) * feePerBlock) +
|
|
238
237
|
(claimFeeRate * BigInt(this._options.maxTransactionsDelta))) * BigInt(Math.floor(options.feeSafetyFactor * 1000000)) / 1000000n;
|
|
239
|
-
|
|
240
|
-
if (amountData.exactIn) {
|
|
241
|
-
//Convert input amount in BTC to
|
|
242
|
-
const amountInNativeToken = await this._prices.getFromBtcSwapAmount(this.chainIdentifier, amountData.amount, this._chain.getNativeCurrencyAddress(), abortController.signal, nativeTokenPrice);
|
|
243
|
-
payoutAmount = amountInNativeToken - totalFeeInNativeToken;
|
|
244
|
-
}
|
|
245
|
-
else {
|
|
246
|
-
if (amountData.token === this._chain.getNativeCurrencyAddress()) {
|
|
247
|
-
//Both amounts in same currency
|
|
248
|
-
payoutAmount = amountData.amount;
|
|
249
|
-
}
|
|
250
|
-
else {
|
|
251
|
-
//Need to convert both to native currency
|
|
252
|
-
const btcAmount = await this._prices.getToBtcSwapAmount(this.chainIdentifier, amountData.amount, amountData.token, abortController.signal, await pricePrefetch);
|
|
253
|
-
payoutAmount = await this._prices.getFromBtcSwapAmount(this.chainIdentifier, btcAmount, this._chain.getNativeCurrencyAddress(), abortController.signal, nativeTokenPrice);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
this.logger.debug("preFetchCallerFeeShare(): Caller fee in native token: " + totalFeeInNativeToken.toString(10) + " total payout in native token: " + payoutAmount.toString(10));
|
|
257
|
-
const callerFeeShare = ((totalFeeInNativeToken * 100000n) + payoutAmount - 1n) / payoutAmount; //Make sure to round up here
|
|
258
|
-
if (callerFeeShare < 0n)
|
|
259
|
-
return 0n;
|
|
260
|
-
if (callerFeeShare >= 2n ** 20n)
|
|
261
|
-
return 2n ** 20n - 1n;
|
|
262
|
-
return callerFeeShare;
|
|
238
|
+
return totalFeeInNativeToken;
|
|
263
239
|
}
|
|
264
240
|
catch (e) {
|
|
265
241
|
abortController.abort(e);
|
|
266
242
|
}
|
|
267
243
|
}
|
|
244
|
+
/**
|
|
245
|
+
* Pre-fetches caller (watchtower) bounty data for the swap. Doesn't throw, instead returns null and aborts the
|
|
246
|
+
* provided abortController
|
|
247
|
+
*
|
|
248
|
+
* @param amountPrefetch
|
|
249
|
+
* @param totalFeeInNativeTokenPrefetch
|
|
250
|
+
* @param amountData
|
|
251
|
+
* @param options Options as passed to the swap creation function
|
|
252
|
+
* @param pricePrefetch
|
|
253
|
+
* @param nativeTokenPricePrefetch
|
|
254
|
+
* @param abortSignal
|
|
255
|
+
* @private
|
|
256
|
+
*/
|
|
257
|
+
async computeCallerFeeShare(amountPrefetch, totalFeeInNativeTokenPrefetch, amountData, options, pricePrefetch, nativeTokenPricePrefetch, abortSignal) {
|
|
258
|
+
if (options.unsafeZeroWatchtowerFee)
|
|
259
|
+
return 0n;
|
|
260
|
+
const amount = await (0, Utils_1.throwIfUndefined)(amountPrefetch, "Cannot get swap amount!");
|
|
261
|
+
if (amount === 0n)
|
|
262
|
+
return 0n;
|
|
263
|
+
const totalFeeInNativeToken = await (0, Utils_1.throwIfUndefined)(totalFeeInNativeTokenPrefetch, "Cannot get total fee in native token!");
|
|
264
|
+
const nativeTokenPrice = await nativeTokenPricePrefetch;
|
|
265
|
+
let payoutAmount;
|
|
266
|
+
if (amountData.exactIn) {
|
|
267
|
+
//Convert input amount in BTC to
|
|
268
|
+
const amountInNativeToken = await this._prices.getFromBtcSwapAmount(this.chainIdentifier, amount, this._chain.getNativeCurrencyAddress(), abortSignal, nativeTokenPrice);
|
|
269
|
+
payoutAmount = amountInNativeToken - totalFeeInNativeToken;
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
if (amountData.token === this._chain.getNativeCurrencyAddress()) {
|
|
273
|
+
//Both amounts in same currency
|
|
274
|
+
payoutAmount = amount;
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
//Need to convert both to native currency
|
|
278
|
+
const btcAmount = await this._prices.getToBtcSwapAmount(this.chainIdentifier, amount, amountData.token, abortSignal, await pricePrefetch);
|
|
279
|
+
payoutAmount = await this._prices.getFromBtcSwapAmount(this.chainIdentifier, btcAmount, this._chain.getNativeCurrencyAddress(), abortSignal, nativeTokenPrice);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
this.logger.debug("computeCallerFeeShare(): Caller fee in native token: " + totalFeeInNativeToken.toString(10) + " total payout in native token: " + payoutAmount.toString(10));
|
|
283
|
+
const callerFeeShare = ((totalFeeInNativeToken * 100000n) + payoutAmount - 1n) / payoutAmount; //Make sure to round up here
|
|
284
|
+
if (callerFeeShare < 0n)
|
|
285
|
+
return 0n;
|
|
286
|
+
if (callerFeeShare >= 2n ** 20n)
|
|
287
|
+
return 2n ** 20n - 1n;
|
|
288
|
+
return callerFeeShare;
|
|
289
|
+
}
|
|
268
290
|
/**
|
|
269
291
|
* Verifies response returned from intermediary
|
|
270
292
|
*
|
|
@@ -273,13 +295,14 @@ class SpvFromBTCWrapper extends ISwapWrapper_1.ISwapWrapper {
|
|
|
273
295
|
* @param lp Intermediary
|
|
274
296
|
* @param options Options as passed to the swap creation function
|
|
275
297
|
* @param callerFeeShare
|
|
276
|
-
* @param
|
|
298
|
+
* @param maxBitcoinFeeRatePromise Maximum accepted fee rate from the LPs
|
|
299
|
+
* @param bitcoinFeeRatePromise
|
|
277
300
|
* @param abortSignal
|
|
278
301
|
* @private
|
|
279
302
|
* @throws {IntermediaryError} in case the response is invalid
|
|
280
303
|
*/
|
|
281
|
-
async verifyReturnedData(resp, amountData, lp, options, callerFeeShare, bitcoinFeeRatePromise, abortSignal) {
|
|
282
|
-
const btcFeeRate = await (0, Utils_1.throwIfUndefined)(
|
|
304
|
+
async verifyReturnedData(resp, amountData, lp, options, callerFeeShare, maxBitcoinFeeRatePromise, bitcoinFeeRatePromise, abortSignal) {
|
|
305
|
+
const btcFeeRate = await (0, Utils_1.throwIfUndefined)(maxBitcoinFeeRatePromise, "Bitcoin fee rate promise failed!");
|
|
283
306
|
abortSignal.throwIfAborted();
|
|
284
307
|
if (btcFeeRate != null && resp.btcFeeRate > btcFeeRate)
|
|
285
308
|
throw new IntermediaryError_1.IntermediaryError(`Required bitcoin fee rate returned from the LP is too high! Maximum accepted: ${btcFeeRate} sats/vB, required by LP: ${resp.btcFeeRate} sats/vB`);
|
|
@@ -288,11 +311,13 @@ class SpvFromBTCWrapper extends ISwapWrapper_1.ISwapWrapper {
|
|
|
288
311
|
let vaultScript;
|
|
289
312
|
let vaultAddressType;
|
|
290
313
|
let btcAddressScript;
|
|
314
|
+
let btcAddressType;
|
|
291
315
|
//Ensure valid btc addresses returned
|
|
292
316
|
try {
|
|
293
317
|
vaultScript = (0, BitcoinUtils_1.toOutputScript)(this._options.bitcoinNetwork, resp.vaultBtcAddress);
|
|
294
318
|
vaultAddressType = (0, BitcoinUtils_1.toCoinselectAddressType)(vaultScript);
|
|
295
319
|
btcAddressScript = (0, BitcoinUtils_1.toOutputScript)(this._options.bitcoinNetwork, resp.btcAddress);
|
|
320
|
+
btcAddressType = (0, BitcoinUtils_1.toCoinselectAddressType)(btcAddressScript);
|
|
296
321
|
}
|
|
297
322
|
catch (e) {
|
|
298
323
|
throw new IntermediaryError_1.IntermediaryError("Invalid btc address data returned", e);
|
|
@@ -302,7 +327,8 @@ class SpvFromBTCWrapper extends ISwapWrapper_1.ISwapWrapper {
|
|
|
302
327
|
resp.vaultId < 0n || //Ensure vaultId is not negative
|
|
303
328
|
vaultScript == null || //Make sure vault script is parsable and of known type
|
|
304
329
|
btcAddressScript == null || //Make sure btc address script is parsable and of known type
|
|
305
|
-
|
|
330
|
+
btcAddressType !== exports.REQUIRED_SPV_SWAP_LP_ADDRESS_TYPE || //Constrain the btc address script type
|
|
331
|
+
vaultAddressType !== exports.REQUIRED_SPV_SWAP_VAULT_ADDRESS_TYPE || //Constrain the vault script type
|
|
306
332
|
decodedUtxo.length !== 2 || decodedUtxo[0].length !== 64 || isNaN(parseInt(decodedUtxo[1])) || //Check valid UTXO
|
|
307
333
|
resp.btcFeeRate < 1 || resp.btcFeeRate > 10000 //Sanity check on the returned BTC fee rate
|
|
308
334
|
)
|
|
@@ -345,8 +371,24 @@ class SpvFromBTCWrapper extends ISwapWrapper_1.ISwapWrapper {
|
|
|
345
371
|
const tokenData = vault.getTokenData();
|
|
346
372
|
//Amounts - make sure the amounts match
|
|
347
373
|
if (amountData.exactIn) {
|
|
348
|
-
if (resp.
|
|
349
|
-
|
|
374
|
+
if (!resp.usedUtxoInputCalculation) {
|
|
375
|
+
//Legacy calculation
|
|
376
|
+
if (resp.btcAmount !== amountData.amount)
|
|
377
|
+
throw new IntermediaryError_1.IntermediaryError("Invalid amount returned");
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
//Implies the raw UTXOs were passed for amount derivation
|
|
381
|
+
//Verify the derivation was done correctly
|
|
382
|
+
if (options.sourceWalletUtxos == null)
|
|
383
|
+
throw new IntermediaryError_1.IntermediaryError("Invalid usedUtxoInputCalcuation return value");
|
|
384
|
+
if (bitcoinFeeRatePromise == null)
|
|
385
|
+
throw new Error("bitcoinFeeRatePromise must be passed for UTXO-based input amount calculation checks");
|
|
386
|
+
const walletUtxos = await options.sourceWalletUtxos;
|
|
387
|
+
const bitcoinFeeRate = await (0, Utils_1.throwIfUndefined)(bitcoinFeeRatePromise, "Failed to fetch bitcoin fee rate!");
|
|
388
|
+
const { balance } = BitcoinWallet_1.BitcoinWallet.getSpendableBalance(walletUtxos, Math.max(resp.btcFeeRate, bitcoinFeeRate), this.getDummySwapPsbt(options.gasAmount !== 0n), exports.REQUIRED_SPV_SWAP_LP_ADDRESS_TYPE);
|
|
389
|
+
if (resp.btcAmount !== balance)
|
|
390
|
+
throw new IntermediaryError_1.IntermediaryError(`Invalid amount returned, expected: ${balance.toString(10)}, got: ${resp.btcAmount.toString(10)}`);
|
|
391
|
+
}
|
|
350
392
|
}
|
|
351
393
|
else {
|
|
352
394
|
//Check the difference between amount adjusted due to scaling to raw amount
|
|
@@ -447,6 +489,58 @@ class SpvFromBTCWrapper extends ISwapWrapper_1.ISwapWrapper {
|
|
|
447
489
|
vaultUtxoValue
|
|
448
490
|
};
|
|
449
491
|
}
|
|
492
|
+
async amountPrefetch(amountData, bitcoinFeeRatePromise, walletUtxosPromise, includeGas, abortController) {
|
|
493
|
+
if (amountData.amount != null)
|
|
494
|
+
return amountData.amount;
|
|
495
|
+
try {
|
|
496
|
+
const bitcoinFeeRate = await (0, Utils_1.throwIfUndefined)(bitcoinFeeRatePromise, "Cannot fetch Bitcoin fee rate!");
|
|
497
|
+
if (walletUtxosPromise == null)
|
|
498
|
+
throw new UserError_1.UserError("Cannot use empty amount without passing UTXOs!");
|
|
499
|
+
const walletUtxos = await walletUtxosPromise;
|
|
500
|
+
if (walletUtxos.length === 0)
|
|
501
|
+
throw new UserError_1.UserError("Wallet doesn't have any BTC balance");
|
|
502
|
+
const spendableBalance = await BitcoinWallet_1.BitcoinWallet.getSpendableBalance(walletUtxos, bitcoinFeeRate, this.getDummySwapPsbt(includeGas), exports.REQUIRED_SPV_SWAP_LP_ADDRESS_TYPE);
|
|
503
|
+
return spendableBalance.balance;
|
|
504
|
+
}
|
|
505
|
+
catch (e) {
|
|
506
|
+
abortController.abort(e);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
bitcoinFeeRatePrefetch(options, abortController) {
|
|
510
|
+
let bitcoinFeeRatePromise;
|
|
511
|
+
if (options?.sourceWalletUtxos != null) {
|
|
512
|
+
if (options.bitcoinFeeRate != null) {
|
|
513
|
+
bitcoinFeeRatePromise = options.bitcoinFeeRate.then(value => {
|
|
514
|
+
if (options.maxAllowedBitcoinFeeRate != Infinity && options.maxAllowedBitcoinFeeRate < value)
|
|
515
|
+
throw new Error("Passed `maxAllowedBitcoinFeeRate` cannot be lower than `bitcoinFeeRate`");
|
|
516
|
+
return value;
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
else {
|
|
520
|
+
bitcoinFeeRatePromise = this._btcRpc.getFeeRate().then(value => {
|
|
521
|
+
if (options.maxAllowedBitcoinFeeRate != Infinity && value > options.maxAllowedBitcoinFeeRate)
|
|
522
|
+
return options.maxAllowedBitcoinFeeRate;
|
|
523
|
+
return value;
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
bitcoinFeeRatePromise = bitcoinFeeRatePromise.catch(e => {
|
|
527
|
+
abortController.abort(e);
|
|
528
|
+
return undefined;
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
const maxBitcoinFeeRatePromise = options.maxAllowedBitcoinFeeRate != Infinity
|
|
532
|
+
? Promise.resolve(options.maxAllowedBitcoinFeeRate)
|
|
533
|
+
: (0, Utils_1.throwIfUndefined)(bitcoinFeeRatePromise ?? options.bitcoinFeeRate ?? this._btcRpc.getFeeRate())
|
|
534
|
+
.then(x => this._options.maxBtcFeeOffset + (x * this._options.maxBtcFeeMultiplier))
|
|
535
|
+
.catch(e => {
|
|
536
|
+
abortController.abort(e);
|
|
537
|
+
return undefined;
|
|
538
|
+
});
|
|
539
|
+
return {
|
|
540
|
+
bitcoinFeeRatePromise,
|
|
541
|
+
maxBitcoinFeeRatePromise
|
|
542
|
+
};
|
|
543
|
+
}
|
|
450
544
|
/**
|
|
451
545
|
* Returns a newly created Bitcoin -> Smart chain swap using the SPV vault (UTXO-controlled vault) swap protocol,
|
|
452
546
|
* with the passed amount. Also allows specifying additional "gas drop" native token that the receipient receives
|
|
@@ -464,13 +558,25 @@ class SpvFromBTCWrapper extends ISwapWrapper_1.ISwapWrapper {
|
|
|
464
558
|
gasAmount: this.parseGasAmount(options?.gasAmount),
|
|
465
559
|
unsafeZeroWatchtowerFee: options?.unsafeZeroWatchtowerFee ?? false,
|
|
466
560
|
feeSafetyFactor: options?.feeSafetyFactor ?? 1.25,
|
|
467
|
-
maxAllowedBitcoinFeeRate: options?.maxAllowedBitcoinFeeRate ?? options?.maxAllowedNetworkFeeRate ?? Infinity
|
|
561
|
+
maxAllowedBitcoinFeeRate: options?.maxAllowedBitcoinFeeRate ?? options?.maxAllowedNetworkFeeRate ?? Infinity,
|
|
562
|
+
sourceWalletUtxos: options?.sourceWalletUtxos == undefined
|
|
563
|
+
? undefined
|
|
564
|
+
: options?.sourceWalletUtxos instanceof Promise ? options.sourceWalletUtxos : Promise.resolve(options.sourceWalletUtxos),
|
|
565
|
+
bitcoinFeeRate: options?.bitcoinFeeRate == undefined
|
|
566
|
+
? undefined
|
|
567
|
+
: options?.bitcoinFeeRate instanceof Promise ? options.bitcoinFeeRate : Promise.resolve(options.bitcoinFeeRate),
|
|
468
568
|
};
|
|
469
569
|
if (_options.gasAmount !== 0n &&
|
|
470
570
|
(this._chain.shouldGetNativeTokenDrop != null
|
|
471
571
|
? !this._chain.shouldGetNativeTokenDrop(amountData.token)
|
|
472
572
|
: amountData.token === this._chain.getNativeCurrencyAddress()))
|
|
473
573
|
throw new UserError_1.UserError("Cannot specify `gasAmount` for swaps to a native token!");
|
|
574
|
+
if (amountData.amount == null && options?.sourceWalletUtxos == null)
|
|
575
|
+
throw new UserError_1.UserError("Source wallet UTXOs need to be passed when amount is null!");
|
|
576
|
+
if (amountData.amount == null && !amountData.exactIn)
|
|
577
|
+
throw new UserError_1.UserError("Amount can be null only for exactIn swaps!");
|
|
578
|
+
if (amountData.amount != null && options?.sourceWalletUtxos != null)
|
|
579
|
+
throw new UserError_1.UserError("Source wallet UTXOs cannot be passed while specifying an input amount!");
|
|
474
580
|
const lpVersions = Intermediary_1.Intermediary.getContractVersionsForLps(this.chainIdentifier, lps);
|
|
475
581
|
const _abortController = (0, Utils_1.extendAbortController)(abortSignal);
|
|
476
582
|
const pricePrefetchPromise = this.preFetchPrice(amountData, _abortController.signal);
|
|
@@ -481,14 +587,10 @@ class SpvFromBTCWrapper extends ISwapWrapper_1.ISwapWrapper {
|
|
|
481
587
|
undefined :
|
|
482
588
|
this.preFetchPrice({ token: nativeTokenAddress }, _abortController.signal);
|
|
483
589
|
const callerFeePrefetchPromise = (0, Utils_1.mapArrayToObject)(lpVersions, (contractVersion) => {
|
|
484
|
-
return this.
|
|
590
|
+
return this.preFetchCallerFeeInNativeToken(amountData, _options, _abortController, contractVersion);
|
|
485
591
|
});
|
|
486
|
-
const bitcoinFeeRatePromise = _options
|
|
487
|
-
|
|
488
|
-
this._btcRpc.getFeeRate().then(x => this._options.maxBtcFeeOffset + (x * this._options.maxBtcFeeMultiplier)).catch(e => {
|
|
489
|
-
_abortController.abort(e);
|
|
490
|
-
return undefined;
|
|
491
|
-
});
|
|
592
|
+
const { maxBitcoinFeeRatePromise, bitcoinFeeRatePromise } = this.bitcoinFeeRatePrefetch(_options, _abortController);
|
|
593
|
+
const amountPromise = this.amountPrefetch(amountData, maxBitcoinFeeRatePromise, _options.sourceWalletUtxos, _options.gasAmount !== 0n, _abortController);
|
|
492
594
|
return lps.map(lp => {
|
|
493
595
|
return {
|
|
494
596
|
intermediary: lp,
|
|
@@ -497,29 +599,46 @@ class SpvFromBTCWrapper extends ISwapWrapper_1.ISwapWrapper {
|
|
|
497
599
|
throw new Error("LP service for processing spv vault swaps not found!");
|
|
498
600
|
const version = lp.getContractVersion(this.chainIdentifier);
|
|
499
601
|
const abortController = (0, Utils_1.extendAbortController)(_abortController.signal);
|
|
602
|
+
const callerFeeRatePromise = this.computeCallerFeeShare(amountPromise, callerFeePrefetchPromise[version], amountData, _options, pricePrefetchPromise, gasTokenPricePrefetchPromise, abortController.signal);
|
|
500
603
|
try {
|
|
501
604
|
const resp = await (0, RetryUtils_1.tryWithRetries)(async (retryCount) => {
|
|
502
605
|
return await IntermediaryAPI_1.IntermediaryAPI.prepareSpvFromBTC(this.chainIdentifier, lp.url, {
|
|
503
606
|
address: recipient,
|
|
504
|
-
amount:
|
|
607
|
+
amount: (0, Utils_1.throwIfUndefined)(amountPromise, "Failed to compute swap amount"),
|
|
505
608
|
token: amountData.token.toString(),
|
|
506
609
|
exactOut: !amountData.exactIn,
|
|
507
610
|
gasToken: nativeTokenAddress,
|
|
508
611
|
gasAmount: _options.gasAmount,
|
|
509
|
-
callerFeeRate: (0, Utils_1.throwIfUndefined)(
|
|
612
|
+
callerFeeRate: (0, Utils_1.throwIfUndefined)(callerFeeRatePromise, "Caller fee prefetch failed!"),
|
|
510
613
|
frontingFeeRate: 0n,
|
|
511
614
|
stickyAddress: options?.stickyAddress,
|
|
615
|
+
amountUtxos: _options.sourceWalletUtxos != null
|
|
616
|
+
? _options.sourceWalletUtxos.then(utxos => {
|
|
617
|
+
if (utxos.length === 0)
|
|
618
|
+
return undefined;
|
|
619
|
+
return utxos.map(utxo => ({
|
|
620
|
+
value: utxo.value,
|
|
621
|
+
vSize: utils_2.utils.inputBytes({ type: utxo.type }),
|
|
622
|
+
cpfp: utxo.cpfp == null ? undefined : { effectiveVSize: utxo.cpfp?.txVsize, effectiveFeeRate: utxo.cpfp?.txEffectiveFeeRate }
|
|
623
|
+
}));
|
|
624
|
+
})
|
|
625
|
+
: undefined,
|
|
626
|
+
amountFeeRate: bitcoinFeeRatePromise,
|
|
512
627
|
additionalParams
|
|
513
628
|
}, this._options.postRequestTimeout, abortController.signal, retryCount > 0 ? false : undefined);
|
|
514
629
|
}, undefined, e => e instanceof RequestError_1.RequestError, abortController.signal);
|
|
515
630
|
this.logger.debug("create(" + lp.url + "): LP response: ", resp);
|
|
516
|
-
const callerFeeShare =
|
|
631
|
+
const callerFeeShare = await callerFeeRatePromise;
|
|
632
|
+
const amount = await (0, Utils_1.throwIfUndefined)(amountPromise);
|
|
517
633
|
const [pricingInfo, gasPricingInfo, { vault, vaultUtxoValue }] = await Promise.all([
|
|
518
634
|
this.verifyReturnedPrice(lp.services[SwapType_1.SwapType.SPV_VAULT_FROM_BTC], false, resp.btcAmountSwap, resp.total * (100000n + callerFeeShare) / 100000n, amountData.token, { swapFeeBtc: resp.swapFeeBtc }, pricePrefetchPromise, usdPricePrefetchPromise, abortController.signal),
|
|
519
635
|
_options.gasAmount === 0n ? Promise.resolve(undefined) : this.verifyReturnedPrice({ ...lp.services[SwapType_1.SwapType.SPV_VAULT_FROM_BTC], swapBaseFee: 0 }, //Base fee should be charged only on the amount, not on gas
|
|
520
636
|
false, resp.btcAmountGas, resp.totalGas * (100000n + callerFeeShare) / 100000n, nativeTokenAddress, { swapFeeBtc: resp.gasSwapFeeBtc }, gasTokenPricePrefetchPromise, usdPricePrefetchPromise, abortController.signal),
|
|
521
|
-
this.verifyReturnedData(resp, amountData, lp, _options, callerFeeShare, bitcoinFeeRatePromise, abortController.signal)
|
|
637
|
+
this.verifyReturnedData(resp, { ...amountData, amount }, lp, _options, callerFeeShare, maxBitcoinFeeRatePromise, bitcoinFeeRatePromise, abortController.signal)
|
|
522
638
|
]);
|
|
639
|
+
let minimumBtcFeeRate = resp.btcFeeRate;
|
|
640
|
+
if (bitcoinFeeRatePromise != null)
|
|
641
|
+
minimumBtcFeeRate = Math.max(minimumBtcFeeRate, await (0, Utils_1.throwIfUndefined)(bitcoinFeeRatePromise));
|
|
523
642
|
const swapInit = {
|
|
524
643
|
pricingInfo,
|
|
525
644
|
url: lp.url,
|
|
@@ -540,7 +659,7 @@ class SpvFromBTCWrapper extends ISwapWrapper_1.ISwapWrapper {
|
|
|
540
659
|
btcAmount: resp.btcAmount,
|
|
541
660
|
btcAmountSwap: resp.btcAmountSwap,
|
|
542
661
|
btcAmountGas: resp.btcAmountGas,
|
|
543
|
-
minimumBtcFeeRate
|
|
662
|
+
minimumBtcFeeRate,
|
|
544
663
|
outputTotalSwap: resp.total,
|
|
545
664
|
outputSwapToken: amountData.token,
|
|
546
665
|
outputTotalGas: resp.totalGas,
|
|
@@ -558,6 +677,12 @@ class SpvFromBTCWrapper extends ISwapWrapper_1.ISwapWrapper {
|
|
|
558
677
|
return quote;
|
|
559
678
|
}
|
|
560
679
|
catch (e) {
|
|
680
|
+
if (e instanceof RequestError_1.OutOfBoundsError) {
|
|
681
|
+
const amountResult = await amountPromise.catch(() => undefined);
|
|
682
|
+
if (_options.sourceWalletUtxos != null && amountResult != null && amountResult <= 0n) {
|
|
683
|
+
e = new UserError_1.UserError("Wallet doesn't have enough BTC balance to cover transaction fees");
|
|
684
|
+
}
|
|
685
|
+
}
|
|
561
686
|
abortController.abort(e);
|
|
562
687
|
throw e;
|
|
563
688
|
}
|
|
@@ -682,7 +807,7 @@ class SpvFromBTCWrapper extends ISwapWrapper_1.ISwapWrapper {
|
|
|
682
807
|
allowLegacyWitnessUtxo: true,
|
|
683
808
|
allowUnknownOutputs: true
|
|
684
809
|
});
|
|
685
|
-
const randomVaultOutScript =
|
|
810
|
+
const randomVaultOutScript = (0, BitcoinUtils_1.getDummyOutputScript)(exports.REQUIRED_SPV_SWAP_VAULT_ADDRESS_TYPE);
|
|
686
811
|
psbt.addInput({
|
|
687
812
|
txid: (0, Utils_1.randomBytes)(32),
|
|
688
813
|
index: 0,
|
|
@@ -7,6 +7,8 @@ import { CoinselectAddressTypes } from "../bitcoin/coinselect2";
|
|
|
7
7
|
export declare function fromOutputScript(network: BTC_NETWORK, outputScriptHex: string): string;
|
|
8
8
|
export declare function toOutputScript(network: BTC_NETWORK, address: string): Buffer;
|
|
9
9
|
export declare function toCoinselectAddressType(outputScript: Uint8Array): CoinselectAddressTypes;
|
|
10
|
+
export declare function getDummyOutputScript(type: CoinselectAddressTypes): Uint8Array;
|
|
11
|
+
export declare function getDummyAddress(network: BTC_NETWORK, type: CoinselectAddressTypes): string;
|
|
10
12
|
/**
|
|
11
13
|
* General parsers for PSBTs, can parse hex or base64 encoded PSBTs
|
|
12
14
|
* @param _psbt
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.parsePsbtTransaction = exports.toCoinselectAddressType = exports.toOutputScript = exports.fromOutputScript = void 0;
|
|
3
|
+
exports.parsePsbtTransaction = exports.getDummyAddress = exports.getDummyOutputScript = exports.toCoinselectAddressType = exports.toOutputScript = exports.fromOutputScript = void 0;
|
|
4
4
|
const utils_1 = require("@scure/btc-signer/utils");
|
|
5
5
|
const buffer_1 = require("buffer");
|
|
6
6
|
const btc_signer_1 = require("@scure/btc-signer");
|
|
7
|
+
const Utils_1 = require("./Utils");
|
|
7
8
|
function fromOutputScript(network, outputScriptHex) {
|
|
8
9
|
return (0, btc_signer_1.Address)(network).encode(btc_signer_1.OutScript.decode(buffer_1.Buffer.from(outputScriptHex, "hex")));
|
|
9
10
|
}
|
|
@@ -71,6 +72,44 @@ function toCoinselectAddressType(outputScript) {
|
|
|
71
72
|
throw new Error("Unrecognized address type!");
|
|
72
73
|
}
|
|
73
74
|
exports.toCoinselectAddressType = toCoinselectAddressType;
|
|
75
|
+
function getDummySpec(type) {
|
|
76
|
+
switch (type) {
|
|
77
|
+
case "p2pkh":
|
|
78
|
+
return {
|
|
79
|
+
type: "pkh",
|
|
80
|
+
hash: (0, Utils_1.randomBytes)(20)
|
|
81
|
+
};
|
|
82
|
+
case "p2sh-p2wpkh":
|
|
83
|
+
return {
|
|
84
|
+
type: "sh",
|
|
85
|
+
hash: (0, Utils_1.randomBytes)(20)
|
|
86
|
+
};
|
|
87
|
+
case "p2wpkh":
|
|
88
|
+
return {
|
|
89
|
+
type: "wpkh",
|
|
90
|
+
hash: (0, Utils_1.randomBytes)(20)
|
|
91
|
+
};
|
|
92
|
+
case "p2wsh":
|
|
93
|
+
return {
|
|
94
|
+
type: "wsh",
|
|
95
|
+
hash: (0, Utils_1.randomBytes)(32)
|
|
96
|
+
};
|
|
97
|
+
case "p2tr":
|
|
98
|
+
return {
|
|
99
|
+
type: "tr",
|
|
100
|
+
pubkey: buffer_1.Buffer.from("0101010101010101010101010101010101010101010101010101010101010101", "hex")
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
throw new Error("Unrecognized address type!");
|
|
104
|
+
}
|
|
105
|
+
function getDummyOutputScript(type) {
|
|
106
|
+
return btc_signer_1.OutScript.encode(getDummySpec(type));
|
|
107
|
+
}
|
|
108
|
+
exports.getDummyOutputScript = getDummyOutputScript;
|
|
109
|
+
function getDummyAddress(network, type) {
|
|
110
|
+
return (0, btc_signer_1.Address)(network).encode(getDummySpec(type));
|
|
111
|
+
}
|
|
112
|
+
exports.getDummyAddress = getDummyAddress;
|
|
74
113
|
/**
|
|
75
114
|
* General parsers for PSBTs, can parse hex or base64 encoded PSBTs
|
|
76
115
|
* @param _psbt
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { IBitcoinWallet } from "../bitcoin/wallet/IBitcoinWallet";
|
|
2
2
|
import { BTC_NETWORK } from "@scure/btc-signer/utils";
|
|
3
|
-
import { BitcoinRpcWithAddressIndex } from "@atomiqlabs/base";
|
|
3
|
+
import { BitcoinNetwork, BitcoinRpcWithAddressIndex } from "@atomiqlabs/base";
|
|
4
4
|
export declare function toBitcoinWallet(_bitcoinWallet: IBitcoinWallet | {
|
|
5
5
|
address: string;
|
|
6
6
|
publicKey: string;
|
|
7
|
-
}, btcRpc: BitcoinRpcWithAddressIndex<any>, bitcoinNetwork: BTC_NETWORK): IBitcoinWallet;
|
|
7
|
+
}, btcRpc: BitcoinRpcWithAddressIndex<any>, bitcoinNetwork: BTC_NETWORK | BitcoinNetwork): IBitcoinWallet;
|
package/package.json
CHANGED
|
@@ -15,9 +15,10 @@ export function accumulative (
|
|
|
15
15
|
): {
|
|
16
16
|
inputs?: CoinselectTxInput[],
|
|
17
17
|
outputs?: CoinselectTxOutput[],
|
|
18
|
+
effectiveFeeRate?: number,
|
|
18
19
|
fee: number
|
|
19
20
|
} {
|
|
20
|
-
if (!isFinite(utils.
|
|
21
|
+
if (!isFinite(utils.numberOrNaN(feeRate))) throw new Error("Invalid feeRate passed!");
|
|
21
22
|
|
|
22
23
|
const inputs = requiredInputs==null ? [] : [...requiredInputs];
|
|
23
24
|
let bytesAccum = utils.transactionBytes(inputs, outputs, type);
|
|
@@ -11,9 +11,10 @@ export function blackjack (
|
|
|
11
11
|
): {
|
|
12
12
|
inputs?: CoinselectTxInput[],
|
|
13
13
|
outputs?: CoinselectTxOutput[],
|
|
14
|
+
effectiveFeeRate?: number,
|
|
14
15
|
fee: number
|
|
15
16
|
} {
|
|
16
|
-
if (!isFinite(utils.
|
|
17
|
+
if (!isFinite(utils.numberOrNaN(feeRate))) throw new Error("Invalid feeRate passed!");
|
|
17
18
|
|
|
18
19
|
const inputs = requiredInputs==null ? [] : [...requiredInputs];
|
|
19
20
|
let bytesAccum = utils.transactionBytes(inputs, outputs, type);
|
|
@@ -20,6 +20,7 @@ export function coinSelect (
|
|
|
20
20
|
): {
|
|
21
21
|
inputs?: CoinselectTxInput[],
|
|
22
22
|
outputs?: CoinselectTxOutput[],
|
|
23
|
+
effectiveFeeRate?: number,
|
|
23
24
|
fee: number
|
|
24
25
|
} {
|
|
25
26
|
// order by descending value, minus the inputs approximate fee
|
|
@@ -38,16 +39,16 @@ export function coinSelect (
|
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
export function maxSendable (
|
|
41
|
-
utxos: CoinselectTxInput[],
|
|
42
|
+
utxos: Omit<CoinselectTxInput, "txId" | "address" | "vout" | "outputScript">[],
|
|
42
43
|
output: {script: Buffer, type: CoinselectAddressTypes},
|
|
43
44
|
feeRate: number,
|
|
44
|
-
requiredInputs?: CoinselectTxInput[],
|
|
45
|
+
requiredInputs?: Omit<CoinselectTxInput, "txId" | "address" | "vout" | "outputScript">[],
|
|
45
46
|
additionalOutputs?: {script: Buffer, value: number}[],
|
|
46
47
|
): {
|
|
47
48
|
value: number,
|
|
48
49
|
fee: number
|
|
49
50
|
} {
|
|
50
|
-
if (!isFinite(utils.
|
|
51
|
+
if (!isFinite(utils.numberOrNaN(feeRate))) throw new Error("Invalid feeRate passed!");
|
|
51
52
|
|
|
52
53
|
const outputs = additionalOutputs ?? [];
|
|
53
54
|
const inputs = requiredInputs ?? [];
|
|
@@ -61,7 +62,7 @@ export function maxSendable (
|
|
|
61
62
|
const utxoBytes = utils.inputBytes(utxo);
|
|
62
63
|
const utxoFee = feeRate * utxoBytes;
|
|
63
64
|
let cpfpFee = 0;
|
|
64
|
-
if(utxo.cpfp!=null && utxo.cpfp.txEffectiveFeeRate<feeRate) cpfpFee = utxo.cpfp.txVsize*(feeRate - utxo.cpfp.txEffectiveFeeRate);
|
|
65
|
+
if(utxo.cpfp!=null && utxo.cpfp.txEffectiveFeeRate<feeRate) cpfpFee = Math.ceil(utxo.cpfp.txVsize*(feeRate - utxo.cpfp.txEffectiveFeeRate));
|
|
65
66
|
const utxoValue = utils.uintOrNaN(utxo.value);
|
|
66
67
|
|
|
67
68
|
// skip detrimental input
|
|
@@ -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
|
};
|