I’m tryping to make a transfer script, to transfer bitcoin from on address to another
I have given my entire script below
import * as bitcoin from "bitcoinjs-lib";
import * as tinysecp from "tiny-secp256k1";
import axios from "axios";
import ECPairFactory from "ecpair";
const ECPair = ECPairFactory(tinysecp);
bitcoin.initEccLib(tinysecp);
const NETWORK = bitcoin.networks.testnet;
const MEMPOOL_API = "https://mempool.space/testnet/api";
// Helper function to validate UTXO
function validateUtxo(utxo: any): void {
if (
!utxo.txid ||
typeof utxo.vout !== "number" ||
typeof utxo.value !== "number"
) {
throw new Error("Invalid UTXO structure");
}
if (!/^[a-fA-F0-9]{64}$/.test(utxo.txid)) {
throw new Error("Invalid UTXO txid format");
}
}
export async function sendBTC_P2TR({
wif,
recipient,
amountSats,
}: {
wif: string;
recipient: string;
amountSats: number;
}): Promise<{ success: boolean; message: string; txId?: string }> {
try {
console.log("🏁 Starting transaction process...");
// Input validation
if (!wif || !recipient || !amountSats) {
throw new Error("Missing required parameters");
}
if (typeof amountSats !== "number" || amountSats <= 0) {
throw new Error("Invalid amount");
}
if (!recipient.startsWith("tb1p")) {
throw new Error("Recipient must be a Taproot testnet address");
}
// Key derivation
const keyPair = ECPair.fromWIF(wif, NETWORK);
if (!keyPair.privateKey) throw new Error("No private key derived from WIF");
const privateKey = keyPair.privateKey;
const internalPubkey = Buffer.from(
tinysecp.pointFromScalar(privateKey, true)!.slice(1)
);
const p2tr = bitcoin.payments.p2tr({ internalPubkey, network: NETWORK });
const address = p2tr.address!;
const scriptPubKey = p2tr.output!;
console.log("📬 Sender Taproot address:", address);
// Fetch UTXOs
const { data: utxos } = await axios.get(
`${MEMPOOL_API}/address/${address}/utxo`
);
if (!utxos.length) return { success: false, message: "No UTXOs found" };
// Estimate fee
const { data: fees } = await axios.get(
`${MEMPOOL_API}/v1/fees/recommended`
);
const feeRate = Math.max(fees.hourFee || 1, 2);
const fee = Math.ceil(feeRate * 110); // Estimated vsize for P2TR
// Select UTXO
const utxo = utxos.find((u: any) => u.value >= amountSats + fee);
if (!utxo)
return {
success: false,
message: `No suitable UTXO found (needed ${amountSats + fee} sats)`,
};
// Key tweaking
const tweak = bitcoin.crypto.taggedHash("TapTweak", internalPubkey);
let tweakedPrivKey = tinysecp.privateAdd(privateKey, tweak);
if (!tweakedPrivKey) throw new Error("Failed to tweak private key");
// Build PSBT
const psbt = new bitcoin.Psbt({ network: NETWORK });
psbt.addInput({
hash: utxo.txid,
index: utxo.vout,
witnessUtxo: { script: scriptPubKey, value: utxo.value },
tapInternalKey: internalPubkey,
});
psbt.addOutput({ address: recipient, value: amountSats });
const change = utxo.value - amountSats - fee;
if (change > 294) {
// Dust limit
psbt.addOutput({ address, value: change });
}
// Signing (fixed approach)
const tx = (psbt as any).__CACHE.__TX as bitcoin.Transaction;
const hash = tx.hashForWitnessV1(0, [scriptPubKey], [utxo.value], 0x00);
const signature = Buffer.from(tinysecp.signSchnorr(hash, tweakedPrivKey));
// Update with proper signature format
psbt.updateInput(0, {
tapKeySig: signature,
});
// Final verification
const validator = (pubkey: Buffer, msghash: Buffer, sig: Buffer) => {
return tinysecp.verifySchnorr(msghash, pubkey, sig);
};
psbt.validateSignaturesOfInput(0, validator);
psbt.finalizeAllInputs();
const txHex = psbt.extractTransaction().toHex();
// Broadcast
const { data: txId } = await axios.post(`${MEMPOOL_API}/tx`, txHex, {
headers: { "Content-Type": "text/plain" },
});
return { success: true, message: "Transaction broadcasted", txId };
} catch (error: any) {
console.error("Transaction failed:", error.message);
return {
success: false,
message: error?.response?.data || error?.message || "Unknown error",
};
}
}
But I’m facing an error which I’m not able to understand why
Transaction failed: Request failed with status code 400
❌ Transaction failed: sendrawtransaction RPC error: {"code":-26,"message":"mandatory-script-verify-flag-failed (Invalid Schnorr signature)"}