ts-sdk

Debugging Reference

Complete guide for debugging, logging, and troubleshooting applications built with the BSV TypeScript SDK.

Debug Mode Activation

Environment Variables

# Enable debug mode
export BSV_DEBUG=true

# Set debug level
export BSV_DEBUG_LEVEL=debug

# Enable specific debug categories
export BSV_DEBUG_CATEGORIES=wallet,transaction,network

# Enable performance debugging
export BSV_DEBUG_PERFORMANCE=true

Programmatic Debug Configuration

import { SDKConfigBuilder } from '@bsv/sdk'

const debugConfig = new SDKConfigBuilder()
  .logging({
    level: 'debug',
    outputs: [
      { type: 'console', config: {} },
      { type: 'file', config: { file: './debug.log' } }
    ],
    format: {
      timestamp: true,
      level: true,
      component: true,
      structured: true,
      colors: true
    },
    performance: {
      enabled: true,
      threshold: 100, // Log operations taking >100ms
      includeStackTrace: true
    }
  })
  .build()

SDK Logging System

Log Levels

enum LogLevel {
  ERROR = 0,   // Critical errors only
  WARN = 1,    // Warnings and errors
  INFO = 2,    // General information
  DEBUG = 3,   // Detailed debugging info
  TRACE = 4    // Extremely verbose tracing
}

Logger Interface

interface Logger {
  error(message: string, context?: any): void
  warn(message: string, context?: any): void
  info(message: string, context?: any): void
  debug(message: string, context?: any): void
  trace(message: string, context?: any): void
  
  // Performance logging
  time(label: string): void
  timeEnd(label: string): void
  
  // Structured logging
  log(level: LogLevel, message: string, context: any): void
}

Creating Custom Loggers

class CustomLogger implements Logger {
  private level: LogLevel = LogLevel.INFO
  
  constructor(level: LogLevel = LogLevel.INFO) {
    this.level = level
  }
  
  error(message: string, context?: any): void {
    if (this.level >= LogLevel.ERROR) {
      console.error(`[ERROR] ${new Date().toISOString()} ${message}`, context)
    }
  }
  
  warn(message: string, context?: any): void {
    if (this.level >= LogLevel.WARN) {
      console.warn(`[WARN] ${new Date().toISOString()} ${message}`, context)
    }
  }
  
  info(message: string, context?: any): void {
    if (this.level >= LogLevel.INFO) {
      console.info(`[INFO] ${new Date().toISOString()} ${message}`, context)
    }
  }
  
  debug(message: string, context?: any): void {
    if (this.level >= LogLevel.DEBUG) {
      console.debug(`[DEBUG] ${new Date().toISOString()} ${message}`, context)
    }
  }
  
  trace(message: string, context?: any): void {
    if (this.level >= LogLevel.TRACE) {
      console.trace(`[TRACE] ${new Date().toISOString()} ${message}`, context)
    }
  }
  
  time(label: string): void {
    console.time(label)
  }
  
  timeEnd(label: string): void {
    console.timeEnd(label)
  }
  
  log(level: LogLevel, message: string, context: any): void {
    const methods = [this.error, this.warn, this.info, this.debug, this.trace]
    methods[level]?.call(this, message, context)
  }
}

Debug Categories

Wallet Debugging

// Enable wallet debugging
const walletDebugger = {
  logConnection: (substrate: string, status: string) => {
    console.debug(`[WALLET] Connection ${status} to ${substrate}`)
  },
  
  logAction: (action: string, args: any) => {
    console.debug(`[WALLET] Action: ${action}`, {
      args: JSON.stringify(args, null, 2),
      timestamp: new Date().toISOString()
    })
  },
  
  logError: (error: any, context: any) => {
    console.error(`[WALLET] Error:`, {
      error: error.message,
      code: error.code,
      context,
      stack: error.stack
    })
  }
}

// Usage in wallet operations
try {
  walletDebugger.logAction('createAction', { description: 'Test transaction' })
  const result = await wallet.createAction({
    description: 'Test transaction',
    outputs: [{ satoshis: 100, lockingScript: '...' }]
  })
  walletDebugger.logConnection('substrate', 'success')
} catch (error) {
  walletDebugger.logError(error, { operation: 'createAction' })
}

Transaction Debugging

class TransactionDebugger {
  static logConstruction(tx: Transaction): void {
    console.debug(`[TRANSACTION] Construction:`, {
      inputs: tx.inputs.length,
      outputs: tx.outputs.length,
      totalInput: tx.inputs.reduce((sum, input) => sum + input.satoshis, 0),
      totalOutput: tx.outputs.reduce((sum, output) => sum + output.satoshis, 0),
      fee: tx.getFee(),
      size: tx.toHex().length / 2,
      txid: Buffer.from(tx.id()).toString('hex')
    })
  }
  
  static logSigning(tx: Transaction, inputIndex: number): void {
    console.debug(`[TRANSACTION] Signing input ${inputIndex}:`, {
      txid: Buffer.from(tx.id()).toString('hex'),
      input: {
        sourceTXID: tx.inputs[inputIndex].sourceTXID,
        sourceOutputIndex: tx.inputs[inputIndex].sourceOutputIndex,
        unlockingScript: tx.inputs[inputIndex].unlockingScript?.toASM()
      }
    })
  }
  
  static logValidation(tx: Transaction, isValid: boolean, errors?: string[]): void {
    console.debug(`[TRANSACTION] Validation:`, {
      txid: Buffer.from(tx.id()).toString('hex'),
      valid: isValid,
      errors: errors || [],
      timestamp: new Date().toISOString()
    })
  }
}

Network Debugging

class NetworkDebugger {
  static logRequest(endpoint: string, method: string, data?: any): void {
    console.debug(`[NETWORK] Request:`, {
      endpoint,
      method,
      data: data ? JSON.stringify(data) : undefined,
      timestamp: new Date().toISOString()
    })
  }
  
  static logResponse(endpoint: string, status: number, data?: any): void {
    console.debug(`[NETWORK] Response:`, {
      endpoint,
      status,
      dataSize: data ? JSON.stringify(data).length : 0,
      timestamp: new Date().toISOString()
    })
  }
  
  static logError(endpoint: string, error: any): void {
    console.error(`[NETWORK] Error:`, {
      endpoint,
      error: error.message,
      code: error.code,
      stack: error.stack,
      timestamp: new Date().toISOString()
    })
  }
}

Transaction Inspection Tools

Transaction Analyzer

class TransactionAnalyzer {
  static analyze(tx: Transaction): TransactionAnalysis {
    const analysis: TransactionAnalysis = {
      basic: this.analyzeBasic(tx),
      inputs: this.analyzeInputs(tx),
      outputs: this.analyzeOutputs(tx),
      scripts: this.analyzeScripts(tx),
      fees: this.analyzeFees(tx),
      size: this.analyzeSize(tx)
    }
    
    return analysis
  }
  
  private static analyzeBasic(tx: Transaction): BasicAnalysis {
    return {
      txid: Buffer.from(tx.id()).toString('hex'),
      version: tx.version,
      lockTime: tx.lockTime,
      inputCount: tx.inputs.length,
      outputCount: tx.outputs.length
    }
  }
  
  private static analyzeInputs(tx: Transaction): InputAnalysis[] {
    return tx.inputs.map((input, index) => ({
      index,
      sourceTXID: input.sourceTXID,
      sourceOutputIndex: input.sourceOutputIndex,
      satoshis: input.satoshis,
      unlockingScript: {
        asm: input.unlockingScript?.toASM(),
        hex: input.unlockingScript?.toHex(),
        size: input.unlockingScript?.toHex().length / 2
      },
      sequence: input.sequence
    }))
  }
  
  private static analyzeOutputs(tx: Transaction): OutputAnalysis[] {
    return tx.outputs.map((output, index) => ({
      index,
      satoshis: output.satoshis,
      lockingScript: {
        asm: output.lockingScript.toASM(),
        hex: output.lockingScript.toHex(),
        size: output.lockingScript.toHex().length / 2,
        type: this.detectScriptType(output.lockingScript)
      }
    }))
  }
  
  private static detectScriptType(script: Script): string {
    const asm = script.toASM()
    
    if (asm.includes('OP_DUP OP_HASH160') && asm.includes('OP_EQUALVERIFY OP_CHECKSIG')) {
      return 'P2PKH'
    } else if (asm.includes('OP_HASH160') && asm.includes('OP_EQUAL')) {
      return 'P2SH'
    } else if (asm.startsWith('OP_RETURN')) {
      return 'OP_RETURN'
    } else {
      return 'CUSTOM'
    }
  }
}

interface TransactionAnalysis {
  basic: BasicAnalysis
  inputs: InputAnalysis[]
  outputs: OutputAnalysis[]
  scripts: ScriptAnalysis
  fees: FeeAnalysis
  size: SizeAnalysis
}

Script Debugger

class ScriptDebugger {
  static debugScript(script: Script): ScriptDebugInfo {
    const chunks = script.chunks
    const debugInfo: ScriptDebugInfo = {
      chunks: [],
      opcodes: [],
      data: [],
      analysis: {
        isValid: true,
        errors: [],
        warnings: []
      }
    }
    
    chunks.forEach((chunk, index) => {
      if (chunk.opcode !== undefined) {
        debugInfo.opcodes.push({
          index,
          opcode: chunk.opcode,
          name: this.getOpcodeName(chunk.opcode),
          description: this.getOpcodeDescription(chunk.opcode)
        })
      }
      
      if (chunk.data) {
        debugInfo.data.push({
          index,
          data: chunk.data,
          hex: Buffer.from(chunk.data).toString('hex'),
          size: chunk.data.length
        })
      }
    })
    
    return debugInfo
  }
  
  private static getOpcodeName(opcode: number): string {
    const opcodeMap: Record<number, string> = {
      0: 'OP_0',
      76: 'OP_PUSHDATA1',
      77: 'OP_PUSHDATA2',
      78: 'OP_PUSHDATA4',
      79: 'OP_1NEGATE',
      81: 'OP_1',
      82: 'OP_2',
      // ... add more opcodes as needed
      118: 'OP_DUP',
      169: 'OP_HASH160',
      136: 'OP_EQUALVERIFY',
      172: 'OP_CHECKSIG'
    }
    
    return opcodeMap[opcode] || `OP_UNKNOWN_${opcode}`
  }
}

Performance Debugging

Performance Monitor

class PerformanceMonitor {
  private static timers = new Map<string, number>()
  private static metrics = new Map<string, PerformanceMetric>()
  
  static startTimer(label: string): void {
    this.timers.set(label, performance.now())
  }
  
  static endTimer(label: string): number {
    const startTime = this.timers.get(label)
    if (!startTime) {
      console.warn(`Timer '${label}' was not started`)
      return 0
    }
    
    const duration = performance.now() - startTime
    this.timers.delete(label)
    
    // Update metrics
    const metric = this.metrics.get(label) || {
      count: 0,
      totalTime: 0,
      avgTime: 0,
      minTime: Infinity,
      maxTime: 0
    }
    
    metric.count++
    metric.totalTime += duration
    metric.avgTime = metric.totalTime / metric.count
    metric.minTime = Math.min(metric.minTime, duration)
    metric.maxTime = Math.max(metric.maxTime, duration)
    
    this.metrics.set(label, metric)
    
    console.debug(`[PERFORMANCE] ${label}: ${duration.toFixed(2)}ms`)
    return duration
  }
  
  static getMetrics(): Map<string, PerformanceMetric> {
    return new Map(this.metrics)
  }
  
  static resetMetrics(): void {
    this.metrics.clear()
    this.timers.clear()
  }
}

interface PerformanceMetric {
  count: number
  totalTime: number
  avgTime: number
  minTime: number
  maxTime: number
}

Memory Usage Monitoring

class MemoryMonitor {
  static logMemoryUsage(label: string): void {
    if (typeof process !== 'undefined' && process.memoryUsage) {
      const usage = process.memoryUsage()
      console.debug(`[MEMORY] ${label}:`, {
        rss: `${Math.round(usage.rss / 1024 / 1024)}MB`,
        heapTotal: `${Math.round(usage.heapTotal / 1024 / 1024)}MB`,
        heapUsed: `${Math.round(usage.heapUsed / 1024 / 1024)}MB`,
        external: `${Math.round(usage.external / 1024 / 1024)}MB`
      })
    } else if (typeof performance !== 'undefined' && (performance as any).memory) {
      const memory = (performance as any).memory
      console.debug(`[MEMORY] ${label}:`, {
        used: `${Math.round(memory.usedJSHeapSize / 1024 / 1024)}MB`,
        total: `${Math.round(memory.totalJSHeapSize / 1024 / 1024)}MB`,
        limit: `${Math.round(memory.jsHeapSizeLimit / 1024 / 1024)}MB`
      })
    }
  }
}

Debug Utilities

Hex Dump Utility

class HexDump {
  static dump(data: Uint8Array | number[], bytesPerLine: number = 16): string {
    const bytes = Array.from(data)
    let result = ''
    
    for (let i = 0; i < bytes.length; i += bytesPerLine) {
      const line = bytes.slice(i, i + bytesPerLine)
      const offset = i.toString(16).padStart(8, '0')
      const hex = line.map(b => b.toString(16).padStart(2, '0')).join(' ')
      const ascii = line.map(b => (b >= 32 && b <= 126) ? String.fromCharCode(b) : '.').join('')
      
      result += `${offset}: ${hex.padEnd(bytesPerLine * 3 - 1)} |${ascii}|\n`
    }
    
    return result
  }
  
  static logDump(data: Uint8Array | number[], label: string): void {
    console.debug(`[HEX DUMP] ${label}:\n${this.dump(data)}`)
  }
}

Network Request Inspector

class NetworkInspector {
  private static requests = new Map<string, NetworkRequest>()
  
  static startRequest(id: string, url: string, method: string, data?: any): void {
    this.requests.set(id, {
      id,
      url,
      method,
      data,
      startTime: Date.now(),
      status: 'pending'
    })
    
    console.debug(`[NETWORK] Starting request ${id}:`, {
      url,
      method,
      dataSize: data ? JSON.stringify(data).length : 0
    })
  }
  
  static endRequest(id: string, status: number, response?: any): void {
    const request = this.requests.get(id)
    if (!request) return
    
    const duration = Date.now() - request.startTime
    request.status = 'completed'
    request.duration = duration
    request.response = response
    
    console.debug(`[NETWORK] Completed request ${id}:`, {
      status,
      duration: `${duration}ms`,
      responseSize: response ? JSON.stringify(response).length : 0
    })
    
    // Clean up old requests
    setTimeout(() => this.requests.delete(id), 60000)
  }
  
  static failRequest(id: string, error: any): void {
    const request = this.requests.get(id)
    if (!request) return
    
    const duration = Date.now() - request.startTime
    request.status = 'failed'
    request.duration = duration
    request.error = error
    
    console.error(`[NETWORK] Failed request ${id}:`, {
      error: error.message,
      duration: `${duration}ms`
    })
  }
  
  static getActiveRequests(): NetworkRequest[] {
    return Array.from(this.requests.values()).filter(r => r.status === 'pending')
  }
}

interface NetworkRequest {
  id: string
  url: string
  method: string
  data?: any
  startTime: number
  duration?: number
  status: 'pending' | 'completed' | 'failed'
  response?: any
  error?: any
}

Debug Configuration Examples

Development Debug Setup

// Enable comprehensive debugging for development
const devDebugConfig = {
  logging: {
    level: 'debug' as LogLevel,
    outputs: [
      { type: 'console', config: { colors: true } },
      { type: 'file', config: { file: './dev-debug.log' } }
    ],
    format: {
      timestamp: true,
      level: true,
      component: true,
      structured: true,
      colors: true
    },
    performance: {
      enabled: true,
      threshold: 50,
      includeStackTrace: true
    }
  }
}

Production Debug Setup

// Minimal debugging for production
const prodDebugConfig = {
  logging: {
    level: 'warn' as LogLevel,
    outputs: [
      { type: 'file', config: { file: '/var/log/bsv-app.log', rotation: true } }
    ],
    format: {
      timestamp: true,
      level: true,
      component: false,
      structured: true,
      colors: false
    },
    performance: {
      enabled: true,
      threshold: 1000,
      includeStackTrace: false
    }
  }
}

Testing Debug Setup

// Debug configuration for testing
const testDebugConfig = {
  logging: {
    level: 'trace' as LogLevel,
    outputs: [
      { type: 'console', config: {} }
    ],
    format: {
      timestamp: false,
      level: true,
      component: true,
      structured: false,
      colors: false
    },
    performance: {
      enabled: false
    }
  }
}

Troubleshooting Common Issues

Debug Checklist

  1. Enable appropriate logging level
    // Set debug level based on issue severity
    const config = { logging: { level: 'debug' } }
    
  2. Check network connectivity
    NetworkInspector.startRequest('health-check', 'https://api.whatsonchain.com/v1/bsv/main/chain/info', 'GET')
    
  3. Validate transaction structure
    const analysis = TransactionAnalyzer.analyze(transaction)
    console.log('Transaction Analysis:', analysis)
    
  4. Monitor performance
    PerformanceMonitor.startTimer('wallet-operation')
    // ... perform operation
    PerformanceMonitor.endTimer('wallet-operation')
    
  5. Check memory usage
    MemoryMonitor.logMemoryUsage('before-operation')
    // ... perform operation
    MemoryMonitor.logMemoryUsage('after-operation')
    

This comprehensive debugging reference provides developers with all the tools and techniques needed to effectively debug and troubleshoot applications built with the BSV TypeScript SDK.