Wednesday, August 6, 2025
HomeBitcoinbitcoin core - Making a switch script utilizing taproot deal with P2TR

bitcoin core – Making a switch script utilizing taproot deal with P2TR

I am tryping to make a switch script, to switch bitcoin from on deal with to a different

I’ve given my whole script beneath

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.house/testnet/api";

// Helper operate to validate UTXO
operate validateUtxo(utxo: any): void {
  if (
    !utxo.txid ||
    typeof utxo.vout !== "quantity" ||
    typeof utxo.worth !== "quantity"
  ) {
    throw new Error("Invalid UTXO construction");
  }
  if (!/^[a-fA-F0-9]{64}$/.take a look at(utxo.txid)) {
    throw new Error("Invalid UTXO txid format");
  }
}

export async operate sendBTC_P2TR({
  wif,
  recipient,
  amountSats,
}: {
  wif: string;
  recipient: string;
  amountSats: quantity;
}): Promise<{ success: boolean; message: string; txId?: string }> {
  strive {
    console.log("🏁 Beginning transaction course of...");

    // Enter validation
    if (!wif || !recipient || !amountSats) {
      throw new Error("Lacking required parameters");
    }
    if (typeof amountSats !== "quantity" || amountSats <= 0) {
      throw new Error("Invalid quantity");
    }
    if (!recipient.startsWith("tb1p")) {
      throw new Error("Recipient should be a Taproot testnet deal with");
    }

    // Key derivation
    const keyPair = ECPair.fromWIF(wif, NETWORK);
    if (!keyPair.privateKey) throw new Error("No personal key derived from WIF");

    const privateKey = keyPair.privateKey;
    const internalPubkey = Buffer.from(
      tinysecp.pointFromScalar(privateKey, true)!.slice(1)
    );

    const p2tr = bitcoin.funds.p2tr({ internalPubkey, community: NETWORK });
    const deal with = p2tr.deal with!;
    const scriptPubKey = p2tr.output!;

    console.log("📬 Sender Taproot deal with:", deal with);

    // Fetch UTXOs
    const { knowledge: utxos } = await axios.get(
      `${MEMPOOL_API}/deal with/${deal with}/utxo`
    );
    if (!utxos.size) return { success: false, message: "No UTXOs discovered" };

    // Estimate charge
    const { knowledge: charges } = await axios.get(
      `${MEMPOOL_API}/v1/charges/beneficial`
    );
    const feeRate = Math.max(charges.hourFee || 1, 2);
    const charge = Math.ceil(feeRate * 110); // Estimated vsize for P2TR

    // Choose UTXO
    const utxo = utxos.discover((u: any) => u.worth >= amountSats + charge);
    if (!utxo)
      return {
        success: false,
        message: `No appropriate UTXO discovered (wanted ${amountSats + charge} sats)`,
      };

    // Key tweaking
    const tweak = bitcoin.crypto.taggedHash("TapTweak", internalPubkey);
    let tweakedPrivKey = tinysecp.privateAdd(privateKey, tweak);
    if (!tweakedPrivKey) throw new Error("Didn't tweak personal key");

    // Construct PSBT
    const psbt = new bitcoin.Psbt({ community: NETWORK });
    psbt.addInput({
      hash: utxo.txid,
      index: utxo.vout,
      witnessUtxo: { script: scriptPubKey, worth: utxo.worth },
      tapInternalKey: internalPubkey,
    });

    psbt.addOutput({ deal with: recipient, worth: amountSats });
    const change = utxo.worth - amountSats - charge;
    if (change > 294) {
      // Mud restrict
      psbt.addOutput({ deal with, worth: change });
    }

    // Signing (fastened strategy)
    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));

    // Replace with correct signature format
    psbt.updateInput(0, {
      tapKeySig: signature,
    });

    // Closing 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 { knowledge: txId } = await axios.submit(`${MEMPOOL_API}/tx`, txHex, {
      headers: { "Content material-Kind": "textual content/plain" },
    });

    return { success: true, message: "Transaction broadcasted", txId };
  } catch (error: any) {
    console.error("Transaction failed:", error.message);
    return ;
  }
}

However I am dealing with an error which I am not capable of perceive why

Transaction failed: Request failed with standing code 400
❌ Transaction failed: sendrawtransaction RPC error: {"code":-26,"message":"mandatory-script-verify-flag-failed (Invalid Schnorr signature)"}

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments