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 likegetPC()
,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 withgasUsed
, transaction infodb
: 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
- Start Simple: Begin with basic tracers before adding complexity
- Limit Data Collection: Only collect necessary information
- Use Efficient Data Structures: Prefer counters over arrays when possible
- Set Appropriate Timeouts: Balance thoroughness with performance
- Test Incrementally: Validate tracers on simple transactions first
- 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