@bsv/sdk
Version:
BSV Blockchain Software Development Kit
449 lines (351 loc) • 13.2 kB
Markdown
# Handling Large Bitcoin Transactions
This guide provides authoritative patterns and best practices for constructing, signing, and broadcasting large Bitcoin transactions using the BSV TypeScript SDK. All examples are based on actual SDK APIs and have been verified against the source code.
## Table of Contents
1. [Understanding Large Transactions](#understanding-large-transactions)
2. [Memory Management Strategies](#memory-management-strategies)
3. [Efficient Transaction Construction](#efficient-transaction-construction)
4. [Batch Processing with `WalletClient`](#batch-processing-with-walletclient)
5. [Fee Calculation for Large Transactions](#fee-calculation-for-large-transactions)
6. [Signing Optimization](#signing-optimization)
7. [Broadcasting Strategies](#broadcasting-strategies)
8. [Error Handling and Recovery](#error-handling-and-recovery)
9. [Performance Monitoring](#performance-monitoring)
10. [Complete Example](#complete-example)
## Understanding Large Transactions
Large Bitcoin transactions typically involve:
- **Many inputs** (50+ UTXOs being consumed)
- **Many outputs** (50+ recipients or complex splitting)
- **Large scripts** (complex locking/unlocking conditions)
- **Chained transactions** (dependent transaction sequences)
The SDK provides several mechanisms to handle these efficiently.
## Memory Management Strategies
### Batch Input/Output Addition
Instead of adding inputs and outputs one by one, batch them to reduce memory allocations:
```typescript
import { Transaction, TransactionInput, TransactionOutput, LockingScript, UnlockingScript } from '@bsv/sdk'
class LargeTransactionBuilder {
private transaction: Transaction
private inputBatch: TransactionInput[] = []
private outputBatch: TransactionOutput[] = []
private batchSize = 100
constructor() {
this.transaction = new Transaction()
}
addInputBatch(inputs: TransactionInput[]): void {
this.inputBatch.push(...inputs)
if (this.inputBatch.length >= this.batchSize) {
this.flushInputs()
}
}
addOutputBatch(outputs: TransactionOutput[]): void {
this.outputBatch.push(...outputs)
if (this.outputBatch.length >= this.batchSize) {
this.flushOutputs()
}
}
private flushInputs(): void {
for (const input of this.inputBatch) {
this.transaction.addInput(input)
}
this.inputBatch = []
// Hint garbage collection for large batches
if (global.gc) {
global.gc()
}
}
private flushOutputs(): void {
for (const output of this.outputBatch) {
this.transaction.addOutput(output)
}
this.outputBatch = []
if (global.gc) {
global.gc()
}
}
finalize(): Transaction {
this.flushInputs()
this.flushOutputs()
return this.transaction
}
}
// Example usage with TransactionInput and TransactionOutput creation
async function createLargeTransactionExample() {
const builder = new LargeTransactionBuilder()
// Create properly formatted inputs with required fields
const inputs: TransactionInput[] = []
for (let i = 0; i < 100; i++) {
inputs.push({
sourceTXID: '0'.repeat(64), // Replace with actual TXID
sourceOutputIndex: i,
unlockingScriptLength: 0,
unlockingScript: new UnlockingScript(), // Will be populated during signing
sequenceNumber: 0xffffffff
} as TransactionInput)
}
// Create properly formatted outputs
const outputs: TransactionOutput[] = []
for (let i = 0; i < 100; i++) {
outputs.push({
satoshis: 100,
lockingScriptLength: 6,
lockingScript: LockingScript.fromASM('OP_RETURN 74657374') // OP_RETURN "test"
} as TransactionOutput)
}
builder.addInputBatch(inputs)
builder.addOutputBatch(outputs)
return builder.finalize()
}
```
### Memory Pool Management
For extremely large transactions, implement a memory pool to manage object lifecycle:
```typescript
class TransactionMemoryPool {
private inputPool: TransactionInput[] = []
private outputPool: TransactionOutput[] = []
private maxPoolSize = 1000
borrowInput(): TransactionInput {
return this.inputPool.pop() || {} as TransactionInput
}
borrowOutput(): TransactionOutput {
return this.outputPool.pop() || {} as TransactionOutput
}
returnInput(input: TransactionInput): void {
if (this.inputPool.length < this.maxPoolSize) {
// Clear the input for reuse
Object.keys(input).forEach(key => delete (input as any)[key])
this.inputPool.push(input)
}
}
returnOutput(output: TransactionOutput): void {
if (this.outputPool.length < this.maxPoolSize) {
Object.keys(output).forEach(key => delete (output as any)[key])
this.outputPool.push(output)
}
}
}
```
## Efficient Transaction Construction
### Using the Transaction Constructor
The SDK's Transaction constructor accepts arrays of inputs and outputs, which is more efficient than individual additions:
```typescript
import { Transaction, TransactionInput, TransactionOutput, LockingScript, UnlockingScript } from '@bsv/sdk'
function buildLargeTransaction(
inputs: TransactionInput[],
outputs: TransactionOutput[]
): Transaction {
// More efficient than multiple addInput/addOutput calls
return new Transaction(
1, // version
inputs,
outputs,
0, // lockTime
{}, // metadata
undefined // merklePath
)
}
// Example usage with properly formatted inputs and outputs
function createExampleTransaction(): Transaction {
const inputs: TransactionInput[] = [{
sourceTXID: '0'.repeat(64), // Replace with actual TXID
sourceOutputIndex: 0,
unlockingScriptLength: 0,
unlockingScript: new UnlockingScript(),
sequenceNumber: 0xffffffff
} as TransactionInput]
const outputs: TransactionOutput[] = [{
satoshis: 100,
lockingScriptLength: 6,
lockingScript: LockingScript.fromASM('OP_RETURN 74657374') // OP_RETURN "test"
} as TransactionOutput]
return buildLargeTransaction(inputs, outputs)
}
```
### Chunked Processing
For very large input/output sets, process them in chunks:
```typescript
async function processLargeInputSet(
allInputs: TransactionInput[],
chunkSize: number = 50
): Promise<Transaction[]> {
const transactions: Transaction[] = []
for (let i = 0; i < allInputs.length; i += chunkSize) {
const chunk = allInputs.slice(i, i + chunkSize)
const tx = new Transaction(1, chunk, [])
// Process chunk
await tx.fee()
await tx.sign()
transactions.push(tx)
// Allow event loop to process other tasks
await new Promise(resolve => setImmediate(resolve))
}
return transactions
}
```
## Batch Processing with `WalletClient`
The SDK's `WalletClient` provides built-in batching capabilities for large transaction workflows:
### Chained Transaction Batching
```typescript
import { WalletClient, CreateActionArgs } from '@bsv/sdk'
class BatchTransactionProcessor {
private walletClient: WalletClient
private maxRetries: number = 3
private retryDelay: number = 1000
constructor(walletClient: WalletClient) {
this.walletClient = walletClient
}
async createChainedBatch(actions: CreateActionArgs[]): Promise<string[]> {
const txids: string[] = []
const batchReferences: string[] = []
// Create all transactions without sending (noSend: true)
for (let i = 0; i < actions.length; i++) {
const action = {
...actions[i],
options: {
...actions[i].options,
noSend: true,
// Include previous transaction outputs as known
knownTxids: txids
}
}
const result = await this.walletClient.createAction(action)
if (result.signableTransaction) {
// Sign the transaction
const signResult = await this.walletClient.signAction({
spends: this.generateSpends(action),
reference: result.signableTransaction.reference,
options: { noSend: true }
})
if (signResult.txid) {
txids.push(signResult.txid)
batchReferences.push(result.signableTransaction.reference)
}
}
}
// Send all transactions as a batch
if (batchReferences.length > 0) {
await this.walletClient.signAction({
spends: {},
reference: batchReferences[0],
options: {
sendWith: txids
}
})
}
return txids
}
private generateSpends(action: CreateActionArgs): Record<number, any> {
const spends: Record<number, any> = {}
if (action.inputs) {
action.inputs.forEach((input, index) => {
spends[index] = {
unlockingScript: input.unlockingScript || '',
sequenceNumber: input.sequenceNumber || 0xffffffff
}
})
}
return spends
}
}
```
### Progress Tracking for Large Batches
```typescript
interface BatchProgress {
total: number
completed: number
failed: number
currentPhase: 'creating' | 'signing' | 'broadcasting'
}
class ProgressTrackingBatch {
async processBatchWithProgress(
actions: CreateActionArgs[],
onProgress?: (progress: BatchProgress) => void
): Promise<string[]> {
this.progress.total = actions.length
this.progress.currentPhase = 'creating'
const results: string[] = []
for (let i = 0; i < actions.length; i++) {
try {
const result = await this.walletClient.createAction(actions[i])
if (result.txid) {
results.push(result.txid)
this.progress.completed++
}
} catch (error) {
console.error(`Failed to process action ${i}:`, error)
this.progress.failed++
}
if (onProgress) {
onProgress({ ...this.progress })
}
// Throttle to prevent overwhelming the wallet
if (i % 10 === 0) {
await new Promise(resolve => setTimeout(resolve, 100))
}
}
return results
}
}
```
## Fee Calculation for Large Transactions
### Custom Fee Models
The SDK provides a `FeeModel` interface and `SatoshisPerKilobyte` implementation:
```typescript
import { SatoshisPerKilobyte, Transaction } from '@bsv/sdk'
// FeeModel interface definition (since it's not exported from SDK)
interface FeeModel {
computeFee(transaction: Transaction): Promise<number>
}
class OptimizedFeeModel implements FeeModel {
private baseFeeModel: SatoshisPerKilobyte
private largeTxThreshold: number
constructor(
baseSatPerKb: number = 1,
largeTxThreshold: number = 100000 // 100KB
) {
this.baseFeeModel = new SatoshisPerKilobyte(baseSatPerKb)
this.largeTxThreshold = largeTxThreshold
}
async computeFee(transaction: Transaction): Promise<number> {
const baseFee = await this.baseFeeModel.computeFee(transaction)
const txSize = transaction.toBinary().length
// Apply discount for large transactions
if (txSize > this.largeTxThreshold) {
const discount = Math.min(0.5, (txSize - this.largeTxThreshold) / 1000000)
return Math.floor(baseFee * (1 - discount))
}
return baseFee
}
}
// Usage
async function calculateOptimizedFee(transaction: Transaction): Promise<void> {
const feeModel = new OptimizedFeeModel(1, 50000)
await transaction.fee(feeModel)
}
```
### Batch Fee Calculation
```typescript
async function calculateFeesInBatch(
transactions: Transaction[],
feeModel: FeeModel
): Promise<number[]> {
const feePromises = transactions.map(tx => feeModel.computeFee(tx))
return Promise.all(feePromises)
}
```
## Conclusion
Handling large Bitcoin transactions efficiently requires a comprehensive approach that addresses memory management, performance optimization, and robust error handling. The BSV TypeScript SDK provides powerful tools and patterns to manage these challenges effectively.
### Next Steps
To implement large transaction handling in your application:
1. **Start Simple**: Begin with the `LargeTransactionBuilder` pattern for basic batching
2. **Add Streaming**: Implement `StreamingTransactionBuilder` for memory-constrained scenarios
3. **Optimize Performance**: Add caching and parallel processing using the optimization patterns
4. **Enhance Monitoring**: Integrate the monitoring and error handling examples
5. **Test Thoroughly**: Use the validation patterns to ensure transaction integrity
### Related Documentation
For additional context and complementary patterns, see:
- [Transaction Batching](./transaction-batching.md) - Efficient multi-output transaction patterns
- [Error Handling](./error-handling.md) - Comprehensive error management strategies
- [Performance Optimization](./performance-optimization.md) - General SDK performance patterns
- [Advanced Transaction Signing](./advanced-transaction-signing.md) - Signing optimization for large transactions
- [Transaction Monitoring](./transaction-monitoring.md) - Monitoring and alerting for transaction operations
The techniques in this guide enable you to build robust, scalable Bitcoin applications that can handle enterprise-level transaction volumes while maintaining security, performance, and reliability standards.