SolidRPC plans change on 28th October. Some prices are going up. Lock in today’s pricing and limits by activating before then. See what’s changing.
Skip to main content
Guides
Category
Operations
Reading time
8 minutes
Published

How to debug a failed EVM transaction from its hash

A transaction hash tells you where to start, but a red status badge does not explain why a contract call failed. The receipt establishes whether the transaction was included and whether its top-level execution succeeded. The transaction input and, when necessary, an execution trace provide the context to investigate the failure.

Use the lightest evidence that answers the question. This workflow moves from receipt to transaction details to revert data and a call trace. It also separates a confirmed onchain failure from an RPC error or an unconfirmed transaction, which require different next steps.

Section 01

What does the transaction receipt prove?

Call `eth_getTransactionReceipt` with the transaction hash. A null receipt does not mean the EVM reverted. It means the receipt was not found through that request. Check the hash, chain, and transaction lookup before diagnosing execution. If the request itself returns an RPC error, record that separately from a receipt result.

For a receipt with status: 0x0, the top-level transaction failed. status: 0x1 means its top-level execution succeeded. Neither value tells you which internal call was responsible. Record blockHash, blockNumber, transactionIndex, gasUsed, and the hash so later evidence refers to the same inclusion. Ethereum's receipt status specification also explains why consuming all gas is not a reliable test for failure.

Do not expect a revert string in the receipt. The standard receipt reports status and logs, not a decoded reason. A failed receipt may have an empty logs array, but that alone is not the diagnosis.

Section 02

Which transaction details should you collect next?

Fetch `eth_getTransactionByHash` and compare its block identity with the receipt. Keep from, to, input, value, gas fields, and transaction type. The input begins with a function selector for an ordinary ABI call, followed by encoded arguments. Decode it with the verified ABI for the contract you actually called. The Solidity ABI specification defines this encoding.

If the transaction is missing, confirm the chain and hash before assuming an execution failure. If its block identity changes during a reorganization, repeat the investigation against the canonical inclusion. Avoid publishing private calldata or credentials in a support ticket or public issue.

Section 03

Can you recover a revert reason without a trace?

If your application already captured revert data from a simulation or an RPC error, decode those bytes with the relevant contract ABI. Solidity uses Error(string), Panic(uint256), and custom errors, but a revert can also contain no data. Custom error data begins with a four-byte selector and ABI-encoded arguments. The Solidity custom-errors guide describes both the format and why an error name alone should not be treated as proof of which contract produced it.

An eth_call reconstructed from the transaction can be a useful hypothesis test, but it is not a historical replay of the mined transaction. A call at a block reference may see state after other transactions, and a call at latest sees a later world entirely. The sender, value, gas, input, block context, and state must match for the comparison to be meaningful. Even then, a different result is evidence of a context mismatch, not proof that the receipt was wrong. See the `eth_call` specification and the EVM tracing guide for the distinction.

Section 04

When should you request a transaction trace?

Request a trace when the receipt and available revert data cannot identify the failing call, especially in a transaction that crosses several contracts. A debug_traceTransaction call with callTracer, where supported, returns a nested call tree. Inspect the top-level frame, then follow child calls to the frame with an error. Compare that frame's input, raw output, error, and parent path. A failed child can be caught by its parent, so do not label the whole transaction failed merely because one inner frame reports an error.

The built-in tracer documentation describes the call-tree fields and notes that top-level revert data may include a parsed revertReason. That field is not guaranteed for every failure or implementation. Preserve raw bytes when the decoded reason is absent. Use opcode-level output only if the call tree still cannot answer the question, since step traces can be much larger. For the difference between trace families, see the trace API guide.

Section 05

What if the trace is unavailable or times out?

Treat method not found, missing historical data, timeout, and a successful trace with no decoded reason as distinct outcomes. The first asks whether the exact trace method is supported. The second asks whether the required block context can be served. A timeout may mean the trace is expensive or its deadline is too short. None of these outcomes changes the receipt's status.

Keep the original transaction hash, block hash, method, tracer options, elapsed time, and returned error. If tracing is unavailable, use the receipt, transaction input, application logs, and any captured simulation output to narrow the cause. Do not substitute a trace from a different block or a latest simulation and present it as the mined execution. A trace capability should be tested with a representative recent and historical transaction before a production debugger depends on it.

Section 06

How do you turn the evidence into a fix?

Map the first relevant failing frame to the contract and ABI you control. Decode its selector and arguments, then inspect the requirement or invariant that produced the revert. Check balances, allowances, authorization, deadlines, slippage bounds, and gas assumptions only when they are relevant to that function. A Panic(uint256) suggests a different class of problem from a business-rule custom error. Empty revert data means you must rely on the surrounding call context rather than inventing a reason.

Reproduce with the same inputs and a deliberately chosen block context, then change one condition at a time. Record the receipt status and block identity from the original transaction beside any reproduction. If you plan to send another transaction, simulate the proposed new call and review its parameters. A successful simulation is useful evidence, but changing chain state and transaction ordering can still change the mined result. The simulation-method guide explains those limits.

Section 07

Failed-transaction diagnosis checklist

  1. Confirm the chain and transaction hash. Distinguish a null receipt from an RPC error.
  2. Save the receipt's status, block hash, block number, transaction index, and gas used.
  3. Fetch the transaction and decode its input with the correct contract ABI.
  4. Decode any revert bytes already captured. Do not assume the receipt contains them.
  5. If the cause remains unclear, request a supported call trace for the mined hash and inspect the failing frame in context.
  6. Record missing-method, missing-history, and timeout outcomes separately.
  7. Reproduce against a specified state, verify the proposed fix, and keep the original onchain evidence distinct from the new simulation.

The receipt answers whether the top-level transaction failed. The trace can help answer where it failed. The contract's rules and the exact state explain why.

Workload review

Want us to benchmark your current RPC stack?

Send your providers, routing setup, method mix, or monthly bills. We will compare the complete stack with one SolidRPC integration across cost, useful completion rate, failover, and operational work.