or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

core-bitcoin.mdcryptography.mdencryption.mdhd-wallets.mdindex.mdtransaction-building.mdutilities.md
tile.json

transaction-building.mddocs/

Transaction Building

High-level transaction construction with TxBuilder, TxIn, TxOut components and automatic fee calculation.

Imports

// ES6 imports
import { TxBuilder, TxIn, TxOut, TxOutMap, Tx } from 'bsv'

// CommonJS imports
const { TxBuilder, TxIn, TxOut, TxOutMap, Tx } = require('bsv')

TxBuilder

High-level transaction builder with automatic fee calculation, change handling, and comprehensive signing support.

Class Definition

class TxBuilder {
  constructor(
    tx?: Tx,                    // Transaction being built
    txIns?: object[],          // Input configurations
    txOuts?: object[],         // Output configurations
    uTxOutMap?: TxOutMap,      // Unspent output map
    sigOperations?: object[],   // Signature operations
    changeScript?: Script,     // Change output script
    feePerKbNum?: number,      // Fee per kilobyte
    dust?: number,             // Dust threshold
    nLockTime?: number         // Transaction lock time
  )

  // Core properties
  tx: Tx                     // Transaction being built
  txIns: object[]           // Input configurations
  txOuts: object[]          // Output configurations
  uTxOutMap: TxOutMap       // Unspent outputs map
  sigOperations: object[]   // Signature operations
  changeScript: Script      // Change output script
  feePerKbNum: number      // Fee rate (satoshis per KB)
  dust: number             // Dust threshold (satoshis)
  nLockTime: number        // Transaction lock time

  // Configuration methods
  setFeePerKbNum(feePerKbNum: number): TxBuilder
  setChangeAddress(changeAddress: Address): TxBuilder
  setChangeScript(changeScript: Script): TxBuilder
  setDust(dust: number): TxBuilder
  setNLocktime(nLockTime: number): TxBuilder

  // Input methods
  inputFromPubKeyHash(
    txHashBuf: Buffer,       // Previous transaction hash
    txOutNum: number,        // Previous output index
    txOut: TxOut,           // Previous output being spent
    pubKey: PubKey,         // Public key for P2PKH
    nSequence?: number,     // Sequence number
    nHashType?: number      // Hash type for signing
  ): TxBuilder

  inputFromScript(
    txHashBuf: Buffer,       // Previous transaction hash
    txOutNum: number,        // Previous output index
    txOut: TxOut,           // Previous output being spent
    script: Script,         // Unlocking script
    nSequence?: number      // Sequence number
  ): TxBuilder

  // Output methods
  outputToAddress(
    valueBn: Bn,            // Output value in satoshis
    addr: Address           // Destination address
  ): TxBuilder

  outputToScript(
    valueBn: Bn,            // Output value in satoshis
    script: Script          // Locking script
  ): TxBuilder

  // Transaction building
  build(opts?: object): TxBuilder     // Build complete transaction
  sort(): TxBuilder                   // Apply BIP 69 sorting

  // Signing methods
  sign(keyPairs: KeyPair[]): TxBuilder           // Sign with multiple key pairs
  signTxIn(
    nIn: number,            // Input index
    keyPair: KeyPair,       // Signing key pair
    txOut: TxOut,          // Output being spent
    nScriptChunk?: number,  // Script chunk index (for multisig)
    nHashType?: number,     // Hash type
    flags?: number          // Script flags
  ): TxBuilder

  // Fee calculation
  estimateSize(): number              // Estimate transaction size
  estimateFee(): number              // Estimate transaction fee
  
  // Signature utilities
  getSig(
    keyPair: KeyPair,       // Signing key pair
    nHashType: number,      // Hash type
    nIn: number,           // Input index
    subScript: Script,     // Script being signed
    flags?: number         // Script flags
  ): Sig

  fillSig(
    nIn: number,           // Input index
    nScriptChunk: number,  // Script chunk index
    sig: Sig               // Signature to insert
  ): TxBuilder

  // Static utility methods
  static allSigsPresent(m: number, script: Script): boolean
  static removeBlankSigs(script: Script): Script
}

Usage Examples

// Create transaction builder
const txBuilder = new TxBuilder()

// Configure fee rate and change
txBuilder.setFeePerKbNum(500) // 500 satoshis per KB
txBuilder.setChangeAddress(changeAddress)
txBuilder.setDust(546) // Standard dust threshold

// Add inputs (spending previous outputs)
const txHashBuf = Buffer.from('previous-tx-hash', 'hex')
const txOut = new TxOut(Bn.fromNumber(100000), Script.fromPubKeyHash(hashBuf))
txBuilder.inputFromPubKeyHash(txHashBuf, 0, txOut, pubKey)

// Add outputs (payments)
txBuilder.outputToAddress(Bn.fromNumber(50000), toAddress)
txBuilder.outputToAddress(Bn.fromNumber(30000), anotherAddress)

// Build transaction with automatic change and fee calculation
txBuilder.build()

// Sign transaction
const keyPairs = [keyPair1, keyPair2]
txBuilder.sign(keyPairs)

// Get final transaction
const finalTx = txBuilder.tx
console.log('Transaction ID:', finalTx.id())

Advanced Transaction Building

// Complete P2PKH transaction building workflow
const txBuilder = new TxBuilder()

// Set transaction parameters
txBuilder.setFeePerKbNum(1000) // Higher fee for faster confirmation
txBuilder.setChangeAddress(changeAddress)
txBuilder.setNLocktime(0) // No lock time

// Add multiple inputs
const utxos = [
  {
    txHashBuf: Buffer.from('utxo1-hash', 'hex'),
    txOutNum: 0,
    txOut: new TxOut(Bn.fromNumber(75000), Script.fromPubKeyHash(hash1)),
    pubKey: pubKey1,
    keyPair: keyPair1
  },
  {
    txHashBuf: Buffer.from('utxo2-hash', 'hex'),
    txOutNum: 1,
    txOut: new TxOut(Bn.fromNumber(50000), Script.fromPubKeyHash(hash2)),
    pubKey: pubKey2,
    keyPair: keyPair2
  }
]

// Add all inputs
utxos.forEach(utxo => {
  txBuilder.inputFromPubKeyHash(
    utxo.txHashBuf,
    utxo.txOutNum,
    utxo.txOut,
    utxo.pubKey
  )
})

// Add outputs
txBuilder.outputToAddress(Bn.fromNumber(80000), recipientAddress)
txBuilder.outputToScript(Bn.fromNumber(10000), opReturnScript)

// Build with change calculation
txBuilder.build()

// Estimate fees
const estimatedSize = txBuilder.estimateSize()
const estimatedFee = txBuilder.estimateFee()
console.log(`Estimated size: ${estimatedSize} bytes, fee: ${estimatedFee} satoshis`)

// Sign transaction
const keyPairs = utxos.map(utxo => utxo.keyPair)
txBuilder.sign(keyPairs)

// Apply BIP 69 sorting
txBuilder.sort()

// Verify transaction is valid
const tx = txBuilder.tx
console.log('Transaction valid:', tx.verify())

TxIn (Transaction Input)

Represents a transaction input that spends a previous output.

Class Definition

class TxIn {
  constructor(
    txHashBuf?: Buffer,     // Previous transaction hash
    txOutNum?: number,      // Previous output index
    scriptVi?: VarInt,      // Script length
    script?: Script,        // Unlocking script
    nSequence?: number      // Sequence number
  )

  // Core properties
  txHashBuf: Buffer       // Previous transaction hash (32 bytes)
  txOutNum: number        // Previous output index
  scriptVi: VarInt       // Script length as VarInt
  script: Script         // Unlocking script
  nSequence: number      // Sequence number (0xffffffff = final)

  // Creation methods
  static fromPubKeyHashTxOut(
    txHashBuf: Buffer,     // Previous transaction hash
    txOutNum: number,      // Previous output index  
    txOut: TxOut,         // Output being spent
    pubKey: PubKey        // Public key for P2PKH
  ): TxIn

  static fromBuffer(buf: Buffer): TxIn
  static fromHex(hex: string): TxIn

  // Serialization methods
  toBuffer(): Buffer
  toHex(): string
  toJSON(): object

  // Utility methods
  hasNullInput(): boolean          // Check for coinbase input
  setScript(script: Script): TxIn  // Set script and update length
}

Usage Examples

// Create P2PKH input
const txHashBuf = Buffer.from('previous-tx-hash', 'hex')
const txOut = new TxOut(Bn.fromNumber(100000), Script.fromPubKeyHash(hashBuf))
const txIn = TxIn.fromPubKeyHashTxOut(txHashBuf, 0, txOut, pubKey)

// Manual input creation
const txIn = new TxIn(
  txHashBuf,                    // Previous tx hash
  0,                           // Output index
  VarInt.fromNumber(0),        // Empty script initially
  new Script(),                // Empty unlocking script
  0xffffffff                   // Final sequence
)

// Set unlocking script later
const unlockingScript = Script.fromAsmString('signature pubkey')
txIn.setScript(unlockingScript)

// Check for coinbase input
const isCoinbase = txIn.hasNullInput()

// Serialization
const txInHex = txIn.toHex()
const txIn2 = TxIn.fromHex(txInHex)

TxOut (Transaction Output)

Represents a transaction output that can be spent by future transactions.

Class Definition

class TxOut {
  constructor(
    valueBn?: Bn,          // Output value in satoshis
    scriptVi?: VarInt,     // Script length
    script?: Script        // Locking script
  )

  // Core properties
  valueBn: Bn            // Output value in satoshis
  scriptVi: VarInt       // Script length as VarInt
  script: Script         // Locking script

  // Creation methods
  static fromProperties(
    valueBn: Bn,          // Output value
    script: Script        // Locking script
  ): TxOut

  static fromBuffer(buf: Buffer): TxOut
  static fromHex(hex: string): TxOut

  // Serialization methods
  toBuffer(): Buffer
  toHex(): string
  toJSON(): object

  // Utility methods
  setScript(script: Script): TxOut  // Set script and update length
}

Usage Examples

// Create P2PKH output
const value = Bn.fromNumber(50000) // 50,000 satoshis
const pubKeyHash = Hash.sha256Ripemd160(pubKey.toBuffer())
const p2pkhScript = Script.fromPubKeyHash(pubKeyHash)
const txOut = TxOut.fromProperties(value, p2pkhScript)

// Create OP_RETURN data output
const data = Buffer.from('Hello, BSV!', 'utf8')
const dataScript = Script.fromOpReturnData(data)
const dataOut = TxOut.fromProperties(Bn.fromNumber(0), dataScript)

// Manual output creation
const txOut = new TxOut()
txOut.valueBn = Bn.fromNumber(100000)
txOut.setScript(Script.fromPubKeyHash(hashBuf))

// Serialization
const txOutHex = txOut.toHex()
const txOut2 = TxOut.fromHex(txOutHex)

TxOutMap

Maps unspent transaction outputs for efficient UTXO management.

Class Definition

class TxOutMap {
  constructor(map?: Map)

  // Core properties
  map: Map               // Internal UTXO storage

  // UTXO management
  set(
    txHashBuf: Buffer,     // Transaction hash
    txOutNum: number,      // Output index
    txOut: TxOut          // Output object
  ): TxOutMap

  get(
    txHashBuf: Buffer,     // Transaction hash
    txOutNum: number       // Output index
  ): TxOut

  setTx(tx: Tx): TxOutMap  // Store all outputs from transaction
}

Usage Examples

// Create UTXO map
const utxoMap = new TxOutMap()

// Add individual UTXOs
const txHashBuf = Buffer.from('tx-hash', 'hex')
const txOut = new TxOut(Bn.fromNumber(100000), script)
utxoMap.set(txHashBuf, 0, txOut)

// Add all outputs from a transaction
const tx = new Tx()
// ... populate transaction
utxoMap.setTx(tx)

// Retrieve UTXO
const retrievedTxOut = utxoMap.get(txHashBuf, 0)

// Use with TxBuilder
const txBuilder = new TxBuilder()
txBuilder.uTxOutMap = utxoMap
// TxBuilder can now validate inputs against known UTXOs

Complete Transaction Building Workflows

Simple P2PKH Payment

// Simple payment from one address to another
async function createSimplePayment(fromKeyPair, toAddress, amount, fee, utxos) {
  const txBuilder = new TxBuilder()
  
  // Set fee rate
  txBuilder.setFeePerKbNum(fee)
  
  // Set change address (same as sender)
  const fromAddress = Address.fromPubKey(fromKeyPair.pubKey)
  txBuilder.setChangeAddress(fromAddress)
  
  // Add inputs
  let totalInput = Bn.fromNumber(0)
  for (const utxo of utxos) {
    txBuilder.inputFromPubKeyHash(
      Buffer.from(utxo.txid, 'hex'),
      utxo.outputIndex,
      utxo.txOut,
      fromKeyPair.pubKey
    )
    totalInput = totalInput.add(utxo.txOut.valueBn)
    
    // Stop when we have enough inputs
    if (totalInput.gte(Bn.fromNumber(amount + fee * 2))) break
  }
  
  // Add output
  txBuilder.outputToAddress(Bn.fromNumber(amount), toAddress)
  
  // Build transaction
  txBuilder.build()
  
  // Sign
  txBuilder.sign([fromKeyPair])
  
  return txBuilder.tx
}

Multi-Input Multi-Output Transaction

// Complex transaction with multiple inputs and outputs
function createComplexTransaction() {
  const txBuilder = new TxBuilder()
  
  // Configuration
  txBuilder.setFeePerKbNum(1000)
  txBuilder.setChangeAddress(changeAddress)
  
  // Add multiple inputs from different addresses
  const inputs = [
    {
      txHash: 'hash1',
      outputIndex: 0,
      txOut: new TxOut(Bn.fromNumber(75000), Script.fromPubKeyHash(hash1)),
      pubKey: pubKey1,
      keyPair: keyPair1
    },
    {
      txHash: 'hash2', 
      outputIndex: 1,
      txOut: new TxOut(Bn.fromNumber(50000), Script.fromPubKeyHash(hash2)),
      pubKey: pubKey2,
      keyPair: keyPair2
    }
  ]
  
  inputs.forEach(input => {
    txBuilder.inputFromPubKeyHash(
      Buffer.from(input.txHash, 'hex'),
      input.outputIndex,
      input.txOut,
      input.pubKey
    )
  })
  
  // Add multiple outputs
  const outputs = [
    { address: address1, amount: 40000 },
    { address: address2, amount: 30000 },
    { address: address3, amount: 25000 }
  ]
  
  outputs.forEach(output => {
    txBuilder.outputToAddress(
      Bn.fromNumber(output.amount),
      output.address
    )
  })
  
  // Add OP_RETURN data
  const metadata = Buffer.from('Transaction metadata', 'utf8')
  const opReturnScript = Script.fromOpReturnData(metadata)
  txBuilder.outputToScript(Bn.fromNumber(0), opReturnScript)
  
  // Build and sign
  txBuilder.build()
  const keyPairs = inputs.map(input => input.keyPair)
  txBuilder.sign(keyPairs)
  
  return txBuilder.tx
}

Multisig Transaction

// Create 2-of-3 multisig transaction
function createMultisigTransaction(pubKeys, signingKeyPairs) {
  const txBuilder = new TxBuilder()
  
  // Create 2-of-3 multisig script
  const multisigScript = Script.fromPubKeys(2, pubKeys, true) // sorted
  
  // Create multisig address-like script hash
  const scriptHash = Hash.sha256Ripemd160(multisigScript.toBuffer())
  const p2shScript = Script.fromScriptHash(scriptHash)
  
  // Spending from multisig requires the original script
  txBuilder.inputFromScript(
    Buffer.from('multisig-tx-hash', 'hex'),
    0,
    new TxOut(Bn.fromNumber(100000), p2shScript),
    multisigScript
  )
  
  // Add output
  txBuilder.outputToAddress(Bn.fromNumber(90000), recipientAddress)
  
  // Build
  txBuilder.build()
  
  // Sign with required number of keys (2 out of 3)
  txBuilder.sign(signingKeyPairs.slice(0, 2))
  
  return txBuilder.tx
}

Fee Calculation and Optimization

// Advanced fee calculation
function optimizeTransactionFee(txBuilder, targetFeeRate) {
  // Build transaction first to get accurate size
  txBuilder.build()
  
  // Get size and calculate optimal fee
  const txSize = txBuilder.estimateSize()
  const optimalFee = Math.ceil((txSize * targetFeeRate) / 1000)
  
  // Update fee rate if needed
  const currentFee = txBuilder.estimateFee()
  if (Math.abs(currentFee - optimalFee) > 100) { // If difference > 100 sats
    txBuilder.setFeePerKbNum(targetFeeRate)
    txBuilder.build() // Rebuild with new fee
  }
  
  return {
    size: txSize,
    fee: txBuilder.estimateFee(),
    feeRate: (txBuilder.estimateFee() / txSize) * 1000
  }
}

// Usage
const txBuilder = new TxBuilder()
// ... configure inputs and outputs
const feeInfo = optimizeTransactionFee(txBuilder, 500) // 500 sat/KB target
console.log(`Optimized: ${feeInfo.size} bytes, ${feeInfo.fee} sats, ${feeInfo.feeRate} sat/KB`)

Error Handling

Transaction building includes comprehensive validation:

// Input validation
try {
  txBuilder.inputFromPubKeyHash(invalidHash, 0, txOut, pubKey)
} catch (error) {
  console.error('Invalid input:', error.message)
}

// Output validation
try {
  txBuilder.outputToAddress(Bn.fromNumber(-1000), address) // Negative amount
} catch (error) {
  console.error('Invalid output amount:', error.message)
}

// Fee validation
try {
  txBuilder.setFeePerKbNum(0) // Zero fee might be rejected
} catch (error) {
  console.error('Invalid fee rate:', error.message)
}

// Build validation
try {
  txBuilder.build() // May fail if inputs < outputs + fees
} catch (error) {
  console.error('Transaction build failed:', error.message)
}

// Signing validation
try {
  txBuilder.sign([wrongKeyPair]) // Wrong key for input
} catch (error) {
  console.error('Signing failed:', error.message)
}