← All guides
intermediate May 16, 2026

Debug a failed transaction with cast

How to diagnose and fix failed transactions on EVM testnets using Foundry's cast tool — decode revert reasons, simulate calls and trace execution.


A failed transaction gives you a hash and an error. cast turns that hash into a full picture of what went wrong — revert reason, execution trace, gas breakdown — without leaving the terminal.

Setup

cast is included with Foundry. Set your RPC URL once so you do not have to repeat it:

export ETH_RPC_URL=https://rpc.sepolia.org

1. Get the revert reason

The fastest first step. Given a failed transaction hash:

cast tx 0xYOUR_FAILED_TX_HASH

Look for the revertReason field. If the contract used a require message or custom error, it will appear here.

For a cleaner output:

cast receipt 0xYOUR_FAILED_TX_HASH

2. Decode raw revert data

Sometimes the revert data is raw hex. Decode it:

cast 4byte-decode 0x08c379a0...

For custom errors defined in a specific contract ABI:

cast abi-decode "MyError(uint256,address)" 0x...

3. Simulate the call before sending

Always simulate before broadcasting to catch reverts early:

cast call <CONTRACT_ADDRESS> \
  "transfer(address,uint256)" \
  0xRECIPIENT 1000000 \
  --from 0xYOUR_ADDRESS

If this reverts, you will see the reason without spending gas or waiting for a transaction.

Simulate at a specific block to reproduce historical state:

cast call <CONTRACT_ADDRESS> "balanceOf(address)(uint256)" \
  0xSOME_ADDRESS \
  --block 7400000

4. Trace the full execution

cast run replays a transaction and shows the full call trace:

cast run 0xYOUR_FAILED_TX_HASH

Output shows every internal call, the value transferred, gas used at each step and the exact point of revert:

Traces:
  [45231] MyContract::transfer(0xabc..., 1000)
    ├─ [3200] ERC20::_transfer(0xdef..., 0xabc..., 1000)
    │   └─ ← [Revert] ERC20InsufficientBalance(0xdef..., 500, 1000)
    └─ ← [Revert]

For a more verbose trace with storage reads and writes:

cast run 0xYOUR_TX_HASH --trace-printer

5. Check state before and after

Read any storage slot from the contract:

# Read public variable
cast call <CONTRACT_ADDRESS> "balanceOf(address)(uint256)" 0xYOUR_ADDRESS

# Read raw storage slot
cast storage <CONTRACT_ADDRESS> 0x0

Compare the value at different blocks:

cast call <CONTRACT_ADDRESS> "owner()(address)" --block 7399999
cast call <CONTRACT_ADDRESS> "owner()(address)" --block 7400000

6. Common revert patterns

Out of gas

cast estimate <CONTRACT_ADDRESS> "doSomething(uint256)" 42

If the estimate is close to the gas limit you set, increase the limit:

cast send <CONTRACT_ADDRESS> "doSomething(uint256)" 42 \
  --gas-limit 500000 \
  --private-key $PRIVATE_KEY

Nonce mismatch

cast nonce 0xYOUR_ADDRESS

If you have stuck pending transactions, wait for them to confirm or replace them with a higher gas price transaction to the same nonce.

Wrong value sent

Check what the function requires:

cast call <CONTRACT_ADDRESS> "minDeposit()(uint256)"

Then send the correct value:

cast send <CONTRACT_ADDRESS> "deposit()" \
  --value 0.01ether \
  --private-key $PRIVATE_KEY

Access control

# Check who the owner is
cast call <CONTRACT_ADDRESS> "owner()(address)"

# Check if your address has a role
cast call <CONTRACT_ADDRESS> \
  "hasRole(bytes32,address)(bool)" \
  $(cast keccak "ADMIN_ROLE") \
  0xYOUR_ADDRESS

7. Decode event logs

Read events emitted by a transaction:

cast receipt 0xYOUR_TX_HASH --json | jq '.logs'

Decode a specific log with the event signature:

cast decode-event "Transfer(address indexed,address indexed,uint256)" \
  0xTOPIC1 0xTOPIC2 0xDATA

8. Quick reference

Task Command
Get tx details cast tx <hash>
Get receipt cast receipt <hash>
Simulate call cast call <addr> "fn()" args
Replay + trace cast run <hash>
Decode revert cast 4byte-decode <data>
Read storage cast storage <addr> <slot>
Check nonce cast nonce <addr>
Estimate gas cast estimate <addr> "fn()" args
Decode event cast decode-event "Event(...)" topics data
Read at block add --block <number> to any call

Tip: alias your RPC

Add to your shell profile to avoid repeating the flag:

alias cast-sepolia="cast --rpc-url https://rpc.sepolia.org"
alias cast-base="cast --rpc-url https://sepolia.base.org"
alias cast-amoy="cast --rpc-url https://rpc-amoy.polygon.technology"