Skip to content

fix(eth-json-rpc-middleware): detect EIP-1474 / Infura-style revert errors#8597

Draft
matthewwalsh0 wants to merge 1 commit intomainfrom
fix/eth-json-rpc-middleware-revert-detection
Draft

fix(eth-json-rpc-middleware): detect EIP-1474 / Infura-style revert errors#8597
matthewwalsh0 wants to merge 1 commit intomainfrom
fix/eth-json-rpc-middleware-revert-detection

Conversation

@matthewwalsh0
Copy link
Copy Markdown
Member

@matthewwalsh0 matthewwalsh0 commented Apr 27, 2026

Explanation

`isExecutionRevertedError` (introduced in #254, Oct 2023) is too strict to match the response shape that Infura, Alchemy, QuickNode, and most production providers actually return:

```ts
return isJsonRpcError(error) &&
error.code === errorCodes.rpc.invalidInput && // -32000
error.message === 'execution reverted'; // exact match
```

The real-world EIP-1474 response is:

```json
{
"code": 3,
"message": "execution reverted: ERC20: transfer amount exceeds balance",
"data": "0x08c379a0..."
}
```

Neither `code === -32000` nor `message === 'execution reverted'` matches, so the function returns `false` for the most common case it's supposed to detect.

Verification

I tested the same reverted USDC `transfer` call against Infura's RPC for 6 chains. All return identical EIP-1474 shape:

Chain code message
Ethereum mainnet 3 `execution reverted: ERC20: transfer amount exceeds balance`
Arbitrum One 3 (same)
Optimism 3 (same)
Polygon 3 (same)
Base 3 (same)
Linea 3 (same)

Impact

Limited in practice. `RetryOnEmptyMiddleware` only intercepts methods with a block-ref param (`eth_call`, `eth_getBalance`, `eth_getCode`, `eth_getTransactionCount`, `eth_getStorageAt`, `eth_getBlockByNumber`), and only when the block ref is a numeric value — `'latest'` and `'pending'` are skipped. Most consumer `eth_call`s in the monorepo use `'latest'`, so the bug doesn't fire.

The cases where it does fire:

  1. Historical `eth_call` against a contract that reverts — gets retried 10× (~10s latency) and the original `error.data` is discarded behind a generic `'retries exhausted'` error
  2. New consumers that want a numeric block tag and expect to receive the revert error (e.g. transaction-controller revert-reason extraction in feat(transaction-controller): extract revert reason for on-chain failures #8589, which switched to `eth_estimateGas` to work around this)

Fix

```ts
const EXECUTION_REVERTED_ERROR_CODE = 3; // EIP-1474

const isExpectedCode =
error.code === errorCodes.rpc.invalidInput ||
error.code === EXECUTION_REVERTED_ERROR_CODE;

return isExpectedCode &&
typeof error.message === 'string' &&
error.message.startsWith('execution reverted');
```

The new check is a strict superset of the old one — geth-style responses (`code: -32000`, exact message) still match.

References

Changelog

```

Fixed

  • `RetryOnEmptyMiddleware` now correctly propagates execution-revert errors from EIP-1474 / Infura-style providers
    • `isExecutionRevertedError` previously required `code: -32000` and an exact `"execution reverted"` message, which never matches the response shape returned by Infura, Alchemy, QuickNode, and other production providers (`code: 3`, `message: "execution reverted: "`)
    • The check now also accepts `code: 3` (per EIP-1474) and a message that starts with `"execution reverted"`, preserving backward compatibility with geth-style responses
      ```

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've highlighted breaking changes using the `BREAKING` category above as appropriate — no breaking changes; the new check is a strict superset of the old one
  • I've prepared draft pull requests for clients and consumer packages to resolve any breaking changes — no breaking changes

…rrors

isExecutionRevertedError previously required code -32000 and an exact
'execution reverted' message, but every Infura RPC (and most production
providers) returns the EIP-1474 form: code 3 with a suffixed message
like 'execution reverted: ERC20: transfer amount exceeds balance'.
Neither condition matched.

The result: RetryOnEmptyMiddleware treated every reverted eth_call with
a numeric block tag as a transient empty response, retried 10 times
(~10s latency), and threw a generic 'retries exhausted' error that
discarded the original revert payload.

The check now accepts code 3 in addition to -32000, and matches messages
that start with 'execution reverted' rather than requiring exact
equality. Geth-style responses continue to work.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant