Skip to content

feat(transaction-controller): extract revert reason for on-chain failures#8589

Draft
matthewwalsh0 wants to merge 1 commit intomainfrom
feat/extract-revert-reason
Draft

feat(transaction-controller): extract revert reason for on-chain failures#8589
matthewwalsh0 wants to merge 1 commit intomainfrom
feat/extract-revert-reason

Conversation

@matthewwalsh0
Copy link
Copy Markdown
Member

Explanation

When a transaction fails on-chain (its receipt has status: 0x0), the controller currently records a generic Transaction failed on-chain error with no information about why it failed. The actual revert reason — ERC20: transfer amount exceeds balance, Panic: arithmetic overflow, etc. — is lost, even though it is recoverable from any standard JSON-RPC endpoint.

This PR extracts a human-readable revert reason for every on-chain failure and surfaces it in two places:

  1. A new revertReason?: string field on TransactionMeta — for consumers that don't want to parse error strings.
  2. Appended to the existing error messageTransaction failed on-chain: ERC20: transfer amount exceeds balance.

How it works

When PendingTransactionTracker detects a receipt with status === 0x0, it replays the original call via eth_call at the block where the transaction was mined. The EVM returns the same revert as it did when the transaction was included, and the response is decoded:

  • Error(string) (selector 0x08c379a0) → the embedded message
  • Panic(uint256) (selector 0x4e487b71) → e.g. Panic: arithmetic overflow or underflow (0x11)
  • Custom error selectorsreverted with custom error 0xdeadbeef
  • Empty reverts → falls back to the provider error message (e.g. execution reverted: ...)
  • No revert data at all → field stays undefined

The whole extraction is wrapped in a try/catch and never throws, so it's safe to run on every failed receipt with no impact on the existing failure path.

Why eth_call (not debug_traceCall / debug_traceTransaction)

eth_call works on every standard JSON-RPC endpoint (Infura, QuickNode, public RPCs). debug_* methods are paywalled or unsupported on most providers. The replay approach has been used by ethers.js and viem for years and gives the same fidelity for the common cases.

References

Resolves the gap identified in Failed Transaction Analysis — the most common Pay failure (ERC20: transfer amount exceeds balance) was completely opaque to consumers.

Changelog

```

Added

  • Extract a human-readable revert reason for transactions that fail on-chain
    • When a transaction's receipt has a failure status (`0x0`), the controller now replays the call via `eth_call` at the failing block and decodes the returned revert data
    • Supports `Error(string)`, `Panic(uint256)` (with named codes), custom error selectors, and empty reverts; falls back to the provider error message when no revert data is surfaced
    • Adds `revertReason?: string` field to `TransactionMeta` for consumers that prefer not to parse error strings
    • Appends the decoded reason to the existing `Transaction failed on-chain` error message
      ```

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
  • I've prepared draft pull requests for clients and consumer packages to resolve any breaking changes — no breaking changes; `revertReason` is purely additive

…ures

When a transaction's receipt has a failure status (0x0), replay the
call via eth_estimateGas and decode the returned revert data so users
see why the transaction failed instead of just 'Transaction failed
on-chain'.

- Add extractRevertReason util that decodes Error(string), Panic(uint256)
  with named codes, custom error selectors, and empty reverts; falls back
  to the provider error message when no revert data is surfaced
- Add revertReason?: string field to TransactionMeta for consumers that
  prefer not to parse error strings
- Append the decoded reason to the existing 'Transaction failed on-chain'
  error message via a new OnChainFailureError subclass

eth_estimateGas is used instead of eth_call as a workaround for a bug
in eth-json-rpc-middleware's RetryOnEmptyMiddleware: its
isExecutionRevertedError check fails to recognise EIP-1474 / Infura-style
revert errors (code 3, suffixed message), causing it to retry reverted
calls 10 times and discard the original error data. eth_estimateGas
returns the same revert payload but bypasses that middleware. A separate
upstream PR fixes the underlying bug.
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