High-level transaction construction with TxBuilder, TxIn, TxOut components and automatic fee calculation.
// ES6 imports
import { TxBuilder, TxIn, TxOut, TxOutMap, Tx } from 'bsv'
// CommonJS imports
const { TxBuilder, TxIn, TxOut, TxOutMap, Tx } = require('bsv')High-level transaction builder with automatic fee calculation, change handling, and comprehensive signing support.
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
}// 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())// 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())Represents a transaction input that spends a previous output.
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
}// 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)Represents a transaction output that can be spent by future transactions.
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
}// 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)Maps unspent transaction outputs for efficient UTXO management.
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
}// 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// 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
}// 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
}// 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
}// 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`)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)
}