CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-bsv

JavaScript library for Bitcoin SV (BSV) blockchain development with cryptographic primitives, transaction handling, and protocol implementations

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

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)
}

Install with Tessl CLI

npx tessl i tessl/npm-bsv

docs

core-bitcoin.md

cryptography.md

encryption.md

hd-wallets.md

index.md

transaction-building.md

utilities.md

tile.json