trace_replayTransaction: Flat Traces, VM Traces and State Diffs
trace_replayTransaction•Trace method•Ethereum JSON-RPCtrace_replayTransaction re-executes a mined transaction and returns the Parity-style view of it: a flat list of call actions, optionally the opcode-level VM trace, and optionally a state diff showing every balance, nonce, code and storage slot the transaction changed. It answers the same questions as debug_traceTransaction with a different schema, and the state diff is the reason people reach for it.
Try trace_replayTransaction
curl -s http://localhost:8545 \
-X POST \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "trace_replayTransaction",
"params": [
"0x1433cd18789195b1f14d55426174f0981ae3ee4256e30804a6d7e1fd286fe840",
[
"trace",
"stateDiff"
]
]
}'Two reasons this cannot run from the browser: the trace namespace is closed on shared endpoints (public ones typically answer that archive or trace requests need a paid token), and Geth-based endpoints do not implement trace_* at all. Point the curl at an Erigon, Nethermind or Reth node with the trace API enabled and it runs as-is.
What trace_replayTransaction does
You choose what you get with the second parameter, an array of trace types. "trace" returns the flat action list. "stateDiff" returns, per touched address, the before and after value of balance, nonce, code and each storage slot. "vmTrace" returns the opcode-level execution. You can request any combination, and the ones you leave out come back as null, so asking for everything by reflex is the easiest way to make this call far more expensive than it needs to be.
The flat format is the real difference from the Geth family. Instead of a nested tree of frames, you get a list where each entry carries a traceAddress array describing its position in the call hierarchy: [] is the root, [0] its first child, [0, 1] the second child of that. Reconstructing a tree from those paths is mechanical, but it is not the same data shape, so a pipeline built on callTracer cannot consume these traces without a converter.
Client support splits along the same line. Erigon, Nethermind and Reth implement the trace namespace; Geth does not implement it at all and answers "method not found", offering only its own debug namespace. That is the first thing to check when this method fails, before looking at anything else.
Parameters
| # | Name | Type | Description |
|---|---|---|---|
| 1 | transactionHash | string | Hash of a mined transaction. Pending transactions cannot be replayed; trace_call is the equivalent for simulating one. |
| 2 | traceTypes | array | Which outputs to produce: any combination of "trace", "stateDiff" and "vmTrace". Requesting only what you need is the difference between kilobytes and hundreds of megabytes. |
What it returns
An object with output (the transaction's return data), trace (the flat action list, each entry holding action, result, subtraces, traceAddress and type), stateDiff and vmTrace. Any type you did not request is null.
In stateDiff, each changed value is expressed as a transition object with the value before and after; slots the transaction only read are absent, and newly created entries are marked as such. This is the cleanest available answer to "what did this transaction change", and it is why the method exists despite costing more than the debug equivalents.
Example response
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"output": "0x0000000000000000000000000000000000000000000000000000000000000001",
"vmTrace": null,
"trace": [
{
"type": "call",
"action": {
"callType": "call",
"from": "0xdc4239109ce3a991673d29b26d84d487ad2cb19b",
"to": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"gas": "0x11c9d",
"input": "0xa9059cbb...",
"value": "0x0"
},
"result": { "gasUsed": "0xf328", "output": "0x…01" },
"subtraces": 1,
"traceAddress": []
}
],
"stateDiff": {
"0xdc4239109ce3a991673d29b26d84d487ad2cb19b": {
"balance": { "*": { "from": "0x1bc16d674ec80000", "to": "0x1bc0f8e0e3b5c8a1" } },
"nonce": { "*": { "from": "0xa", "to": "0xb" } },
"code": "=",
"storage": {}
}
}
}
}Trimmed to one trace entry and one diffed account. In stateDiff, "=" means unchanged and "*" wraps a from/to transition; the sender's nonce increments and the balance drops by the fee. traceAddress [] marks the root call, and subtraces counts its direct children, here the proxy's delegatecall.
trace_replayTransaction with ethers.js
import { WebSocketProvider } from "ethers";
// trace_* needs Erigon, Nethermind or Reth; Geth does not implement it
const provider = new WebSocketProvider("ws://eth:8545");
const replay = await provider.send("trace_replayTransaction", [
"0x1433cd18789195b1f14d55426174f0981ae3ee4256e30804a6d7e1fd286fe840",
["trace", "stateDiff"], // leave out vmTrace unless you truly need opcodes
]);
// flat traces carry their position instead of nesting
for (const t of replay.trace) {
const depth = " ".repeat(t.traceAddress.length);
console.log(`${depth}${t.type} ${t.action.to ?? ""} gas=${t.result?.gasUsed}`);
}
// every account the transaction touched
console.log(Object.keys(replay.stateDiff));Gotchas and common errors
Geth does not implement the trace namespace
This is not a configuration flag on Geth; the trace_* family is the Parity/OpenEthereum interface, implemented by Erigon, Nethermind and Reth. On a Geth node the answer is "method not found" no matter which APIs you enable, and the equivalent is debug_traceTransaction with callTracer plus prestateTracer in diffMode for the state changes.
vmTrace is the expensive word in the array
Adding "vmTrace" turns a kilobyte-scale answer into an opcode-by-opcode record of the entire execution, with the memory and storage deltas of every step. For a complex DeFi transaction that is hundreds of megabytes. Request "trace" and "stateDiff" by default, and reach for vmTrace only when you are genuinely debugging at the opcode level.
Flat traces are not call trees
Each entry carries traceAddress, its path in the hierarchy, and subtraces, its number of direct children. That is enough to rebuild the tree, but it is a different shape from the nested frames callTracer emits, and the field names differ throughout: action and result instead of a single frame object, callType instead of type. Mixing both families in one codebase without a normalising layer is a reliable source of bugs.
Replaying old transactions needs archive state
Like every replay method, this one needs the state as it was before the transaction. Recent history is fine on a full node; anything deeper requires archive state, and public endpoints often reject historical trace requests outright as paid-tier features. On BLAZED.sh archive access is an Enterprise feature and standard plans replay at the tip.
stateDiff is the reason to be here
If you only need the call tree, the debug family gives you the same information at the standard rate. What this method adds is a first-class before-and-after diff of every account and storage slot the transaction touched, which is far easier to consume than reconstructing changes from logs or from prestateTracer output. Choose the family by what you need, not by habit.
What trace_replayTransaction costs on BLAZED.sh
trace_replayTransaction is one of three methods priced at 4 credits per call on BLAZED.sh rather than the standard 1, alongside trace_replayBlockTransactions and txpool_content. The response-size surcharge applies on top: over 100KB, responses add 50 credits per MB, which a trace-and-stateDiff replay rarely reaches and a vmTrace replay reaches immediately. Where the same answer is available from debug_traceTransaction, which costs 1 credit, the debug family is the cheaper route; pay the 4 when you specifically want flat traces or a state diff.
See the full credit price listtrace_replayTransaction: frequently asked questions
Why does trace_replayTransaction return "method not found"?
Almost certainly because the node is Geth, which does not implement the trace namespace at all. Erigon, Nethermind and Reth do. On Geth, use debug_traceTransaction with callTracer, and prestateTracer with diffMode when you need state changes.
What is the difference between trace_replayTransaction and debug_traceTransaction?
Two interfaces for the same replay. The debug method returns nested frames from named tracers; this one returns a flat trace list with traceAddress paths, plus optional vmTrace and stateDiff. Schemas differ, so standardise on one family per pipeline. On BLAZED.sh the debug method costs 1 credit and this one costs 4.
What does stateDiff actually contain?
Per touched address, the before and after values for balance, nonce, code and each storage slot the transaction changed, with "=" marking anything unchanged. Slots that were only read do not appear.
Should I request vmTrace?
Only for opcode-level debugging. It records every EVM step and turns a small response into a very large one, which on any per-request billing model is where the cost of this method actually comes from.
How do I replay a whole block?
trace_replayBlockTransactions takes a block number and the same trace types, and is also priced at 4 credits. The Geth-family equivalent is debug_traceBlockByNumber at the standard rate.
Call trace_replayTransaction 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.