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"