diff --git a/src/__tests__/families/bitcoin/wallet-btc/wallet.estimateMaxSpendable.test.ts b/src/__tests__/families/bitcoin/wallet-btc/wallet.estimateMaxSpendable.test.ts index 3f4eb2ac3d..9e69749c63 100644 --- a/src/__tests__/families/bitcoin/wallet-btc/wallet.estimateMaxSpendable.test.ts +++ b/src/__tests__/families/bitcoin/wallet-btc/wallet.estimateMaxSpendable.test.ts @@ -30,8 +30,7 @@ describe("testing estimateMaxSpendable", () => { let maxSpendable = await wallet.estimateAccountMaxSpendable( account, 0, - [], - true + [] ); const balance = 109088; expect(maxSpendable.toNumber()).toEqual(balance); @@ -43,16 +42,14 @@ describe("testing estimateMaxSpendable", () => { hash: "f80246be50064bb254d2cad82fb0d4ce7768582b99c113694e72411f8032fd7a", outputIndex: 0, }, - ], - true + ] ); expect(maxSpendableExcludeUtxo.toNumber()).toEqual(balance - 1000); let feesPerByte = 100; maxSpendable = await wallet.estimateAccountMaxSpendable( account, feesPerByte, - [], - true + [] ); expect(maxSpendable.toNumber()).toEqual( balance - @@ -69,8 +66,7 @@ describe("testing estimateMaxSpendable", () => { maxSpendable = await wallet.estimateAccountMaxSpendable( account, feesPerByte, - [], - true + [] ); expect(maxSpendable.toNumber()).toEqual(0); }, 60000); diff --git a/src/families/bitcoin/bridge/libcore.ts b/src/families/bitcoin/bridge/libcore.ts index 3634337365..83f1ee888d 100644 --- a/src/families/bitcoin/bridge/libcore.ts +++ b/src/families/bitcoin/bridge/libcore.ts @@ -43,9 +43,9 @@ const receive = makeAccountBridgeReceive({ const getCacheKey = (a, t) => `${a.id}_${a.blockHeight || 0}_${t.amount.toString()}_${String( t.useAllAmount - )}_${t.recipient}_${t.feePerByte ? t.feePerByte.toString() : ""}_${ - t.utxoStrategy.pickUnconfirmedRBF ? 1 : 0 - }_${t.utxoStrategy.strategy}_${String(t.rbf)}_${t.utxoStrategy.excludeUTXOs + )}_${t.recipient}_${t.feePerByte ? t.feePerByte.toString() : ""}_${0}_${ + t.utxoStrategy.strategy + }_${String(t.rbf)}_${t.utxoStrategy.excludeUTXOs .map(({ hash, outputIndex }) => `${hash}@${outputIndex}`) .join("+")}`; @@ -61,7 +61,6 @@ const createTransaction = (): Transaction => ({ amount: new BigNumber(0), utxoStrategy: { strategy: 0, - pickUnconfirmedRBF: false, excludeUTXOs: [], }, recipient: "", diff --git a/src/families/bitcoin/bridge/mock.ts b/src/families/bitcoin/bridge/mock.ts index ca5a7e78de..76179770a0 100644 --- a/src/families/bitcoin/bridge/mock.ts +++ b/src/families/bitcoin/bridge/mock.ts @@ -32,7 +32,6 @@ const createTransaction = (): Transaction => ({ rbf: false, utxoStrategy: { strategy: 0, - pickUnconfirmedRBF: false, excludeUTXOs: [], }, }); diff --git a/src/families/bitcoin/cache.ts b/src/families/bitcoin/cache.ts index 64ec71a4bb..c1a5012fe4 100644 --- a/src/families/bitcoin/cache.ts +++ b/src/families/bitcoin/cache.ts @@ -15,9 +15,9 @@ const getCacheKeyForCalculateFees = ({ }) => `${a.id}_${a.blockHeight || 0}_${t.amount.toString()}_${String( t.useAllAmount - )}_${t.recipient}_${t.feePerByte ? t.feePerByte.toString() : ""}_${ - t.utxoStrategy.pickUnconfirmedRBF ? 1 : 0 - }_${t.utxoStrategy.strategy}_${String(t.rbf)}_${t.utxoStrategy.excludeUTXOs + )}_${t.recipient}_${t.feePerByte ? t.feePerByte.toString() : ""}_${0}_${ + t.utxoStrategy.strategy + }_${String(t.rbf)}_${t.utxoStrategy.excludeUTXOs .map(({ hash, outputIndex }) => `${hash}@${outputIndex}`) .join("+")}`; diff --git a/src/families/bitcoin/cli-transaction.ts b/src/families/bitcoin/cli-transaction.ts index ba3e299da6..c51c08165b 100644 --- a/src/families/bitcoin/cli-transaction.ts +++ b/src/families/bitcoin/cli-transaction.ts @@ -8,11 +8,6 @@ const options = [ type: String, desc: "how much fee per byte", }, - { - name: "pickUnconfirmedRBF", - type: Boolean, - desc: "also pick unconfirmed replaceable txs", - }, { name: "excludeUTXO", alias: "E", @@ -52,7 +47,6 @@ function inferTransactions( rbf: opts.rbf || false, utxoStrategy: { strategy: bitcoinPickingStrategy[opts["bitcoin-pick-strategy"]] || 0, - pickUnconfirmedRBF: opts.pickUnconfirmedRBF || false, excludeUTXOs: (opts.excludeUTXO || []).map((str) => { const [hash, index] = str.split("@"); invariant( diff --git a/src/families/bitcoin/datasets/bitcoin.ts b/src/families/bitcoin/datasets/bitcoin.ts index ef74ce0aef..c0c2669547 100644 --- a/src/families/bitcoin/datasets/bitcoin.ts +++ b/src/families/bitcoin/datasets/bitcoin.ts @@ -47,7 +47,6 @@ const dataset: CurrenciesData = { rbf: false, utxoStrategy: { strategy: 0, - pickUnconfirmedRBF: false, excludeUTXOs: [], }, }), @@ -73,7 +72,6 @@ const dataset: CurrenciesData = { rbf: false, utxoStrategy: { strategy: 0, - pickUnconfirmedRBF: false, excludeUTXOs: [], }, }), @@ -99,7 +97,6 @@ const dataset: CurrenciesData = { rbf: false, utxoStrategy: { strategy: 0, - pickUnconfirmedRBF: false, excludeUTXOs: [], }, }), diff --git a/src/families/bitcoin/datasets/digibyte.ts b/src/families/bitcoin/datasets/digibyte.ts index 3b1235f9d0..8f95648bfe 100644 --- a/src/families/bitcoin/datasets/digibyte.ts +++ b/src/families/bitcoin/datasets/digibyte.ts @@ -46,7 +46,6 @@ const dataset: CurrenciesData = { rbf: false, utxoStrategy: { strategy: 0, - pickUnconfirmedRBF: false, excludeUTXOs: [], }, }), @@ -69,7 +68,6 @@ const dataset: CurrenciesData = { rbf: false, utxoStrategy: { strategy: 0, - pickUnconfirmedRBF: false, excludeUTXOs: [], }, }), @@ -92,7 +90,6 @@ const dataset: CurrenciesData = { rbf: false, utxoStrategy: { strategy: 0, - pickUnconfirmedRBF: false, excludeUTXOs: [], }, }), diff --git a/src/families/bitcoin/datasets/litecoin.ts b/src/families/bitcoin/datasets/litecoin.ts index 4457209d5f..dc0cc9c432 100644 --- a/src/families/bitcoin/datasets/litecoin.ts +++ b/src/families/bitcoin/datasets/litecoin.ts @@ -47,7 +47,6 @@ const dataset: CurrenciesData = { rbf: false, utxoStrategy: { strategy: 0, - pickUnconfirmedRBF: false, excludeUTXOs: [], }, }), @@ -70,7 +69,6 @@ const dataset: CurrenciesData = { rbf: false, utxoStrategy: { strategy: 0, - pickUnconfirmedRBF: false, excludeUTXOs: [], }, }), @@ -93,7 +91,6 @@ const dataset: CurrenciesData = { rbf: false, utxoStrategy: { strategy: 0, - pickUnconfirmedRBF: false, excludeUTXOs: [], }, }), diff --git a/src/families/bitcoin/js-buildTransaction.ts b/src/families/bitcoin/js-buildTransaction.ts index daa3aa240e..99dd5be327 100644 --- a/src/families/bitcoin/js-buildTransaction.ts +++ b/src/families/bitcoin/js-buildTransaction.ts @@ -44,7 +44,6 @@ export const buildTransaction = async ( walletAccount, transaction.feePerByte.toNumber(), //!\ wallet-btc handles fees as JS number transaction.utxoStrategy.excludeUTXOs, - transaction.utxoStrategy.pickUnconfirmedRBF, [transaction.recipient] ); log("btcwallet", "building transaction", transaction); diff --git a/src/families/bitcoin/js-createTransaction.ts b/src/families/bitcoin/js-createTransaction.ts index 13826499bc..a2447cb727 100644 --- a/src/families/bitcoin/js-createTransaction.ts +++ b/src/families/bitcoin/js-createTransaction.ts @@ -12,7 +12,6 @@ const createTransaction = (): Transaction => { amount: new BigNumber(0), utxoStrategy: { strategy: 0, - pickUnconfirmedRBF: false, excludeUTXOs: [], }, recipient: "", diff --git a/src/families/bitcoin/js-estimateMaxSpendable.ts b/src/families/bitcoin/js-estimateMaxSpendable.ts index 8cc20745ef..c703988fcb 100644 --- a/src/families/bitcoin/js-estimateMaxSpendable.ts +++ b/src/families/bitcoin/js-estimateMaxSpendable.ts @@ -30,7 +30,6 @@ const estimateMaxSpendable = async ({ walletAccount, feePerByte.toNumber(), //!\ wallet-btc handles fees as JS number transaction?.utxoStrategy?.excludeUTXOs || [], - transaction?.utxoStrategy?.pickUnconfirmedRBF || false, transaction ? [transaction.recipient] : [] ); return maxSpendable.lt(0) ? new BigNumber(0) : maxSpendable; diff --git a/src/families/bitcoin/libcore-buildTransaction.ts b/src/families/bitcoin/libcore-buildTransaction.ts index 07e7f57eea..d925e0f4f8 100644 --- a/src/families/bitcoin/libcore-buildTransaction.ts +++ b/src/families/bitcoin/libcore-buildTransaction.ts @@ -6,7 +6,7 @@ import { isValidRecipient } from "../../libcore/isValidRecipient"; import { bigNumberToLibcoreAmount } from "../../libcore/buildBigNumber"; import type { Core, CoreCurrency, CoreAccount } from "../../libcore/types"; import type { CoreBitcoinLikeTransaction, Transaction } from "./types"; -import { getUTXOStatus } from "./transaction"; +import { getUTXOStatus } from "./logic"; import { promiseAllBatched } from "../../promise"; import { parseBitcoinUTXO, perCoinLogic } from "./transaction"; diff --git a/src/families/bitcoin/logic.ts b/src/families/bitcoin/logic.ts index d7c682e2fc..7c31acb5bd 100644 --- a/src/families/bitcoin/logic.ts +++ b/src/families/bitcoin/logic.ts @@ -79,7 +79,7 @@ export function isChangeOutput(output: BitcoinOutput): boolean { type UTXOStatus = | { excluded: true; - reason: "pickUnconfirmedRBF" | "pickPendingNonRBF" | "userExclusion"; + reason: "pickPendingUtxo" | "userExclusion"; } | { excluded: false; @@ -88,16 +88,11 @@ export const getUTXOStatus = ( utxo: BitcoinOutput, utxoStrategy: UtxoStrategy ): UTXOStatus => { - if (!utxoStrategy.pickUnconfirmedRBF && utxo.rbf && !utxo.blockHeight) { + if (!utxo.blockHeight && !utxo.isChange) { + // exclude pending and not change utxo return { excluded: true, - reason: "pickUnconfirmedRBF", - }; - } - if (!utxo.rbf && !utxo.blockHeight) { - return { - excluded: true, - reason: "pickPendingNonRBF", + reason: "pickPendingUtxo", }; } if ( diff --git a/src/families/bitcoin/specs.ts b/src/families/bitcoin/specs.ts index 09b16c2ed5..9aa98d8879 100644 --- a/src/families/bitcoin/specs.ts +++ b/src/families/bitcoin/specs.ts @@ -96,7 +96,8 @@ const genericTest = ({ // verify that no utxo that was supposed to be exploded were used expect( utxosPicked.filter( - (u) => getUTXOStatus(u, transaction.utxoStrategy).excluded + (u: BitcoinOutput) => + u.blockHeight && getUTXOStatus(u, transaction.utxoStrategy).excluded ) ).toEqual([]); }; @@ -243,7 +244,6 @@ const bitcoinLikeMutations = ({ { utxoStrategy: { ...transaction.utxoStrategy, - pickUnconfirmedRBF: true, }, }, { diff --git a/src/families/bitcoin/transaction.ts b/src/families/bitcoin/transaction.ts index 37e1d0134e..e5e82610dd 100644 --- a/src/families/bitcoin/transaction.ts +++ b/src/families/bitcoin/transaction.ts @@ -7,7 +7,6 @@ import type { FeeItems, FeeItemsRaw, BitcoinOutput, - UtxoStrategy, CoreBitcoinLikeOutput, CoreBitcoinLikeInput, BitcoinInput, @@ -90,7 +89,7 @@ const formatNetworkInfo = ( export const formatTransaction = (t: Transaction, account: Account): string => { const n = getEnv("DEBUG_UTXO_DISPLAY"); - const { excludeUTXOs, strategy, pickUnconfirmedRBF } = t.utxoStrategy; + const { excludeUTXOs, strategy } = t.utxoStrategy; const displayAll = excludeUTXOs.length <= n; return ` SEND ${ @@ -109,7 +108,7 @@ ${[ Object.keys(bitcoinPickingStrategy).find( (k) => bitcoinPickingStrategy[k] === strategy ), - pickUnconfirmedRBF && "pick-unconfirmed", + "pick-unconfirmed", t.rbf && "RBF-enabled", ] .filter(Boolean) @@ -200,14 +199,6 @@ export const perCoinLogic: Record< }), }, }; -export type UTXOStatus = - | { - excluded: true; - reason: "pickUnconfirmedRBF" | "userExclusion"; - } - | { - excluded: false; - }; export async function parseBitcoinInput( input: CoreBitcoinLikeInput ): Promise { @@ -267,32 +258,6 @@ export async function parseBitcoinUTXO( utxo.rbf = await output.isReplaceable(); return utxo; } -export function getUTXOStatus( - utxo: BitcoinOutput, - utxoStrategy: UtxoStrategy -): UTXOStatus { - if (!utxoStrategy.pickUnconfirmedRBF && utxo.rbf && !utxo.blockHeight) { - return { - excluded: true, - reason: "pickUnconfirmedRBF", - }; - } - - if ( - utxoStrategy.excludeUTXOs.some( - (u) => u.hash === utxo.hash && u.outputIndex === utxo.outputIndex - ) - ) { - return { - excluded: true, - reason: "userExclusion", - }; - } - - return { - excluded: false, - }; -} export function isChangeOutput(output: BitcoinOutput): boolean { if (!output.path) return false; const p = output.path.split("/"); diff --git a/src/families/bitcoin/types.ts b/src/families/bitcoin/types.ts index c2f66ba991..30e394b529 100644 --- a/src/families/bitcoin/types.ts +++ b/src/families/bitcoin/types.ts @@ -134,7 +134,6 @@ export type BitcoinPickingStrategy = typeof bitcoinPickingStrategy[keyof typeof bitcoinPickingStrategy]; export type UtxoStrategy = { strategy: BitcoinPickingStrategy; - pickUnconfirmedRBF: boolean; excludeUTXOs: Array<{ hash: string; outputIndex: number; diff --git a/src/families/bitcoin/wallet-btc/wallet.ts b/src/families/bitcoin/wallet-btc/wallet.ts index bbd3cbca27..fec571066a 100644 --- a/src/families/bitcoin/wallet-btc/wallet.ts +++ b/src/families/bitcoin/wallet-btc/wallet.ts @@ -122,10 +122,12 @@ class BitcoinLikeWallet { account: Account, feePerByte: number, excludeUTXOs: Array<{ hash: string; outputIndex: number }>, - pickUnconfirmedRBF: boolean, outputAddresses: string[] = [] ) { const addresses = await account.xpub.getXpubAddresses(); + const changeAddresses = (await account.xpub.getAccountAddresses(1)).map( + (item) => item.address + ); const utxos = flatten( await Promise.all( addresses.map((address) => @@ -144,7 +146,11 @@ class BitcoinLikeWallet { excludeUtxo.outputIndex === utxo.output_index ) ) { - if ((pickUnconfirmedRBF && utxo.rbf) || utxo.block_height !== null) { + // we can use either non pending utxo or change utxo + if ( + changeAddresses.includes(utxo.address) || + utxo.block_height !== null + ) { usableUtxoCount++; balance = balance.plus(utxo.value); }