Skip to Content
EVMDebug TracingJavaScript Tracers

JavaScript Tracers

JavaScript tracers allow you to create custom debugging logic for analyzing EVM transactions on Sei. This powerful feature enables sophisticated analysis patterns beyond what built-in tracers provide.

Interface Overview

Every JavaScript tracer follows this structure:

{ "tracer": `{ // Initialize variables data: [], // Called for each opcode execution step: function(log, db) { // Your analysis logic here }, // Called when execution fails (optional) fault: function(log, db) { return {}; }, // Called at the end to return results result: function(ctx, db) { return { // Your analysis results }; } }` }

Core Methods

step(log, db)

Called for each EVM opcode execution. This is where your main analysis logic goes.

Parameters:

  • log: Current execution context with methods like getPC(), op.toString(), getCost()
  • db: Database interface for querying state

Gas Cost: Varies by tracer complexity

result(ctx, db)

Called at the end of execution to return your analysis results.

Parameters:

  • ctx: Execution context with gasUsed, transaction info
  • db: Database interface for final queries

Advanced Tracer Examples

State Change Tracker

Monitor all state modifications with detailed analysis:

{ "tracer": `{ stateChanges: {}, step: function(log, db) { if (log.op.toString() == "SSTORE") { var key = log.stack.peek(0).toString(16); var value = log.stack.peek(1).toString(16); var address = log.contract.getAddress().toString(); if (!this.stateChanges[address]) { this.stateChanges[address] = {}; } this.stateChanges[address][key] = { oldValue: db.getState(address, key), newValue: value, pc: log.getPC() }; } }, result: function(ctx, db) { return { modifiedAccounts: Object.keys(this.stateChanges).length, stateChanges: this.stateChanges }; } }` }

DeFi Transaction Analysis

Analyze complex DeFi transactions with multiple contract interactions:

{ "tracer": `{ transfers: [], step: function(log, db) { // Track ERC20 Transfer events (LOG3 with Transfer signature) if (log.op.toString() == "LOG3") { var topic0 = log.stack.peek(2).toString(16); // ERC20 Transfer event signature if (topic0 == "ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") { this.transfers.push({ from: log.stack.peek(3).toString(16), to: log.stack.peek(4).toString(16), value: log.memory.slice(log.stack.peek(0), log.stack.peek(1)), contract: log.contract.getAddress().toString() }); } } }, result: function(ctx, db) { return { totalTransfers: this.transfers.length, transfers: this.transfers, gasEfficiency: this.transfers.length > 0 ? ctx.gasUsed / this.transfers.length : 0 }; } }` }

Security Analysis Tracer

Detect suspicious patterns and potential vulnerabilities:

{ "tracer": `{ suspiciousOps: [], externalCalls: 0, selfDestructs: 0, step: function(log, db) { var op = log.op.toString(); // Track external calls if (op == "CALL" || op == "DELEGATECALL" || op == "STATICCALL") { this.externalCalls++; // Flag suspicious delegate calls if (op == "DELEGATECALL") { this.suspiciousOps.push({ type: "DELEGATECALL", target: log.stack.peek(1).toString(16), pc: log.getPC() }); } } // Track self-destructs if (op == "SELFDESTRUCT") { this.selfDestructs++; this.suspiciousOps.push({ type: "SELFDESTRUCT", beneficiary: log.stack.peek(0).toString(16), pc: log.getPC() }); } }, result: function(ctx, db) { return { riskScore: this.suspiciousOps.length * 10 + this.selfDestructs * 50, externalCalls: this.externalCalls, suspiciousOperations: this.suspiciousOps, recommendations: this.suspiciousOps.length > 0 ? ["Review delegate calls", "Verify contract security"] : ["Transaction appears safe"] }; } }` }

Gas Optimization Tracer

Identify expensive operations for gas optimization:

{ "tracer": `{ expensive: [], threshold: 1000, // Gas cost threshold step: function(log, db) { var cost = log.getCost(); if (cost > this.threshold) { this.expensive.push({ pc: log.getPC(), op: log.op.toString(), cost: cost, gas: log.getGas() }); } }, result: function(ctx, db) { // Sort by cost descending this.expensive.sort((a, b) => b.cost - a.cost); return { expensiveOperations: this.expensive.slice(0, 20), totalExpensiveCost: this.expensive.reduce((sum, op) => sum + op.cost, 0), optimizationPotential: this.expensive.length > 0 }; } }` }

Performance Optimization

⚠️
Performance: JavaScript tracers can significantly slow down tracing. Optimize your tracer code and use timeouts for production use.

Memory-Efficient Patterns

// ✅ Efficient - Use summary data { "tracer": `{ summary: { totalGas: 0, operationCounts: {}, maxStackDepth: 0 }, step: function(log, db) { // Update counters instead of storing arrays this.summary.totalGas += log.getCost(); var op = log.op.toString(); this.summary.operationCounts[op] = (this.summary.operationCounts[op] || 0) + 1; var stackDepth = log.stack.length(); if (stackDepth > this.summary.maxStackDepth) { this.summary.maxStackDepth = stackDepth; } }, result: function(ctx, db) { return this.summary; } }` }

Selective Operation Tracking

{ "tracer": `{ data: [], counter: 0, maxEntries: 1000, // Limit data collection step: function(log, db) { // Only track specific operations var op = log.op.toString(); if ((op == "SSTORE" || op == "SLOAD") && this.counter < this.maxEntries) { this.data.push([ log.getPC(), op, log.getCost() ]); this.counter++; } }, result: function(ctx, db) { return { operations: this.data, truncated: this.counter >= this.maxEntries }; } }` }

Configuration Options

Basic Configuration

{ "tracer": "your-javascript-tracer", "tracerConfig": { "timeout": "30s", "enableMemory": false, "enableStack": false, "enableStorage": false, "enableReturnData": true } }

Production Settings

{ "tracerConfig": { "timeout": "60s", // Longer timeout for complex traces "enableMemory": false, // Disable to save memory "enableStack": false, // Disable to save memory "enableStorage": true, // Enable only if needed "enableReturnData": true // Usually safe to enable } }

Practical Integration Examples

Smart Contract Debugger

contract TransactionAnalyzer { struct TraceResult { uint256 gasUsed; uint256 operationCount; bool hasErrors; } event TransactionTraced( bytes32 indexed txHash, uint256 gasUsed, uint256 operationCount ); function analyzeTransaction( bytes32 txHash, string memory tracerCode ) external { // Off-chain: Use the tracer to analyze the transaction // On-chain: Process and store the results // This would typically be called by an oracle or off-chain service // that performs the tracing and submits results } }

DeFi Analytics Dashboard

// Off-chain analytics service class DeFiAnalytics { async analyzeSwap(txHash) { const swapTracer = `{ swaps: [], step: function(log, db) { if (log.op.toString() == "LOG3") { // Detect swap events var topic0 = log.stack.peek(2).toString(16); if (topic0 == "swap_event_signature") { this.swaps.push({ contract: log.contract.getAddress().toString(), // ... extract swap data }); } } }, result: function(ctx, db) { return { swaps: this.swaps }; } }`; const result = await this.traceTransaction(txHash, swapTracer); return this.processSwapData(result); } }

Best Practices

  1. Start Simple: Begin with basic tracers before adding complexity
  2. Limit Data Collection: Only collect necessary information
  3. Use Efficient Data Structures: Prefer counters over arrays when possible
  4. Set Appropriate Timeouts: Balance thoroughness with performance
  5. Test Incrementally: Validate tracers on simple transactions first
  6. Handle Errors Gracefully: Include error handling in your tracers

Common Use Cases

Gas Profiling

const gasProfiler = `{ profile: {}, step: function(log, db) { var op = log.op.toString(); var cost = log.getCost(); if (!this.profile[op]) { this.profile[op] = { count: 0, totalCost: 0 }; } this.profile[op].count++; this.profile[op].totalCost += cost; }, result: function(ctx, db) { // Calculate averages for (var op in this.profile) { this.profile[op].avgCost = this.profile[op].totalCost / this.profile[op].count; } return this.profile; } }`;

Event Extraction

const eventExtractor = `{ events: [], step: function(log, db) { var op = log.op.toString(); if (op.startsWith("LOG")) { this.events.push({ address: log.contract.getAddress().toString(), topics: this.extractTopics(log), data: this.extractData(log) }); } }, extractTopics: function(log) { // Implementation depends on log structure return []; }, result: function(ctx, db) { return { events: this.events }; } }`;
Need Help? If you encounter issues with JavaScript tracers, check out our Troubleshooting Guide for common solutions and debugging tips.
Last updated on