bitcoinjs-lib icon indicating copy to clipboard operation
bitcoinjs-lib copied to clipboard

Send a Transaction with SegWit P2PKH Keypair

Open 0xCrispy opened this issue 1 year ago • 8 comments

Hi,

I'm new to bitcoinjs-lib and am having a hard time of creating a simple node.js script to send BTC on testnet. A lot of the resources online use outdated method names (e.g., bitcoin.Transaction() vs bitcoin.TransactionBuilder(), need to import ECPair, etc..)

If someone can assist with a working script it would be much appreciated. Currently, I have something along the lines of:

const bitcoin = require('bitcoinjs-lib');
const ECPairFactory = require('ecpair').default;
const ecc = require('tiny-secp256k1');

const ECPair = ECPairFactory(ecc);
const TESTNET = bitcoin.networks.testnet

const privateKey = '';
const toAddress = '';
const keyPair = ECPair.fromWIF(privateKey, TESTNET);

const account = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network: TESTNET })
const psbt = new bitcoin.Psbt({ network: keyPair.network });

psbt.addOutput({
  address: toAddress,
  value: 1500,

psbt.signAllInputs(keyPair.signSchnorr)
  psbt.finalizeAllInputs();

const transaction = psbt.extractTransaction();
console.log(transaction)

0xCrispy avatar Jan 30 '24 13:01 0xCrispy

Try reading these tests from top to bottom. For the helper functions, don't bother looking at them first, just glean the fact that they return a bunch of payment information.

Once you get a grasp on how each script type is constructed using PSBT (read each example including comments) then you should have a better grasp on what you need to do.

https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.spec.ts

junderw avatar Jan 30 '24 13:01 junderw

Here is one example you can try to test it it should work but you have to fund your wallet with test btc in order that works. Also replace the placeholder values like previous_transaction_hash and previous_output_script with the actual values from the UTXO you are spending. You did not precisely explain what do you want to achieve? below are two working examples:

1.Example

const bitcoin = require('bitcoinjs-lib');
const ECPair = require('bitcoinjs-lib').ECPair;
const networks = require('bitcoinjs-lib').networks;
const privateKey = ''; // Replace with your private key
const toAddress = '';  // Replace with the recipient's address
const TESTNET = networks.testnet;
const address = keyPair.getAddress();
const keyPair = ECPair.fromWIF(privateKey, TESTNET);
const address = keyPair.getAddress();
const psbt = new bitcoin.Psbt({ network: TESTNET });

// Add the input (UTXO) - assuming you have an existing unspent transaction output (UTXO)
psbt.addInput({
  hash: 'previous_transaction_hash',
  index: 0, // index of the output in the previous transaction
  witnessUtxo: {
    script: Buffer.from('previous_output_script', 'hex'),
    value: 1000000, // value of the UTXO in satoshis
  },
  redeemScript: undefined, // redeemScript if P2SH
  witnessScript: undefined, // witnessScript if P2WSH
});

// Add the output
psbt.addOutput({
  address: toAddress,
  value: 1500, // amount to send in satoshis
});

// Sign the input
psbt.signInput(0, keyPair);

// Finalize the transaction
psbt.finalizeAllInputs();

// Get the finalized transaction
const transaction = psbt.extractTransaction();

console.log(transaction.toHex());

2.Example

const bitcoin = require('bitcoinjs-lib');
const axios = require('axios');
const fromAddress = ''; // Sender's testnet address
const toAddress = '';   // Recipient's testnet address
const privateKey = '';   // Sender's private key in WIF format
const amountToSend = 1500; // Amount to send in satoshis
const network = bitcoin.networks.testnet;

async function sendBitcoin() {
    const keyPair = bitcoin.ECPair.fromWIF(privateKey, network);
    const address = keyPair.getAddress();

  // Fetch UTXOs 
    const utxos = await axios.get(`https://api.blockcypher.com/v1/btc/test3/addrs/${fromAddress}?unspentOnly=true`);
      const utxo = utxos.data.txrefs[0]; // Assuming you have at least one UTXO

  const txb = new bitcoin.TransactionBuilder(network);

  // Add the UTXO 
  txb.addInput(utxo.tx_hash, utxo.tx_output_n);

  // Add the recipient address as an output
  txb.addOutput(toAddress, amountToSend);

  // Sign the transaction
  txb.sign(0, keyPair);

  // Build and broadcast the transaction
  const tx = txb.build();
  const txHex = tx.toHex();

  // Broadcast the transaction
  await axios.post(`https://api.blockcypher.com/v1/btc/test3/txs/push`, { tx: txHex });

  console.log(`Transaction sent: https://live.blockcypher.com/btc-testnet/tx/${tx.getId()}`);
}

sendBitcoin();

Andrej656 avatar Jan 30 '24 21:01 Andrej656

@junderw i am getting error [Error: Can not finalize input #0]

  import * as bitcoin from 'bitcoinjs-lib';
 const keyPaird = bitcoin.ECPair.fromWIF(privateKey,network);
 const payment = bitcoin.payments.p2sh({  redeem: bitcoin.payments.p2wpkh({ pubkey: keyPaird.publicKey, network: network }) });
      console.log(payment)
    const { address } = payment;
    const redeemScript = payment.redeem?.output;
    
    console.log('bro')
    if (!redeemScript || !address) return null;
      console.log('redde,')
      const psbt = new bitcoin.Psbt({ network });
      
//       // Fetch the source address UTXO data
      const utxoData = await axios.get(`https://api.blockcypher.com/v1/btc/test3/addrs/${sourceAddress}/full`);
     console.log(utxoData.data)
     let balance=utxoData.data.balance
     console.log(balance)
      const utxos = utxoData.data.txs.flatMap((tx) => {
        let voutIndex = 0;
        return tx.outputs.map((output) => {
            const utxo = {
                txid: tx.hash,
                vout: voutIndex++,
                scriptPubKey: output.script,
                amount: output.value,
            };

            if (!output.spent_by) {
                return utxo;
            }
        });
    }).filter(Boolean);
    console.log(utxos,'utox')
  
       console.log(utxos)
      const fee = 10000; // Set an appropriate fee (in satoshis)
     
for (const element of utxos) {
  const response = await axios.get(`https://api.blockcypher.com/v1/btc/test3/txs/${element.txid}?includeHex=true`);
  const previousTx = response.data.hex;
   console.log(previousTx,'dasds')
  psbt.addInput({
      hash: element.txid,
      index: element.vout,
      nonWitnessUtxo: Buffer.from(previousTx, 'hex'),
     redeemScript:redeemScript
  });
}  


const feeRate = 1; // Adjust this value based on the current network conditions

const left=balance-feeRate-amountToSend
    
//       // Add output
      psbt.addOutput({
        address: destinationAddress,
        value: amountToSend+feeRate
      });
      psbt.addOutput({
        address:sourceAddress,
        value:left
      })
   
    //  }
     
      // Sign each input
     try {
      
      psbt.signAllInputs(keyPaird)
     
      console.log(psbt.data.inputs)

      // Finalize all inputs
      psbt.finalizeAllInputs();
       
      // Extract transaction
      const tx = psbt.extractTransaction().toHex();
      
      console.log('Transaction:', tx);
      
      getTransactionId(tx);
      
     } catch (error) {
      console.log(error)
     }

Aamir0890 avatar Feb 27 '24 03:02 Aamir0890

You need to make sure that every output is actually your output.

junderw avatar Feb 27 '24 04:02 junderw

[{"nonWitnessUtxo": [Object], "redeemScript": [Object], "unknownKeyVals": []}, {"nonWitnessUtxo": [Object], "partialSig": [[Object]], "redeemScript": [Object], "unknownKeyVals": []}, {"nonWitnessUtxo": [Object], "redeemScript": [Object], "unknownKeyVals": []}] is the response I am getting from

psbt.signAllInputs(keyPaird console.log(psbt.data.inputs) I think I don't have a partialSig for first element of array and I am getting error. I am not able to resolve it.

Aamir0890 avatar Feb 27 '24 04:02 Aamir0890

That's because it's not your UTXO.

junderw avatar Feb 27 '24 04:02 junderw

@junderw how do i resolve const payment = bitcoin.payments.p2sh({ redeem: bitcoin.payments.p2wpkh({ pubkey: keyPaird.publicKey, network: network }) }); to get correct address and redeemScript from

const { address } = payment; const redeemScript = payment.redeem?.output; The address I am getting from here is not the actual address I have,that I am making https://api.blockcypher.com/v1/btc/test3/addrs/${sourceAddress}/full API call from.

Aamir0890 avatar Feb 27 '24 04:02 Aamir0890

You are misunderstanding me. I didn't say your address or redeemScript are wrong.

You are trying to sign a utxo which is not yours.

Your code is adding EVERY utxo. You only want to add the utxo which actually has your address.

You can not spend Bitcoin that was not sent to you.

Bitcoin transactions have multiple outputs, and each output might be sent to a different address.

Do you understand now? Please let me know if something is not clear.

junderw avatar Feb 27 '24 10:02 junderw