Closed Nijinsha closed 1 year ago
hi @Nijinsha Have you found a solution?
Changing the amount in any of the outputs will not alter the transaction size. So you can just subtract the fee from the output you desire.
Thanks @Nijinsha
I was considering creating a temporary Psbt
object and recreating it after reducing the fee based on the estimated fee rate. Here's an example:
const psbt1 = new bitcoin.Psbt({ network: NETWORK });
psbt1.addInput({
hash: txHash,
index: 0,
nonWitnessUtxo: Buffer.from(
rawTransaction.hex,
'hex'
)
})
psbt1.addOutput({
address: mainAddress,
value: transactionAmount // use whole amount
});
const feeRate = ... // get feeRate from an API
const vSize = psbt1.extractTransaction().virtualSize();
const fee = feeRate * vSize;
const changedValue = transactionAmount - fee;
// Create real psbt
const psbt = new bitcoin.Psbt({ network: NETWORK });
psbt.addInput({
hash: txHash,
index: 0,
nonWitnessUtxo: Buffer.from(
rawTransaction.hex,
'hex'
)
})
psbt.addOutput({
address: mainAddress,
value: Math.floor(changedValue) // use changedValue here
});
// do the rest...
However, I think this might not be the best approach. Instead, can I use the setFee
method directly on the Psbt
object, like this?
// Add the fee to your PSBT object
psbt.setFee(fee);
Please let me know if this is a recommended approach or if there's a better way to do it.
And can I use nonWitnessUtxo
in all cases like below?
psbt.addInput({
hash: txHash,
index: 0,
nonWitnessUtxo: Buffer.from(
rawTransaction.hex,
'hex'
)
})
I found this at https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.spec.ts
// // If this input was segwit, instead of nonWitnessUtxo, you would add
// // a witnessUtxo as follows. The scriptPubkey and the value only are needed.
// witnessUtxo: {
// script: Buffer.from(
// '76a9148bbc95d2709c71607c60ee3f097c1217482f518d88ac',
// 'hex',
// ),
// value: 90000,
// },
Thank you!
Changing the amount in any of the outputs will not alter the transaction size. So you can just subtract the fee from the output you desire.
Thanks @motorina0 Could you provide me an example code?
............. there is no setFee
method on the Psbt class...
There's no setFee
function anywhere near this repo.
Please don't use Chat-GPT to answer issues...
One way to do it: Create a 0 fee tx and use that to measure.
// Imagine a function called
// function createPsbt(feeSats: number | null): [Psbt, number] {}
// This function will set a 0 fee if feeSats is null, and it will return the finished,
// fully signed, fully finalized Psbt along with the number of sats in the change output
const [psbt, changeAmount] = createPsbt(null);
const feeRate = 15; // sats per vByte (get from some fee rate API like mempool.space)
const vSize = psbt.extractTransaction().virtualSize(); // vBytes
const feeSats = feeRate * vSize;
// Minimum amount an output can send (it's actually closer to 546 sats, but depends on other factors)
const dustLimit = 600;
// Since we need to subtract the fee from changeAmount, and it needs at least 600 sats left over to be a valid output
// We run this check... in reality you might want to loop back, add another input, then try again.
if (changeAmount < feeSats + dustLimit) {
throw new Error('Need to add another input so your change amount can accommodate more fees');
}
// Now it will lower the amount
const [psbt2, ] = createPsbt(feeSats);
Another way: Use knowledge of input and output types to calculate.
https://gist.github.com/junderw/b43af3253ea5865ed52cb51c200ac19c
Thanks @junderw! I really appreciate your help. And can I use nonWitnessUtxo in all cases like below? I am not sure when I can use nonWitnessUtxo or witnessUtxo.
psbt.addInput({
hash: txHash,
index: 0,
nonWitnessUtxo: Buffer.from(
rawTransaction.hex,
'hex'
)
})
Check the examples.
witnessUtxo is for segwit, nonWitnessUtxo is for non-segwit.
Some wallets include both for each input.
@junderw I have 2 questions about the solution you shared above and will be very grateful to you if you can help me understand these
1- Is it necessary to set "dustLimit"? how it can be advantageous and what if we skip this?
2- if changeAmount < feeSats + dustLimit, we have to add another input to meet the transaction requirement, but adding another input means we have to calculate feeSats again by signing a 0 fee transaction with additional input, doesn't this let the signer straight into a loophole? as the new fee may require additional input and this loop cost us a hectic computation and signing cost.
Also, if Bitcoin fees are implicit, then how do the Bitcoin wallets show the transaction fee to the end user while performing the transaction? Referencing the auto-calculated fees in the screenshots attached in https://www.alfa.cash/guides/how-to-set-miners-fee-bitcoin-transaction-popular-crypto-wallets-blockchaininfo-electrum
Fees and dust are highly complicated topics, and a lot of wallets try to simplify things wherever they can, (ie. dust calculation is actually pretty complicated, but "600 or more is definitely fine" is just guessing high just to be safe. iirc 99.999% of the time it's 564 and sometimes it's less, but people don't like trying to calculate it to save a couple of sats.
coin selection (deciding which utxo to add to the transaction as an input), dust processing, and fee calculation are 3 very complicated topics that mix with each other, and are hard to bake into a library...
If we offered an "easy" way to calculate it that cost you 10% extra fees for the rest of your lifetime, as soon as you found out, you might get very angry with us.
Coin selection also affects privacy of your wallet greatly, as well.
Thank you @junderw for such a nice explanation. One thing, as you said 1 P2PKH input is 148 vBytes, please correct me if I'm wrong that 1 P2WPKH (non-segwit) input is 41 vBytes?
I have 1 P2WPKH (non-segwit) input, 1 P2WPKH output and another 1 P2PKH output and my virtual Bytes of hex after signature are 144, which is only possible when input is 68 bytes P2WPKH output is 31 bytes P2PKH output is 34 bytes and extra 11 bytes (don't know why and how)
Please enlighten me with your thoughts on this
Also, @junderw Sir, Is it the best approach to use the highest value UTXO first and then add (if needed) the next UTXO/s (In descending order as per their values)?
There is no best approach.
Each approach has it's own trade offs.
That approach tends to create a lot of tiny dust outputs. The longer you use that strategy, the more dust outputs you have to ignore.
@junderw From the calculation you've mentioned with "fee cost of the input", why did you skip "output/s fee"? as it also will be paid through value in input/s. So in this regard, I assume the calculations will be: 1 P2PKH input = 148 vBytes 1 P2PKH output (receiver address) = 34 vBytes 1 P2PKH output (change address) = 34 vBytes if the current fee rate is 15 then we should consider UTXO to add worth 15(148+34+34) = 3240 satoshis, also, all inputs worth less than 3x the fee would be ignored so 3 15 (148+34+34) = 9720. for 2 inputs it'll be 2 9720, for 3 inputs, 3 9720. if it's all correct then the dust amount you've mentioned is 600, such dust UTXOs can never be considered even fee rate is 1 as 3 1 * (148+34+34) = 648 which is less than 600
Please let me know if my calculations are wrong, where I'm mistaken, and how can be tackled, Thank you
I could get the size with this and estimated s/vb from an API
psbt.extractTransaction().virtualSize()
How to update the psbt to reduce the output value by the calculated fees?