Skip to content

Instantly share code, notes, and snippets.

@ottosch
Last active June 12, 2025 14:54
Show Gist options
  • Save ottosch/1220798daf741d6d2563170a79de2f54 to your computer and use it in GitHub Desktop.
Save ottosch/1220798daf741d6d2563170a79de2f54 to your computer and use it in GitHub Desktop.
Script to spend an UTXO to a single address, with custom fee rate
#! /usr/bin/env node
// Instructions: place the file inside a directory, run `npm init -y` and install dependencies with
// `npm i bitcoinjs-lib@^6.1.1 ecpair@^2.1.0 tiny-secp256k1@^2.2.1`
const bitcoin = require("bitcoinjs-lib");
const ecc = require("tiny-secp256k1");
const { ECPairFactory } = require("ecpair");
const ECPair = ECPairFactory(ecc);
bitcoin.initEccLib(ecc);
// fill with correct/desired values
const network = bitcoin.networks.bitcoin;
const wif = "<privkey>";
const inputValue = 3000;
const feeRate = 0.1;
const sendToAddr = "<destination_addr>";
const txid = "<txid>";
const index = 0;
const keyPair = ECPair.fromWIF(wif, network);
const internalPubkey = keyPair.publicKey.subarray(1);
const { output } = bitcoin.payments.p2tr({ internalPubkey, network });
const tweakedKeyPair = keyPair.tweak(
bitcoin.crypto.taggedHash('TapTweak', internalPubkey),
);
const dummyPsbt = new bitcoin.Psbt({ network });
dummyPsbt.addInput({
hash: txid,
index,
witnessUtxo: {
script: output,
value: inputValue
},
tapInternalKey: internalPubkey
});
dummyPsbt.addOutput({
address: sendToAddr,
value: inputValue
});
dummyPsbt.signAllInputs(tweakedKeyPair);
dummyPsbt.finalizeAllInputs();
const size = dummyPsbt.extractTransaction().virtualSize();
const fee = Math.ceil(size * feeRate);
const finalPsbt = new bitcoin.Psbt({ network });
finalPsbt.addInput({
hash: txid,
index,
witnessUtxo: {
script: output,
value: inputValue
},
tapInternalKey: internalPubkey
});
finalPsbt.addOutput({
address: sendToAddr,
value: inputValue - fee
});
finalPsbt.signAllInputs(tweakedKeyPair);
finalPsbt.finalizeAllInputs();
const finalTx = finalPsbt.extractTransaction();
const actualSize = finalTx.virtualSize();
const actualFeeRate = (fee / actualSize).toFixed(3);
console.log(`Size: ${actualSize} vb`);
console.log(`Fee: ${fee} sats`);
console.log(`Fee rate: ${actualFeeRate} sat/vb`);
console.log("\nTransaction hex:")
console.log(finalTx.toHex());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment