Standard Transactions

Complete examples for creating standard BSV transaction types including P2PKH, P2PK, and their variations.

Overview

BSV supports several standard transaction types that define how bitcoins are locked and unlocked. The most common is Pay-to-Public-Key-Hash (P2PKH), but understanding all standard types is essential for building robust applications. This guide covers P2PKH, P2PK (Pay-to-Public-Key), and multi-output patterns.

Related SDK Components:

P2PKH Transactions

import { Transaction, PrivateKey, P2PKH, Script } from '@bsv/sdk'

/**
 * Pay-to-Public-Key-Hash (P2PKH) Transactions
 *
 * The most common transaction type on BSV
 */
class P2PKHTransactions {
  /**
   * Create a standard P2PKH payment transaction
   */
  async createP2PKHPayment(
    senderKey: PrivateKey,
    recipientAddress: string,
    amount: number,
    utxo: {
      txid: string
      vout: number
      satoshis: number
      script: Script
    }
  ): Promise<Transaction> {
    try {
      if (amount < 546) {
        throw new Error('Amount below dust threshold (546 satoshis)')
      }

      if (amount > utxo.satoshis) {
        throw new Error('Insufficient funds in UTXO')
      }

      const tx = new Transaction()

      // Add input with P2PKH unlocking script
      tx.addInput({
        sourceTXID: utxo.txid,
        sourceOutputIndex: utxo.vout,
        unlockingScriptTemplate: new P2PKH().unlock(senderKey),
        sequence: 0xffffffff
      })

      // Add payment output with P2PKH locking script
      tx.addOutput({
        satoshis: amount,
        lockingScript: Script.fromAddress(recipientAddress)
      })

      // Calculate and add change output
      const fee = 500
      const change = utxo.satoshis - amount - fee

      if (change >= 546) {
        tx.addOutput({
          satoshis: change,
          lockingScript: new P2PKH().lock(senderKey.toPublicKey().toHash())
        })
      }

      await tx.sign()

      console.log('P2PKH payment created')
      console.log('Transaction ID:', tx.id('hex'))
      console.log('Amount:', amount)
      console.log('Recipient:', recipientAddress)

      return tx
    } catch (error) {
      throw new Error(`P2PKH payment failed: ${error.message}`)
    }
  }

  /**
   * Create P2PKH transaction with multiple recipients
   */
  async createMultiRecipientPayment(
    senderKey: PrivateKey,
    recipients: Array<{ address: string; amount: number }>,
    utxos: Array<{
      txid: string
      vout: number
      satoshis: number
      script: Script
    }>
  ): Promise<Transaction> {
    try {
      // Validate recipients
      for (const recipient of recipients) {
        if (recipient.amount < 546) {
          throw new Error(`Amount for ${recipient.address} below dust threshold`)
        }
      }

      const totalAmount = recipients.reduce((sum, r) => sum + r.amount, 0)
      const totalInput = utxos.reduce((sum, u) => sum + u.satoshis, 0)
      const fee = 500 + (utxos.length * 150) + (recipients.length * 50)

      if (totalInput < totalAmount + fee) {
        throw new Error('Insufficient funds')
      }

      const tx = new Transaction()

      // Add all inputs
      for (const utxo of utxos) {
        tx.addInput({
          sourceTXID: utxo.txid,
          sourceOutputIndex: utxo.vout,
          unlockingScriptTemplate: new P2PKH().unlock(senderKey),
          sequence: 0xffffffff
        })
      }

      // Add recipient outputs
      for (const recipient of recipients) {
        tx.addOutput({
          satoshis: recipient.amount,
          lockingScript: Script.fromAddress(recipient.address)
        })
      }

      // Add change output
      const change = totalInput - totalAmount - fee

      if (change >= 546) {
        tx.addOutput({
          satoshis: change,
          lockingScript: new P2PKH().lock(senderKey.toPublicKey().toHash())
        })
      }

      await tx.sign()

      console.log('Multi-recipient payment created')
      console.log('Recipients:', recipients.length)
      console.log('Total amount:', totalAmount)
      console.log('Transaction ID:', tx.id('hex'))

      return tx
    } catch (error) {
      throw new Error(`Multi-recipient payment failed: ${error.message}`)
    }
  }

  /**
   * Create P2PKH transaction with custom fee rate
   */
  async createWithCustomFee(
    senderKey: PrivateKey,
    recipientAddress: string,
    amount: number,
    feeRate: number, // satoshis per byte
    utxo: {
      txid: string
      vout: number
      satoshis: number
      script: Script
    }
  ): Promise<Transaction> {
    try {
      // Estimate transaction size
      const estimatedSize =
        10 + // version, locktime
        148 + // input (typical P2PKH)
        34 + // payment output
        34 // change output

      const fee = Math.ceil(estimatedSize * feeRate)

      const tx = new Transaction()

      tx.addInput({
        sourceTXID: utxo.txid,
        sourceOutputIndex: utxo.vout,
        unlockingScriptTemplate: new P2PKH().unlock(senderKey),
        sequence: 0xffffffff
      })

      tx.addOutput({
        satoshis: amount,
        lockingScript: Script.fromAddress(recipientAddress)
      })

      const change = utxo.satoshis - amount - fee

      if (change >= 546) {
        tx.addOutput({
          satoshis: change,
          lockingScript: new P2PKH().lock(senderKey.toPublicKey().toHash())
        })
      }

      await tx.sign()

      console.log('P2PKH payment with custom fee')
      console.log('Fee rate:', feeRate, 'sat/byte')
      console.log('Total fee:', fee, 'satoshis')
      console.log('Transaction ID:', tx.id('hex'))

      return tx
    } catch (error) {
      throw new Error(`Custom fee payment failed: ${error.message}`)
    }
  }
}

/**
 * Usage Example
 */
async function p2pkhExample() {
  const p2pkh = new P2PKHTransactions()
  const privateKey = PrivateKey.fromRandom()

  const utxo = {
    txid: 'funding-tx...',
    vout: 0,
    satoshis: 100000,
    script: new P2PKH().lock(privateKey.toPublicKey().toHash())
  }

  // Simple payment
  const tx1 = await p2pkh.createP2PKHPayment(
    privateKey,
    '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa',
    50000,
    utxo
  )

  // Multi-recipient payment
  const tx2 = await p2pkh.createMultiRecipientPayment(
    privateKey,
    [
      { address: '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa', amount: 10000 },
      { address: '1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2', amount: 20000 }
    ],
    [utxo]
  )
}

P2PK Transactions

Transaction Templates

Address Validation and Conversion

See Also

SDK Components:

Learning Paths:

Last updated