Skip to main content

Requirements

The examples in this guide call the debug JSON-RPC endpoints directly with curl. You do not need an SDK or other libraries. To pretty-print the JSON responses, you can install jq:
Debug tracing is your primary tool to understand how EVM transactions execute on Sei. With it, you can analyze transaction flow, optimize gas usage, debug smart contract interactions, and troubleshoot production issues.

What you can trace

Transaction execution

  • Step-by-step opcode execution
  • Contract call hierarchy
  • Gas consumption breakdown
  • State changes and storage access

Contract interactions

  • Cross-contract calls and returns
  • Event emission analysis
  • Precompile usage tracking
  • External library calls

Performance analysis

  • Gas optimization opportunities
  • Bottleneck identification
  • Cache hit and miss patterns
  • State access efficiency

Security analysis

  • Suspicious operation detection
  • Reentrancy pattern analysis
  • Access control verification
  • Vulnerability scanning

Available tracing methods

debug_traceStateAccess respects the node’s configured TraceTimeout. If the trace deadline elapses during replay or serialization, the call aborts with a context deadline exceeded error instead of running unbounded. Additionally, the per-module state-access log is capped at 4 MiB of retained key/value payload. When a module exceeds this cap, its trace dump sets "truncated": true: the reads/has fields then reflect only the retained prefix, while the stats (operation counts and durations) remain complete and accurate.
| debug_traceTransactionProfile | Trace plus timing/store-access profiling | Latency breakdown and DB-access analysis |

Transaction analysis example

This example traces an ERC-20 transfer transaction:
Response:

Debugging failed transactions

Use these steps to analyze and fix transaction failures:

Step 1: Identify the problem

Step 2: Trace the execution

Response:

Step 3: Fix and test

Common debugging scenarios

Transaction reverted

Problem: The transaction failed with a revert. Solution: Use callTracer to find the exact revert reason.

Out of gas

Problem: The transaction ran out of gas. Solution: Use the gas analysis tracer to optimize gas usage.

Unexpected behavior

Problem: The transaction succeeded, but the result is wrong. Solution: Use the opcode tracer for step-by-step analysis.

Slow performance

Problem: The transaction uses too much gas. Solution: Use the state access tracer to find inefficiencies.

Quick reference

Essential commands

Common tracers

  • callTracer: Contract call hierarchy
  • opcodeTracer: Opcode-level execution
  • Custom JS: Custom analysis logic

Pre-baked trace cache

RPC nodes can optionally pre-compute and cache debug_trace* results in the background. The node then serves trace requests from a local on-disk cache instead of re-executing the block live on every call. You configure this opt-in feature through [evm] fields in app.toml. It is recommended for RPC nodes only. When the feature is enabled, a background worker re-executes each committed block with the configured tracers. It stores the results in a Pebble database at <home>/data/trace_db. These methods serve results from this cache on a cache hit. Otherwise, they fall through to live re-execution:
  • debug_traceTransaction
  • debug_traceBlockByNumber and debug_traceBlockByHash

When the cache is used

The node serves a request from the cache only when trace baking is enabled and the request uses a bakeable tracer configuration:
  • The tracer is one of callTracer, prestateTracer, or flatCallTracer.
  • The request does not include a custom tracerConfig. A per-call tracerConfig (for example, {"withLog": true}) is not part of the cache key. Any custom tracer config makes the request un-bakeable, so it falls through to live re-execution.
Requests that use the struct logger (no tracer), a JavaScript tracer, or any other named tracer always run live.

Configuration

These [evm] fields in app.toml control trace baking:
Trace baking adds a persistent on-disk store at <home>/data/trace_db and increases disk usage. The node flushes the store’s write-ahead log when it shuts down cleanly.

Tracer allowlist

Caller-supplied tracer values on the debug_trace* endpoints are gated by [evm] config in app.toml. By default, only a fixed set of native geth tracers may be requested, and request-supplied JavaScript tracer source is rejected. This deviates from upstream geth, which accepts JavaScript tracers by default. The gate applies to debug_traceCall, debug_traceTransaction, debug_traceBlockByNumber/debug_traceBlockByHash, and debug_traceTransactionProfile. When no tracer is supplied, the default struct logger remains available. Behavior notes:
  • A tracer name that is not listed in trace_allowed_tracers is rejected, unless it is JavaScript source and trace_allow_js_tracers is enabled.
  • JavaScript tracer source is rejected unless trace_allow_js_tracers is set to true.
  • When muxTracer is requested, its nested tracer names are validated recursively against the same allowlist, with a bounded nesting depth of 16.
  • trace_bake_tracers names are held to the same native-only rule and validated at startup, so a non-native or misspelled baked tracer fails startup instead of being evaluated as JavaScript source on every block.
The trace_bake_tracers note above that eligible values are standard named tracers is subsumed by this native-only startup validation: entries must be native tracer names such as callTracer, prestateTracer, or flatCallTracer.

Struct-logger output cap

The default debug_trace* endpoints (debug_traceCall, debug_traceTransaction, and debug_traceBlockByNumber/debug_traceBlockByHash) use the built-in struct logger when no custom tracer is supplied. Traces that read many distinct storage slots can retain large amounts of output, so the node bounds the retained struct-logger output per traced transaction. The max_trace_struct_log_bytes field under [evm] in app.toml controls this cap: Behavior notes:
  • The bound is applied per transaction, not per RPC call. Because geth builds a fresh struct logger for each transaction, a debug_traceBlock* call over N transactions can retain up to N times this value (and the parallelized block-trace path holds several concurrent traces live).
  • A caller-supplied Limit larger than max_trace_struct_log_bytes is clamped down to the configured value.
  • A smaller caller-supplied Limit is honored unchanged.
  • Custom tracers (for example callTracer, prestateTracer, or JavaScript tracers) are unaffected, as are requests when the cap is disabled (0).

Removed legacy trace filters

The legacy *ExcludeTraceFail endpoints have been removed. For block tracing, use debug_traceBlockByNumber or debug_traceBlockByHash. For EVM receipts, use eth_getTransactionReceipt. There is no block or filter method to discover synthetic logs from Cosmos-originated transactions. If you already know a synthetic transaction hash, enable sei_getTransactionReceipt to get its receipt and logs.

Next steps

  1. JavaScript tracers: Custom analysis scripts
  2. Troubleshooting: Common issues and solutions
Start with callTracer for general debugging. Then use specialized tracers for specific analysis needs.