fix: query layer 1 gas fee oracles via direct eth_call instead of ethers Web3Provider#9505
Open
cryptotavares wants to merge 5 commits into
Open
fix: query layer 1 gas fee oracles via direct eth_call instead of ethers Web3Provider#9505cryptotavares wants to merge 5 commits into
cryptotavares wants to merge 5 commits into
Conversation
…ers Web3Provider Previously OracleLayer1GasFeeFlow and MantleLayer1GasFeeFlow called OP-stack oracle contracts via an ethers Contract backed by Web3Provider. Web3Provider batches its JSON-RPC dispatch using setTimeout, which never fires on React Native iOS when the timer pump is starved due to a display-link freeze. This blocks addTransaction indefinitely, preventing the dapp confirmation modal from ever appearing (MetaMask/metamask-mobile#32863, Sev1). Replace the Contract/Web3Provider path with Interface.encodeFunctionData (from @ethersproject/abi) to encode calldata, then dispatch via the controller's existing rpcRequest utility (a plain provider.request eth_call). This path is timer-free and works correctly even while the RN timer pump is frozen. Layer1GasFeeFlowRequest gains a messenger field so flows can call rpcRequest directly. The fix is applied to both the base oracle flow (getL1Fee, getOperatorFee) and MantleLayer1GasFeeFlow (tokenRatio). Validated end-to-end on a live MetaMask Mobile repro: the confirmation modal now surfaces while the RN timer pump is frozen.
matthewwalsh0
requested changes
Jul 15, 2026
|
|
||
| ### Changed | ||
|
|
||
| - Add `messenger` property to `Layer1GasFeeFlowRequest` ([#9505](https://github.com/MetaMask/core/pull/9505)) |
Member
There was a problem hiding this comment.
Minor, is this unnecessary in changelog since Layer1GasFeeFlowRequest isn't exposed by the package?
| messenger: TransactionControllerMessenger; | ||
|
|
||
| /** RPC Provider instance. */ | ||
| provider: Provider; |
Member
There was a problem hiding this comment.
Can we remove this at the same time since it's no longer used and encourages use of rpcRequest instead?
matthewwalsh0
approved these changes
Jul 15, 2026
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.
Explanation
Current state:
OracleLayer1GasFeeFlowandMantleLayer1GasFeeFlowcall OP-stack gas price oracle contracts using an ethersContractbacked byWeb3Provider. Every read through this path (contract.getL1Fee→BaseProvider.call) first awaitsgetNetwork()→detectNetwork(), which internally depends on zero-delaysetTimeoutcalls: the constructor's network-detectionsetTimeout(…, 0), anawait timer(0)inside_uncachedDetectNetwork, and the detect-network cache-clearsetTimeout(…, 0).Why it needs to change: On React Native iOS, the JS timer pump is driven by a single
CADisplayLinkregistered inRCTDisplayLink. When the display link stops receiving ticks from the render server (backboardd), allsetTimeoutcallbacks silently stop firing — while the app remains visually foreground-active. This happens reproducibly when a WKWebView (in-app browser) becomes the active surface. Bridgeless RN has no zero-delay fast path, so evensetTimeout(…, 0)waits for a display frame that never comes. As a result, ethers'getNetwork()/detectNetwork()timers never fire, the oracleeth_callis never dispatched,updateTransactionLayer1GasFeeblocks indefinitely insideaddTransaction, no approval is ever created, and the dapp confirmation modal never appears. This was reported as a Sev1 in MetaMask Mobile (MetaMask/metamask-mobile#32863) and reproduced on both iOS Simulator and physical iPhone.The fix: Replace the
Contract/Web3Providerdispatch path with:Interface.encodeFunctionData(from@ethersproject/abi, already an indirect dependency) to ABI-encode the calldata forgetL1Fee,getOperatorFee, andtokenRatio.rpcRequestutility, which callsprovider.requestdirectly — a plain EIP-1193 call with no timer dependency.Layer1GasFeeFlowRequestgains amessengerfield so the flows can reachrpcRequest. The fix covers both the base oracle flow (OracleLayer1GasFeeFlow:getL1Fee+getOperatorFee) andMantleLayer1GasFeeFlow(tokenRatiointransformOracleFee). The fix was validated end-to-end on a live mobile repro: after the patch, the confirmation modal appears correctly while the RN timer pump is frozen.No new npm dependencies are introduced —
@ethersproject/abiwas already transitively present;@ethersproject/contractsand@ethersproject/providersare no longer imported.References
Checklist
Note
Medium Risk
Touches the
addTransactionL1 fee path and introduces a breaking shape change onLayer1GasFeeFlowRequestfor any custom flows, though behavior is covered by updated tests and targets a known mobile deadlock.Overview
Layer 1 gas fee flows no longer use ethers
Contract+Web3Providerto read OP-stack and Mantle oracles. They ABI-encodegetL1Fee,getOperatorFee, andtokenRatiowith@ethersproject/abiand call the existingrpcRequesthelper (eth_callonprovider.request), avoiding ethers’setTimeout-based network detection that can hang indefinitely on React Native iOS and blockaddTransaction/ dapp confirmations.Layer1GasFeeFlowRequestnow carriesmessengerinstead ofprovider;updateTransactionLayer1GasFeepasses the messenger through and no longer resolves a provider up front. Oracle responses treat empty0x/ non-string results as errors for L1 fee and MantletokenRatio(operator fee still defaults to zero on failure).@ethersproject/providersis dropped from dependencies; tests are updated to mockrpcRequestand asserteth_callpayloads.Reviewed by Cursor Bugbot for commit f191b5f. Bugbot is set up for automated code reviews on this repo. Configure here.