fix(eth): read handlers participate in failover; method-rejection counted as transient#62
Merged
BitHighlander merged 2 commits intodevelopfrom May 1, 2026
Merged
Conversation
…nted as transient
CowSwap, Uniswap multicalls, and most modern dApps make heavy use of eth_call / eth_getCode / eth_getStorageAt / eth_estimateGas / eth_feeHistory. These were single-shot against getProvider()'s currently-selected URL with no failover, so a narrow-purpose RPC like Flashbots — which Pioneer's catalog ranks in the top tier and which getProvider's pre-flight getBlockNumber() probe always passes — would get picked first on every read and 403 with -32601 'rpc method is not whitelisted'. Repeated requests preferred Flashbots again because the failedRpcs cooldown only fires when the pre-flight test fails (it doesn't on Flashbots).
Two-part fix:
1. isTransientRpcError now classifies method-rejection as transient: 'rpc method is not whitelisted', 'method not found', 'method not supported', '-32601', '403'. Each failure records the URL in failedRpcs for 60s so the next call goes elsewhere.
2. All 14 EVM read handlers (eth_getBlockByNumber, eth_blockNumber, eth_getBalance, eth_getTransactionReceipt, eth_getTransactionByHash, web3_clientVersion, eth_call, eth_max{Priority,}FeePerGas, eth_estimateGas, eth_gasPrice, eth_feeHistory, eth_getCode, eth_getStorageAt, eth_getTransactionCount) now route through withRpcFailover instead of getProvider(). Two cross-cutting reads (sign-flow fee-warning preflight bundle, transfer-build nonce/gas/fee chain) also migrated.
getProvider remains only as the drop-check fallback when the SW restart loses the success-URL hint. All other paths share failedRpcs through withRpcFailover so failover decisions are consistent.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two findings from PR #62's review. P1: handleEthGetStorageAt called provider.getStorageAt(...), but ethers v6 renamed it to getStorage. The migration to withRpcFailover preserved the buggy call (it was already broken in the pre-PR code). TypeError at runtime; no failover happens because the failure is a JS-level bug, not an RPC error. Switched to raw send('eth_getStorageAt', params) — byte-exact with the dApp's request and sidesteps v5/v6 method-name divergence. P2: isTransientRpcError matched bare .includes('403'). A revert reason or hex payload containing "403" would have been classified as transient, causing the same successfully-rejected eth_call / eth_estimateGas to be replayed across every RPC and unnecessarily cooling them. Narrowed to "server response 403" (ethers v6's transport wrapper format from the user's earlier log) and "http 403". The Flashbots case still matches via the existing "rpc method is not whitelisted" / "-32601" patterns. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
BitHighlander
added a commit
that referenced
this pull request
May 1, 2026
Cumulative since 0.0.28: #57 feat: Pioneer-sourced EVM chain registry + Solana sign-message UX #58 feat(ux): risk-tiered approval colors, hex dump + copy, deep-link Chainlist, typed timeouts #59 fix(eth): RPC failover on broadcast + pin networks on JsonRpcProvider #60 fix(audit): UTXO build race + cross-chain broadcast resilience + read failover #61 fix: drop orphaned approval events on service-worker startup #62 fix(eth): read handlers participate in failover; method-rejection counted as transient #63 fix(masking): default MetaMask masking ON; readable contrast on Settings text Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Symptom (CowSwap)
```
| ethereumHandler | | getProvider | | Trying RPC [1/25]: https://rpc.flashbots.net/
| ethereumHandler | | getProvider | | ✅ RPC working! Block: 24997148
| handleWalletRequest | Error processing method eth_call: server response 403
responseBody: {"jsonrpc":"2.0","error":{"code":-32601,"message":"rpc method is not whitelisted"}}
```
Repeated on every subsequent eth_call — Flashbots gets picked first every time.
Root cause
Flashbots' RPC is narrow-purpose (privacy/MEV protection): it serves `eth_sendRawTransaction`, `eth_chainId`, and `eth_blockNumber`, and rejects everything else with HTTP 403 + JSON-RPC `-32601` (`rpc method is not whitelisted`). Pioneer's catalog ranks it in the top tier (correct for its actual purpose), so:
Fix — two parts
1. Classify method-rejection as transient
`isTransientRpcError` now also matches:
These are URL-level capability problems; trying another URL helps. Treating them as transient means the URL gets a 60s cooldown via `failedRpcs`.
2. All 14 EVM read handlers migrate to `withRpcFailover`
`eth_getBlockByNumber`, `eth_blockNumber`, `eth_getBalance`, `eth_getTransactionReceipt`, `eth_getTransactionByHash`, `web3_clientVersion`, `eth_call`, `eth_max{Priority,}FeePerGas`, `eth_estimateGas`, `eth_gasPrice`, `eth_feeHistory`, `eth_getCode`, `eth_getStorageAt`, `eth_getTransactionCount` — each now runs through the failover loop with classification, transport timeout, and per-attempt URL rotation.
Plus two cross-cutting reads also migrated:
`getProvider()` remains only as the drop-check fallback when the SW restart loses the success-URL hint. All other paths share `failedRpcs` through `withRpcFailover` so failover decisions are consistent.
Files
Test plan
🤖 Generated with Claude Code