Batch Operations
Overview
Batch Payment Processor
import { Transaction, PrivateKey, P2PKH, Script } from '@bsv/sdk'
/**
* Batch Payment Processor
*
* Efficiently process multiple payments in a single transaction,
* reducing fees and blockchain footprint.
*/
class BatchPaymentProcessor {
private readonly feePerByte = 0.5
/**
* Process batch payments to multiple recipients
*
* @param privateKey - Private key to sign the transaction
* @param utxos - Available UTXOs to fund the payments
* @param recipients - Array of recipients with addresses and amounts
* @returns Signed transaction ready for broadcasting
*/
async processBatchPayment(
privateKey: PrivateKey,
utxos: Array<{
txid: string
vout: number
satoshis: number
script: Script
}>,
recipients: Array<{
address: string
amount: number
memo?: string
}>
): Promise<Transaction> {
try {
// Validate recipients
if (recipients.length === 0) {
throw new Error('At least one recipient is required')
}
// Calculate total payment amount
const totalPayment = recipients.reduce((sum, r) => sum + r.amount, 0)
// Estimate transaction size and fee
const estimatedSize = this.estimateTransactionSize(
utxos.length,
recipients.length + 1 // +1 for change output
)
const estimatedFee = Math.ceil(estimatedSize * this.feePerByte)
// Select UTXOs to cover payment + fees
const selectedUTXOs = this.selectUTXOs(utxos, totalPayment + estimatedFee)
const totalInput = selectedUTXOs.reduce((sum, u) => sum + u.satoshis, 0)
// Create transaction
const tx = new Transaction()
// Add inputs
for (const utxo of selectedUTXOs) {
tx.addInput({
sourceTXID: utxo.txid,
sourceOutputIndex: utxo.vout,
unlockingScriptTemplate: new P2PKH().unlock(privateKey),
sequence: 0xffffffff
})
}
// Add recipient outputs
for (const recipient of recipients) {
// Validate amount is above dust threshold
if (recipient.amount < 546) {
throw new Error(`Amount ${recipient.amount} is below dust threshold (546 satoshis)`)
}
tx.addOutput({
satoshis: recipient.amount,
lockingScript: Script.fromAddress(recipient.address)
})
// Optional: Add OP_RETURN memo if provided
if (recipient.memo) {
tx.addOutput({
satoshis: 0,
lockingScript: Script.fromASM(`OP_FALSE OP_RETURN ${Buffer.from(recipient.memo).toString('hex')}`)
})
}
}
// Calculate and add change output
const actualFee = this.calculateActualFee(tx)
const changeAmount = totalInput - totalPayment - actualFee
if (changeAmount > 546) {
tx.addOutput({
satoshis: changeAmount,
lockingScript: new P2PKH().lock(privateKey.toPublicKey().toHash())
})
} else if (changeAmount < 0) {
throw new Error('Insufficient funds to cover payment and fees')
}
// Sign the transaction
await tx.sign()
return tx
} catch (error) {
throw new Error(`Batch payment processing failed: ${error.message}`)
}
}
/**
* Estimate transaction size in bytes
*/
private estimateTransactionSize(numInputs: number, numOutputs: number): number {
const baseSize = 10
const inputSize = 148 // Average P2PKH input size
const outputSize = 34 // Average P2PKH output size
return baseSize + (numInputs * inputSize) + (numOutputs * outputSize)
}
/**
* Select UTXOs to meet target amount
*/
private selectUTXOs(
utxos: Array<{ satoshis: number; [key: string]: any }>,
targetAmount: number
): typeof utxos {
// Sort UTXOs by size (largest first)
const sorted = [...utxos].sort((a, b) => b.satoshis - a.satoshis)
const selected: typeof utxos = []
let total = 0
for (const utxo of sorted) {
selected.push(utxo)
total += utxo.satoshis
if (total >= targetAmount) {
return selected
}
}
throw new Error(`Insufficient funds: need ${targetAmount}, have ${total}`)
}
/**
* Calculate actual transaction fee based on size
*/
private calculateActualFee(tx: Transaction): number {
const size = tx.toHex().length / 2 // Convert hex string length to bytes
return Math.ceil(size * this.feePerByte)
}
}
/**
* Usage Example
*/
async function example() {
const processor = new BatchPaymentProcessor()
const privateKey = PrivateKey.fromWif('your-wif-key')
const utxos = [
{
txid: 'abc123...',
vout: 0,
satoshis: 100000,
script: new P2PKH().lock(privateKey.toPublicKey().toHash())
},
{
txid: 'def456...',
vout: 0,
satoshis: 50000,
script: new P2PKH().lock(privateKey.toPublicKey().toHash())
}
]
const recipients = [
{ address: '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa', amount: 10000 },
{ address: '1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2', amount: 20000, memo: 'Payment 1' },
{ address: '1JfbZRwdDHKZmuiZgYArJZhcuuzuw2HuMu', amount: 15000 }
]
const tx = await processor.processBatchPayment(privateKey, utxos, recipients)
console.log('Transaction ID:', tx.id('hex'))
console.log('Total recipients:', recipients.length)
}Batch UTXO Consolidation
Batch UTXO Distribution
Optimized Batch Processing
Related Examples
See Also
Last updated
