Creating Bitcoin PSBTs

PSBTs or partially signed Bitcoin transactions is a standard that allows unsigned or partially signed transactions to be transported for signing by multiple parties.

Here we will show examples of creating partially signed Bitcoin transactions using the light-weight and audited Bitcoin javascript library scure-btc-signer.

Creating a transaction with a wrapped-segwit (P2SH-P2WPHK) address

import * as btc from '@scure/btc-signer'
import { hex, base64 } from '@scure/base'

// You can use any public Bitcoin API to retrieve unspent outputs
const output = {
  "tx_hash": "f39d37ec885de70c598648a2f80f103e1cdf34f7021ddfcb22216b7076169226",
  "block_height": 780179,
  "tx_input_n": -1,
  "tx_output_n": 1,
  "value": 300000,
  "ref_balance": 22681,
  "spent": false,
  "confirmations": 123,
  "confirmed": "2023-03-10T10:02:21Z",
  "double_spend": false
}

// in the below, use btc.TEST_NETWORK for testnet/signet or btc.NETWORK for mainnet
const publicKey = hex.encode("02818b7ff740a40f311d002123087053d5d9e0e1546674aedb10e15a5b57fd3985")
const p2wpkh = btc.p2wpkh(publicKey, btc.TEST_NETWORK);
const p2sh = btc.p2sh(p2wpkh, btc.TEST_NETWORK);

tx.addInput({
  txid: output.tx_hash,
  index: output.tx_output_n,
  witnessUtxo: {
    script: p2sh.script,
    amount: BigInt(output.value),
  },
  redeemScript: p2sh.redeemScript,
})

// You can add more inputs here as necessary

// Add outputs
const recipient = "tb1pzwa68q3udj0f7g5xtdakgecvf45dvssu66ry7y3at22w7vus20vq3sgw62"
const changeAddress = "2NBfRKCUpafbatj5gV9T82uau3igdSf9BXJ"

tx.addOutputAddress(recipient, BigInt(200000), btc.TEST_NETWORK)
tx.addOutputAddress(changeAddress, BigInt(80000), btc.TEST_NETWORK)

// Generate the base64 encoded PSBT that can be 
// passed to a compatible wallet for signing
const psbt = tx.toPSBT(0)
const psbtB64 = base64.encode(psbt0)

Creating a transaction with a native-segwit (P2WPHK) address

Creating a transaction with a Taproot (P2TR) address

Last updated