debug_traceTransaction: Full Execution Traces
debug_traceTransaction•Trace method•Ethereum JSON-RPCdebug_traceTransaction re-executes a mined transaction exactly as it ran on chain and returns what happened inside the EVM: every internal call, every state touch, optionally every opcode. It powers the "internal txns" tabs on explorers, post-mortem debugging and MEV analysis, and it is also the method most RPC providers will not give you.
Try debug_traceTransaction
curl -s http://localhost:8545 \
-X POST \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "debug_traceTransaction",
"params": [
"0x16897e492b2e023d8f07be9e925f2c15a91000ef11a01fc71e70f75050f1e03c",
{
"tracer": "callTracer"
}
]
}'Public endpoints keep the debug namespace switched off, so this request would be rejected with "method not found" on a shared URL. Point the curl at a node with debug APIs enabled, such as your own or the one a BLAZED.sh container sits on, swap in a real transaction hash, and it runs as-is.
What debug_traceTransaction does
The node replays the block containing your transaction from the start, up to and through your transaction, with tracing hooks enabled. Because it re-executes real historical state, the results are exact rather than estimated: actual gas per call frame, actual reverts, the actual call tree.
The shape of the response is decided by the tracer option. With no tracer you get the raw opcode-level struct logger, one entry per EVM step with stack, memory and gas, which for a complex DeFi transaction runs to hundreds of megabytes. In practice you always pass a named tracer: callTracer for the call tree, prestateTracer for the state the transaction read (or, with diffMode, how it changed state), 4byteTracer for selector statistics.
Geth defines this interface, and Nethermind, Erigon and Reth implement it with the same named tracers. Parity-style clients additionally expose the alternative trace_transaction and trace_replayTransaction family. Either way the debug namespace must be enabled on the node, which is exactly what shared public endpoints do not do.
Parameters
| # | Name | Type | Description |
|---|---|---|---|
| 1 | transactionHash | string | Hash of a mined transaction. Pending transactions cannot be traced with this method; use debug_traceCall to trace a simulation instead. |
| 2 | optionsoptional | object | Tracer selection and knobs: tracer (callTracer, prestateTracer, 4byteTracer or a custom tracer), tracerConfig ({ onlyTopCall, withLog, diffMode }), timeout (e.g. "10s"), and reexec (how many blocks back the node may re-execute to rebuild state). |
What it returns
Depends on the tracer. callTracer returns one nested frame per call: type (CALL, DELEGATECALL, STATICCALL, CREATE, SELFDESTRUCT), from, to, value, gas, gasUsed, input, output, and a calls array of child frames; failed frames add error and, when decodable, revertReason.
prestateTracer returns per-address balance, nonce, code and the storage slots the transaction touched. The default struct logger returns gas, failed, returnValue and the structLogs opcode array.
Example response
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"type": "CALL",
"from": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
"to": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
"value": "0x2386f26fc10000",
"gas": "0x2e253",
"gasUsed": "0x1f4e2",
"input": "0x7ff36ab5...",
"output": "0x...",
"calls": [
{
"type": "CALL",
"from": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
"to": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"gasUsed": "0x5da6",
"input": "0xd0e30db0",
"calls": []
}
]
}
}A trimmed callTracer result for a router swap: the top frame is the user's call, child frames are the internal calls it made. Hash and values are illustrative.
debug_traceTransaction with ethers.js
import { WebSocketProvider } from "ethers";
// Inside a BLAZED.sh container the node is one local socket away
const provider = new WebSocketProvider("ws://blazed_infra_eth-execution:8545");
// ethers has no wrapper for debug_*; use provider.send
const trace = await provider.send("debug_traceTransaction", [
"0x16897e492b2e023d8f07be9e925f2c15a91000ef11a01fc71e70f75050f1e03c",
{ tracer: "callTracer", tracerConfig: { withLog: true } },
]);
console.log(trace.calls.length, "internal calls");Gotchas and common errors
Always name a tracer
The default struct logger records every opcode with stack and memory. For a simple transfer that is manageable; for a busy DeFi transaction it is hundreds of megabytes that take the node significant time to produce and you significant time to parse. A callTracer result for the same transaction is a few kilobytes. Omitting the tracer option is almost always a mistake.
Most endpoints will not run it
Shared public RPCs disable the debug namespace outright, and the big commercial providers sell trace access as a premium add-on with its own pricing and rate limits. On a dedicated node the API is simply enabled: co-located code on BLAZED.sh talks to a node where tracing is a local call, not a paywalled feature.
Old transactions need re-executable state
Tracing needs the state of the block before yours. Full nodes keep only recent states, so tracing an old transaction fails with errors like "required historical state unavailable". The reexec option lets the node rebuild state by replaying up to N blocks back, which works but gets slow fast. For deep history you want an archive node; on BLAZED.sh archive access is an Enterprise feature, and standard plans trace recent transactions on tip-of-chain nodes.
Heavy traces time out
Clients bound tracer run time (Geth defaults to a few seconds) and return a timeout error when re-execution plus tracing exceeds it. Pass timeout: "30s" in the options for heavyweight transactions, and set tracerConfig.onlyTopCall: true when you only need the top-level frame; skipping child-frame collection is a large speedup.
Reading reverts out of the tree
A failed frame carries error (e.g. "execution reverted") and, when the revert data decodes as Error(string), a human-readable revertReason. The top-level frame can show success while a nested call reverted and was caught by a try/catch pattern upstream, so when hunting a failure, walk the tree and find the deepest error frame; that is the true cause.
What debug_traceTransaction costs on BLAZED.sh
debug_traceTransaction costs 1 credit per call on BLAZED.sh; the heavy Parity-style replay methods trace_replayTransaction and trace_replayBlockTransactions are the ones billed at 4 credits. What you actually watch with tracing is the response-size surcharge: responses over 100KB add 50 credits per MB, and tracing is the method family that can genuinely hit it. A callTracer result for a typical swap stays in the kilobytes; a full struct-log trace can be thousands of times larger and cost accordingly, one more reason to always pass a named tracer.
See the full credit price listdebug_traceTransaction: frequently asked questions
Why does my provider return "method not found" for debug_traceTransaction?
The debug namespace is disabled on shared and public endpoints, and major providers gate tracing behind premium tiers. You need a node with the debug API enabled: your own, or a dedicated node like the one your code is co-located with on BLAZED.sh.
What is the difference between debug_traceTransaction and trace_transaction?
They are two API families for the same job. debug_traceTransaction is the Geth-style interface with named tracers like callTracer; trace_transaction and trace_replayTransaction are the Parity/OpenEthereum-style interface returning a flat action list, VM trace and state diff. Schemas differ, so pick one family per pipeline. On BLAZED.sh debug_traceTransaction costs 1 credit while trace_replayTransaction costs 4.
Do I need an archive node to trace transactions?
Not for recent ones: a full node traces anything whose pre-state it still holds, plus older blocks via slow reexec replays. For transactions deep in history you need archive state, which on BLAZED.sh is an Enterprise feature.
How do I trace a transaction before sending it?
Use debug_traceCall: it takes an eth_call-style call object plus a block tag and runs the same tracers against a simulation, so you can inspect the full call tree of a transaction that was never broadcast.
Which tracer should I use?
callTracer for the call tree and revert hunting, with withLog: true if you also want emitted events; prestateTracer for the state a transaction reads, or with diffMode for the state it changes; 4byteTracer for a quick census of function selectors. The opcode-level default is a last resort.
Call debug_traceTransaction from the node itself
Deploy your container or script next to a synced Ethereum node. No rate limits, no compute units, sub-10ms local RPC.