DEV Community

SwiftNodes
SwiftNodes

Posted on Originally published at swiftnodes.io

trace_filter and trace_transaction: Reading What Receipts Can't Show

A successful receipt tells you almost nothing about what a transaction actually did. The token moved, but through which contracts? The call succeeded, but which internal transfer carried the value? Logs record what contracts chose to emit; everything between calls is invisible to eth_getTransactionReceipt. The Parity trace namespace was built for exactly that gap, and two of its methods do most of the work: trace_transaction for a single transaction's full internal-call tree, and trace_filter for scanning ranges of blocks. We operate RPC across 75+ chains and probe trace support weekly (it's part of our method-support matrix), so this comes with current availability data, not folklore.

trace_transaction: one transaction, fully unpacked

Here's a real response for a fresh Sonic transaction, captured through our endpoint while writing this:

// trace_transaction("0xe3bb52bf…") on Sonic 
[
  {
    "action": {
      "callType": "call",
      "from": "0x1fc056…",
      "to": "0xf87af5…",
      "value": "0x0",
      "gas": "0x10e78",
      "input": "0xe17e76e3…"
    },
    "blockNumber": 78366386,
    "result": { "gasUsed": "0x1201d", "output": "0x…0001" },
    "subtraces": 0,
    "traceAddress": [],
    "transactionHash": "0xe3bb52bf…",
    "transactionPosition": 0,
    "type": "call"
  }
]
Enter fullscreen mode Exit fullscreen mode

Each entry is one step of execution. The fields that matter:

  • action — who called whom (from/to), with what value, callType (call, delegatecall, staticcall), or for other types: create (contract deployment) and suicide.
  • traceAddress — this step's position in the call tree. [] is the top-level call; [2] is the third sub-call of the top level; [2,0] is its first child. That array is how you reconstruct the whole tree.
  • subtraces — how many children this step spawned.
  • result — gas used and return data at this level, separately from the transaction-level receipt.

A DEX swap through a router, two pools, and a recipient ends up as a dozen or more of these entries — the exact path the value traveled. That reconstruction is what accounting tools, forensic dashboards, and internal-transfer indexers are actually built on. For the cost side of tracing (and why you should never loop these calls casually), see our earlier piece on debug_traceTransaction.

trace_filter: the indexer's range scan

trace_transaction answers "what did this hash do?" trace_filter answers "what happened across these blocks?" — scans a block range and returns every trace matching your filter:

// trace_filter over three recent Ethereum blocks 
// 7,276 traces returned
[
  {
    "action": {
      "from": "0x835033…",
      "callType": "call",
      "to": "0x933339…",
      "value": "0x214e8348c4f0000",   // 0.15 ETH
      "input": "0x",
      "gas": "0x13498"
    },
    "blockNumber": 25859102,
    "subtraces": 0,
    "traceAddress": [],
    "transactionHash": "0x49bcbd…",
    "type": "call"
  }
]
Enter fullscreen mode Exit fullscreen mode

The filter object accepts fromBlock, toBlock, plus optional address and topics constraints. Indexers use it the way Safe's Transaction Service does for non-L2 deployments — their infrastructure docs list the trace methods among the indexer's RPC requirements, with trace_filter doing the discovery work. Note the volume in that sample: roughly 2,400 internal actions per Ethereum block. That number explains everything in the next section.

The param gotcha we hit today

Some implementations reject block tags in the filter and demand numeric blocks. The same query, "fromBlock": "latest" versus "0x18a9c1d":

{"code":-32602,"message":"Invalid params","data":"invalid value: string \"latest\", expected a 8 byte hex string at line 1 column 21"}
Enter fullscreen mode Exit fullscreen mode

Fetch eth_blockNumber and pass hex numbers — it works everywhere the method works at all. -32602 here is good news, by the way: the method exists and parsed your request. That ambiguity between "fix your params" and "method missing" is the same taxonomy we covered in yesterday's error-code field guide.

Where traces actually work in 2026

The trace namespace comes from the Parity client lineage. Geth never implemented it, which means most L2s and geth-fork chains simply don't have it — and among chains whose software could serve traces, providers frequently gate them because they're expensive. This week's probe across the 54 EVM chains we serve:

Capability Chains serving it
trace_filter 11 of 54
trace_transaction 11 of 54
Both (full indexer suite) 8 of 54 — Ethereum, Gnosis, Sonic, Berachain, Fraxtal, Plasma, PulseChain, Soneium

OP Stack chains and most zk chains return clean rejections — the namespace is gone there by design. And even where a chain supports tracing, the upstream provider can gate it. A real response from a Base request, via a popular public endpoint:

{"code":-32602,"message":"Archive requests require a personal token. Get one at: https://www.allnodes.com/publicnode"}
Enter fullscreen mode Exit fullscreen mode

That's policy, not capability — which is why per-chain claims need probing rather than reading someone's feature page. The live, re-probed breakdown is on our trace_filter and trace_transaction pages, column-by-column for every chain.

Working with traces in practice

  • Reconstructing internal transfers: filter by to or from address over a block range, keep entries with type: "call" and non-zero value — that's the ETH that moved between contracts, invisible to receipt-based accounting.
  • Post-mortem debugging: when a transaction "succeeded" but balances don't add up, trace_transaction shows every internal hop. Pair with state proofs when you need to verify, not just inspect.
  • Historical tracing requires archive access: traces are state-dependent, so scanning old ranges routes through archive nodes — on SwiftNodes that's &archive=1, included on paid plans.
  • Respect the volume: thousands of traces per block means wide filters are heavy. Narrow your block range, filter by address where you can, and cache aggressively. Same discipline as getLogs range caps.

Check any endpoint yourself

npx rpc-doctor <url> probes method support — including the trace pair — and reports what an endpoint actually serves, for any endpoint, yours or anyone's. It's open source.

And if you want traces where they exist and honest answers where they don't: our routing spreads load across upstreams that genuinely serve the namespace, and the method-support matrix is re-probed weekly through the same path your requests take — free tier included.


Originally published on the SwiftNodes blog. SwiftNodes provides flat-rate multi-chain RPC endpoints — HTTP + WebSocket, 75+ chains, no per-request metering. Grab a free key.

Top comments (0)