Typical Transaction (1 input, 2 outputs) - Legacy P2PKH

To follow along this tutorial

  • Clone the Github repository

  • cd code

  • npm install or yarn install

  • Execute the transaction code by typing node tx_filename.js

  • Alternatively you can enter the commands step-by-step by cd into ./code then type node in a terminal to open the Node.js REPL

  • Open the Bitcoin Core GUI console or use bitcoin-cli for the Bitcoin Core commands

  • Use bx aka Libbitcoin-explorer as a handy complement

Let’s create a legacy P2PKH transaction with 1 input and 2 outputs. This is a more realistic example that illustrates the notion of change. The second output is actually our change.

Spending a UTXO is like spending a bank note or a coin, you can’t split it. You spend it and you get back the change.

Creating UTXO to spend

We first need to create a transaction in order to have a UTXO at our disposal that we can spend.

In the Bitcoin console, send 1 BTC to alice_1 P2PKH address.
sendtoaddress n4SvybJicv79X1Uc4o3fYXWGwXadA53FSq 1

We now have an UTXO locked with alice_1 public key hash. In order to spend it, we will refer to it with the transaction id (txid) and the output index (vout), also called outpoint. Fortunately, sendtoaddress returns the id of the transaction.

Get the output index so that we have the outpoint (txid / vout).
gettransaction TX_ID
Find the output index (or vout) under details  vout

Creating a typical transaction

Now let’s spend the UTXO with BitcoinJS.

Import libraries, test wallets and set the network.
const bitcoin = require('bitcoinjs-lib')
const { alice, bob } = require('./wallets.json')
const network = bitcoin.networks.regtest
Create a BitcoinJS key pair object for alice_1, the spender of our new UTXO, and the only one capable of spending it.
const keyPairAlice1 = bitcoin.ECPair.fromWIF(alice[1].wif, network)
Create the PSBT by filling TX_ID, TX_OUT and TX_HEX.
const psbt = new bitcoin.Psbt({network})
  .addInput({
    hash: 'TX_ID',
    index: TX_OUT,
    nonWitnessUtxo: Buffer.from('TX_HEX', 'hex')
  }) (1)
  .addOutput({
    address: bob[1].p2pkh,
    value: 5e7, (2)
  })
  .addOutput({
    address: alice[2].p2pkh,
    value: 499e5, (3)
  })
1 Outpoint and previous tx hexadecimal
2 The actual "spend": bob_1 P2PKH recipient address and the amount of 0.5 BTC
3 Alice’s change: alice’s change address (here alice_2) and the amount of 0.499 BTC

The miner fee is calculated by subtracting the outputs from the inputs.
100 000 000 - (50 000 000 + 49 900 000) = 100 000 100 000 satoshis equals 0,001 BTC, this is the miner fee.

The UTXO is locked with alice_1’s public key hash. If she wants to spend it, she needs to prove her ownership of the private key that is linked to the public key, which hash is written in the UTXO.

To do so, alice_1 will sign this transaction that we just built with her private key. BitcoinJS will automatically place the signature into the scriptSig field of the input 0.

Sign the input and validate signature
psbt.signInput(0, keyPairAlice1)
psbt.validateSignaturesOfInput(0)
Finalize the PSBT.
psbt.finalizeAllInputs()
Finally we can extract the transaction and get the raw hex serialization.
console.log('Transaction hexadecimal:')
console.log(psbt.extractTransaction().toHex())
Inspect the raw transaction with Bitcoin Core CLI, check that everything is correct.
decoderawtransaction TX_HEX

Broadcasting the transaction

It’s time to broadcast the transaction via Bitcoin Core CLI.
sendrawtransaction TX_HEX

sendrawtransaction returns the transaction ID, with which you can inspect your transaction again.

getrawtransaction TX_ID true
Don’t forget the second argument. If false, it returns the hex string, otherwise it returns a detailed json object.