Skip to main content
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.