RPC error 3 / -32000: execution reverted
Last updated:
At a glance
| Property | Value |
|---|---|
| Applies to | Ethereum, Polygon, BNB Smart Chain, Base and Robinhood Chain |
| JSON-RPC code | 3, or -32000 with the same text |
| Origin | The contract, relayed unchanged |
| Retryable | No — deterministic for that block |
| Revert payload | 0x08c379a0 = Error(string); custom errors use their own selector |
| Ethereum, Polygon, BSC, Base | Passed through as returned |
| Robinhood Chain | Passed through; revert reason readable via debug_traceCall (best-effort) |
Cause
A contract stopped execution with REVERT during eth_call, eth_estimateGas or a trace. Nodes report it either as JSON-RPC code 3 or as -32000 with the text 'execution reverted'; both forms reach you unchanged, because a revert is a result about your transaction rather than an upstream fault. When the contract used a require with a message, the error carries a data payload beginning 0x08c379a0 — the ABI selector of Error(string) — and the reason is decodable from it.
Solution
- Decode error.data: a payload starting 0x08c379a0 is Error(string) and holds the human-readable reason; custom errors carry their own four-byte selector, which you can match against your ABI.
- Re-run the same call through debug_traceCall on a network that publishes it to see where execution stopped, rather than guessing from the message alone.
- Check the block you pinned: a call that succeeds at latest and reverts at an older block usually means state, not logic — see header not found for the archive boundary.
- Do not retry unchanged. The result is deterministic for that block and state; a retry returns the same revert.
Decode the reason from error.data
The first four bytes of error.data say what kind of revert you got. Everything after them is ABI-encoded arguments, so the reason is usually in the response you already have.
| Starts with | Meaning | How to read it |
|---|---|---|
| 0x08c379a0 | Error(string), from require or revert with a message | ABI-decode one string |
| 0x4e487b71 | Panic(uint256), from the compiler: overflow, division by zero, out-of-bounds index | Decode the code: 0x11 overflow, 0x12 division by zero, 0x32 array index |
| Any other four bytes | A custom error | Match the selector against the contract's ABI |
| Empty, 0x | Revert with no data | Trace the call: the data is not in the response |
// Error(string): selector, offset word, length word, UTF-8 bytes
const hex = err.data.slice(10);
const length = parseInt(hex.slice(64, 128), 16);
const bytes = hex.slice(128, 128 + length * 2).match(/../g) ?? [];
const reason = new TextDecoder().decode(
Uint8Array.from(bytes, (b) => parseInt(b, 16)),
);Common causes, by what the call was doing
An ERC-20 transferFrom that reverts usually lacks an allowance or a balance, and the two look alike until you read the reason. Swaps revert on slippage checks when the price moved between building and running the call. eth_estimateGas reverts for the same reasons, since it has to execute the transaction: an execution reverted from a gas estimate means the transaction would fail, not that gas estimation is broken.
Check the from address too. Many calls behave differently by sender, and an eth_call without from runs as the zero address, which fails access checks that a real sender would pass.
When the same call succeeds and then reverts
eth_call runs against a block you choose. A simulation at latest can pass and the transaction can still revert in a later block, because state moved. To reproduce a historical failure, pin the call to the block before the transaction; that needs archive state for old blocks, so the answer may be header not found instead. Read the transaction receipt to confirm: status: 0x0 means it was mined and reverted, and you paid for the gas used.
Example
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": 3,
"message": "execution reverted",
"data": "0x08c379a0…"
}
}FAQ
- Is execution reverted a Triport error?
- No. It is the chain's answer to your call, and we hand it back unchanged — including the data payload, which is where the reason lives.
- Why is there no reason text?
- The contract reverted without a message, or used a custom error. Only a require with a string produces the 0x08c379a0 Error(string) payload; custom errors identify themselves by a four-byte selector you match against your ABI.