UNPKG

@bsv/sdk

Version:

BSV Blockchain Software Development Kit

573 lines (462 loc) 21 kB
**Duration**: 75 minutes **Prerequisites**: Completed "Transaction Types and Data" tutorial, Node.js, basic TypeScript knowledge ## Learning Goals - Master multi-input/multi-output transaction construction - Implement advanced fee calculation and optimization strategies - Handle change outputs efficiently - Use advanced `WalletClient` options for transaction control - Implement UTXO selection and management strategies - Handle complex transaction scenarios and error recovery ## Introduction This tutorial builds on your knowledge of basic `WalletClient` usage to explore advanced transaction construction patterns. You'll learn how to create sophisticated transactions that handle multiple inputs and outputs, optimize fees, and manage UTXOs effectively for production applications. ## Prerequisites - Complete the [Transaction Types and Data](./transaction-types.md) tutorial - Have a BRC-100 compliant wallet (such as the [MetaNet Desktop Wallet](https://metanet.bsvb.tech/)) installed and configured - Some BSV in your wallet - Understanding of Bitcoin transaction fundamentals ## Advanced `WalletClient` Options The `createAction` method supports many advanced options through the `options` parameter. Let's explore these capabilities: ```typescript import { WalletClient } from '@bsv/sdk' async function exploreAdvancedOptions() { try { const wallet = new WalletClient('auto', 'localhost') // Authenticate with the wallet const { authenticated } = await wallet.isAuthenticated() if (!authenticated) { await wallet.waitForAuthentication() } // Create a transaction with advanced options const actionResult = await wallet.createAction({ description: 'Advanced options demonstration', outputs: [ { satoshis: 100, lockingScript: '006a0e416476616e636564206f7074696f6e73', // OP_RETURN "Advanced options" outputDescription: 'Advanced options demo' } ], options: { // Don't automatically sign and process - gives you more control signAndProcess: false, // Accept delayed broadcast for better fee optimization acceptDelayedBroadcast: true, // Return only the transaction ID to save bandwidth returnTXIDOnly: false, // Don't send the transaction immediately noSend: true, // Randomize output order for privacy randomizeOutputs: true } }) console.log('Transaction created with advanced options:') console.log('Signable transaction available for manual processing') if (actionResult.signableTransaction) { console.log('Transaction reference:', actionResult.signableTransaction.reference) console.log('Transaction ready for signing') } } catch (error: any) { console.error('Error:', error) // Provide helpful troubleshooting if (error.message?.includes('unlockingScript')) { console.log('\nTroubleshooting: When specifying custom inputs, you must provide:') console.log('- unlockingScript (valid hexadecimal string, not empty)') console.log('- unlockingScriptLength (typically 107 for P2PKH)') console.log('\nRecommendation: Let the wallet auto-select inputs by omitting the inputs array') } } } exploreAdvancedOptions().catch(console.error) ``` ## Multi-Input Transaction Construction Multi-input transactions combine multiple UTXOs to fund larger outputs. The BSV TypeScript SDK provides two approaches: 1. **Automatic Input Selection (Recommended)**: Let the wallet automatically select UTXOs by creating outputs that require more satoshis than any single UTXO can provide. 2. **Manual Input Specification (Advanced)**: Explicitly specify which UTXOs to use as inputs. This requires providing complete unlocking script information and is typically only needed for specialized use cases. ```typescript import { WalletClient } from '@bsv/sdk' async function createMultiInputTransaction() { try { const wallet = new WalletClient('auto', 'localhost') const { authenticated } = await wallet.isAuthenticated() if (!authenticated) { await wallet.waitForAuthentication() } // First, let's see what UTXOs we have available (basket name is 'bsv-tutorial' for consistency with previous tutorials, where we created some outputs) const { outputs } = await wallet.listOutputs({ basket: 'bsv-tutorial', include: 'locking scripts', limit: 10 }) console.log(`Found ${outputs.length} available outputs:`) outputs.forEach((output, index) => { console.log(` ${index + 1}. ${output.outpoint}: ${output.satoshis} satoshis (spendable: ${output.spendable})`) }) // Check if we have enough UTXOs for a multi-input transaction const spendableOutputs = outputs.filter(output => output.spendable && output.satoshis >= 100) if (spendableOutputs.length < 2) { console.log('Need at least 2 spendable outputs for this demo') return } // Method 1: Automatic Input Selection (Recommended) // Create a transaction that requires multiple inputs by using a large output amount // The wallet will automatically select multiple UTXOs if needed const totalAvailable = spendableOutputs.reduce((sum, output) => sum + output.satoshis, 0) const largeAmount = Math.min(1500, Math.floor(totalAvailable * 0.8)) // Use 80% of available funds console.log(`Creating transaction requiring ${largeAmount} satoshis (should trigger multi-input selection)`) const actionResult = await wallet.createAction({ description: 'Multi-input transaction example', outputs: [ { satoshis: largeAmount, lockingScript: '006a0c4d756c74692d696e707574', // OP_RETURN "Multi-input" outputDescription: 'Multi-input demo output' } ] // No inputs specified - let wallet auto-select }) console.log('Transaction created successfully!') console.log('Transaction ID:', actionResult.txid) console.log(`View on explorer: https://whatsonchain.com/tx/${actionResult.txid}`) // Method 2: Manual Input Specification (Advanced) // Manual input specification requires unlocking script details // This is commented out as it requires proper unlocking script construction /* const manualActionResult = await wallet.createAction({ description: 'Manual multi-input transaction', inputs: [ { outpoint: spendableOutputs[0].outpoint, unlockingScript: '00', // Placeholder - would need actual unlocking script unlockingScriptLength: 107, // Typical P2PKH unlocking script length inputDescription: 'First manually selected input' }, { outpoint: spendableOutputs[1].outpoint, unlockingScript: '00', // Placeholder - would need actual unlocking script unlockingScriptLength: 107, // Typical P2PKH unlocking script length inputDescription: 'Second manually selected input' } ], outputs: [ { satoshis: 150, lockingScript: '006a104d616e75616c2d6d756c74692d696e707574', // OP_RETURN "Manual-multi-input" outputDescription: 'Manual multi-input demo output' } ] }) */ } catch (error: any) { console.error('Error:', error) // Provide helpful troubleshooting if (error.message?.includes('unlockingScript')) { console.log('\nTroubleshooting: When specifying custom inputs, you must provide:') console.log('- unlockingScript (valid hexadecimal string, not empty)') console.log('- unlockingScriptLength (typically 107 for P2PKH)') console.log('\nRecommendation: Let the wallet auto-select inputs by omitting the inputs array') } } } createMultiInputTransaction().catch(console.error) ``` ## Batch Processing Multiple Payments For businesses and applications that need to send multiple payments efficiently, batch processing reduces fees and blockchain footprint: ```typescript import { WalletClient } from '@bsv/sdk' async function createBatchPayment() { try { const wallet = new WalletClient('auto', 'localhost') const { authenticated } = await wallet.isAuthenticated() if (!authenticated) { await wallet.waitForAuthentication() } // Simulate multiple payment recipients const payments = [ { amount: 100, description: 'Payment to Alice', data: 'Invoice #001' }, { amount: 150, description: 'Payment to Bob', data: 'Invoice #002' }, { amount: 200, description: 'Payment to Carol', data: 'Invoice #003' } ] // Create outputs for each payment const outputs = payments.map((payment, index) => { // Create OP_RETURN with payment data const dataHex = Buffer.from(payment.data).toString('hex') const dataLength = Buffer.from(payment.data).length const dataLengthHex = dataLength.toString(16).padStart(2, '0') const lockingScript = `006a${dataLengthHex}${dataHex}` return { satoshis: payment.amount, lockingScript: lockingScript, outputDescription: payment.description, basket: 'payments', // Organize outputs in a specific basket tags: ['batch-payment', `invoice-${index + 1}`] // Tag for easy tracking } }) // Create the batch transaction const actionResult = await wallet.createAction({ description: 'Batch payment transaction', outputs: outputs, labels: ['batch-processing', 'business-payments'], // Labels for transaction tracking options: { randomizeOutputs: false // Keep payment order for accounting } }) if (actionResult.txid) { console.log(`Batch payment transaction created: ${actionResult.txid}`) console.log(`Processed ${payments.length} payments in a single transaction`) console.log(`Total amount: ${payments.reduce((sum, p) => sum + p.amount, 0)} satoshis`) console.log(`View on explorer: https://whatsonchain.com/tx/${actionResult.txid}`) } } catch (error: unknown) { console.error('Error:', error) } } createBatchPayment().catch(console.error) ``` ## Advanced UTXO Management with Baskets Baskets provide powerful UTXO organization capabilities. Here's how to use them for advanced transaction patterns: ```typescript import { WalletClient } from '@bsv/sdk' async function advancedBasketManagement() { try { const wallet = new WalletClient('auto', 'localhost') const { authenticated } = await wallet.isAuthenticated() if (!authenticated) { await wallet.waitForAuthentication() } // Create outputs in different baskets for different purposes console.log('Creating specialized UTXO baskets...') const actionResult = await wallet.createAction({ description: 'UTXO organization with baskets', outputs: [ { satoshis: 500, lockingScript: '006a0c48696768207072696f72697479', // OP_RETURN "High priority" outputDescription: 'High priority reserve', basket: 'high-priority', tags: ['reserve', 'urgent-use'], customInstructions: 'Reserved for urgent transactions' }, { satoshis: 300, lockingScript: '006a0d4d656469756d207072696f72697479', // OP_RETURN "Medium priority" outputDescription: 'Medium priority operations', basket: 'medium-priority', tags: ['operations', 'daily-use'] }, { satoshis: 200, lockingScript: '006a0b4c6f77207072696f72697479', // OP_RETURN "Low priority" outputDescription: 'Low priority batch', basket: 'low-priority', tags: ['batch', 'bulk-operations'] } ] }) if (actionResult.txid) { console.log(`Basket organization transaction: ${actionResult.txid}`) console.log('Note: OP_RETURN outputs are unspendable by design (for data storage)') console.log('Waiting for wallet to process new outputs...') // Give the wallet a moment to process the new outputs await new Promise(resolve => setTimeout(resolve, 2000)) } // Now let's examine our organized UTXOs console.log('\nExamining basket organization...') console.log('Note: OP_RETURN outputs show spendable: false because they are data-only outputs') const baskets = ['high-priority', 'medium-priority', 'low-priority'] for (const basketName of baskets) { const { outputs, totalOutputs } = await wallet.listOutputs({ basket: basketName, includeTags: true, includeCustomInstructions: true, limit: 10 }) console.log(`\n${basketName.toUpperCase()} Basket:`) console.log(` Total outputs: ${totalOutputs}`) if (totalOutputs === 0) { console.log(` Note: No outputs found. This could be because:`) console.log(` - Outputs need confirmation time`) console.log(` - Wallet needs time to process new baskets`) console.log(` - OP_RETURN outputs might not be tracked as spendable UTXOs`) } outputs.forEach((output, index) => { console.log(` Output ${index + 1}:`) console.log(` Outpoint: ${output.outpoint}`) console.log(` Amount: ${output.satoshis} satoshis`) console.log(` Spendable: ${output.spendable}`) if (output.tags) { console.log(` Tags: ${output.tags.join(', ')}`) } if (output.customInstructions) { console.log(` Instructions: ${output.customInstructions}`) } }) } // Add processing delay await new Promise(resolve => setTimeout(resolve, 5000)); } catch (error: unknown) { console.error('Error:', error) } } advancedBasketManagement().catch(console.error) ``` ## Transaction Chaining and Dependencies Sometimes you need to create transactions that depend on previous ones. Here's how to handle transaction chaining: ```typescript import { WalletClient } from '@bsv/sdk' async function createTransactionChain() { try { const wallet = new WalletClient('auto', 'localhost') const { authenticated } = await wallet.isAuthenticated() if (!authenticated) { await wallet.waitForAuthentication() } console.log('Creating transaction chain...') console.log('Transaction chaining allows you to create a series of transactions that depend on each other.') console.log('This is useful for scenarios where you need to ensure that a transaction is only processed after a previous one has been confirmed.') console.log('In this example, we will create two transactions: the first one creates an output, and the second one spends that output.') // First transaction - creates outputs for the chain const firstTx = await wallet.createAction({ description: 'Chain starter transaction', outputs: [ { satoshis: 400, lockingScript: '006a0d436861696e207374617274657220', // OP_RETURN "Chain starter" outputDescription: 'Chain starter output', basket: 'chain-demo', tags: ['chain', 'step-1'] } ], options: { acceptDelayedBroadcast: true // Allow some delay for better fee optimization } }) if (!firstTx.txid) { console.log('First transaction failed') return } console.log(`First transaction: ${firstTx.txid}`) // Wait a moment for the transaction to be processed await new Promise(resolve => setTimeout(resolve, 2000)) // Second transaction - demonstrates chaining with sendWith option // Note: We use automatic input selection rather than manual specification // Manual input specification requires unlockingScript and unlockingScriptLength // which adds complexity for tutorial purposes const secondTx = await wallet.createAction({ description: 'Chain continuation transaction', outputs: [ { satoshis: 150, lockingScript: '006a0c436861696e20636f6e74696e756174696f6e', // OP_RETURN "Chain continuation" outputDescription: 'Chain continuation output', basket: 'chain-demo', tags: ['chain', 'step-2'] } ], options: { sendWith: [firstTx.txid] // Ensure this transaction is sent with the first one } }) if (secondTx.txid) { console.log(`Second transaction: ${secondTx.txid}`) console.log('Transaction chain completed successfully') } } catch (error: unknown) { console.error('Error in transaction chain:', error) } } createTransactionChain().catch(console.error) ``` ## Performance Optimization Strategies For high-throughput applications, consider these optimization techniques: ```typescript import { WalletClient } from '@bsv/sdk' async function optimizedTransactionPatterns() { try { const wallet = new WalletClient('auto', 'localhost') const { authenticated } = await wallet.isAuthenticated() if (!authenticated) { await wallet.waitForAuthentication() } // Strategy 1: Pre-allocate UTXOs for different purposes console.log('Pre-allocating UTXOs for optimal performance...') const allocationTx = await wallet.createAction({ description: 'UTXO pre-allocation for performance', outputs: [ // Create multiple small UTXOs for frequent operations ...Array(5).fill(null).map((_, i) => ({ satoshis: 200, lockingScript: `006a0f507265616c6c6f636174656420${i.toString(16).padStart(2, '0')}`, // "Preallocated" + index outputDescription: `Pre-allocated UTXO ${i + 1}`, basket: 'pre-allocated', tags: ['performance', 'ready-to-use'] })) ], options: { acceptDelayedBroadcast: true } }) if (allocationTx.txid) { console.log(`Pre-allocation transaction: ${allocationTx.txid}`) } // Strategy 2: Batch multiple operations efficiently await new Promise(resolve => setTimeout(resolve, 3000)) const operations = [ { type: 'data', content: 'Operation 1' }, { type: 'data', content: 'Operation 2' }, { type: 'data', content: 'Operation 3' } ] const batchOutputs = operations.map((op, index) => { const dataHex = Buffer.from(op.content).toString('hex') const dataLength = Buffer.from(op.content).length const dataLengthHex = dataLength.toString(16).padStart(2, '0') return { satoshis: 100, lockingScript: `006a${dataLengthHex}${dataHex}`, outputDescription: `Batch operation ${index + 1}`, basket: 'operations', tags: ['batch', `op-${index + 1}`] } }) // Use pre-allocated UTXOs for the batch const { outputs: preAllocated } = await wallet.listOutputs({ basket: 'pre-allocated', limit: 3 }) console.log(`Found ${preAllocated.length} pre-allocated UTXOs`) if (preAllocated.length > 0) { // Let the wallet automatically select inputs instead of manual specification const batchTx = await wallet.createAction({ description: 'Optimized batch operation', outputs: batchOutputs, options: { randomizeOutputs: false, // Maintain order for processing acceptDelayedBroadcast: true } }) if (batchTx.txid) { console.log(`Optimized batch transaction: ${batchTx.txid}`) console.log(`Processed ${operations.length} operations efficiently`) } } } catch (error: unknown) { console.error('Optimization error:', error) } } optimizedTransactionPatterns().catch(console.error) ``` ## Conclusion You've now mastered advanced transaction construction with the BSV TypeScript SDK's `WalletClient`. You can: - Create sophisticated multi-input/multi-output transactions - Implement robust error handling and retry strategies - Optimize transaction patterns for performance - Use advanced `WalletClient` features like baskets, tags, and options - Handle complex scenarios like transaction chaining and batch processing These techniques enable you to build production-ready applications that efficiently manage Bitcoin transactions while maintaining reliability and performance. ## Next Steps - Review the [Transaction Signing Methods](../guides/transaction-signing-methods.md) for custom signing scenarios - Check out specialized tutorials for your specific use case - Advanced transaction construction requires robust error handling for production applications. For comprehensive coverage of error handling patterns, retry mechanisms, and recovery strategies, see the dedicated [Error Handling Tutorial](./error-handling.md). ## Additional Resources - [Wallet Reference](../reference/wallet.md) - [BSV Blockchain Documentation](https://docs.bsvblockchain.org/) - [MetaNet Desktop Wallet](https://metanet.bsvb.tech/)