chore: [performance] remove 30+ unnecessary RPC API calls on wallet unlock #33379
Conversation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit afba082. Configure here.
|
|
||
| totalEnabled += 1; | ||
|
|
||
| if (networkMetadata.status === NetworkStatus.Available) { |
There was a problem hiding this comment.
Stale RPC status shows banner
Medium Severity
Removing the startup network probe stops refreshing persisted networksMetadata. The banner timer still treats non-Available statuses (e.g. Unavailable) as failures, while only Unknown is ignored. After a prior outage, users can see a connection banner on unlock even when RPC is healthy again.
Reviewed by Cursor Bugbot for commit afba082. Configure here.
🔍 Smart E2E Test Selection
click to see 🤖 AI reasoning detailsE2E Test Selection: Performance Test Selection: |
|





Description
I recently started profiling MetaMask on mid-range Android devices and noticed something that surprised me - the wallet was making over 100 API calls the moment it unlocked. I decided to dig into it and figure out which of those were actually necessary.
Image of the 120+ API calls made on app unlock:

After tracing each call to its origin, the ones that stood out the most were the RPC calls - specifically because they were firing on every single wallet unlock, for every enabled network, before the user had done anything.
By default MetaMask ships with 8 enabled EVM popular networks. On unlock, the app was making 3 RPC calls per network, so a minimum of 24 RPC calls just to open the wallet. If you've added all popular networks that number jumps to 45.
Image of the 32 RPC API calls on a wallet of a user with 0 balance (new user). This happens on every wallet unlock!

Here's what each of those three calls was doing:
eth_blockNumber- called byAccountTrackerController's RPC balance fetcher before making a Multicall3 call. It needs a block reference to pass toeth_call.eth_callto Multicall3 (0xca11bde...) - called byAccountTrackerControllerto fetch the user's native ETH balance on chains not covered by the Accounts API v4, using a batchedaggregate3call.eth_blockNumber+eth_getBlockByNumber- called byEngine.lookupEnabledNetworks(), which was being triggered on every wallet mount by the network connection banner hook to probe whether each RPC endpoint was healthy.The thing is, MetaMask has improved a lot in the past year. We now have the Accounts API v4 which fetches balances for all major EVM networks centrally, and the new unified
AssetsControllertakes over balance fetching entirely when theassetsUnifyStateflag is on. The oldAccountTrackerControllerwas still running its unlock handler and hitting RPC on chains the Accounts API should have covered, becauseisDeprecatedwas never wired to the feature flag.But the bigger question I kept asking myself was: why are we probing RPC endpoints at all on startup? A user might open the app just to check their balance via the Accounts API, navigate somewhere, and close it - never touching a flow that needs RPC. We were firing 24+ RPC calls on their behalf anyway, for no reason.
And here's the key insight: we don't need to know if an RPC is broken until the user actually tries to use it. If they switch networks, send a transaction, or connect to a dApp and the RPC fails,
NetworkControllerfiresrpcEndpointChainUnavailableand the network connection banner already shows up reactively. The banner works perfectly without a startup probe - it just needed one small guard so it doesn't false-positive on app restart (more on that below).So this PR makes two changes:
1. Remove the startup network probe from the banner hook.
useNetworkConnectionBannerwas callingEngine.lookupEnabledNetworks()on every mount, which probed all enabled networks just to seednetworksMetadatabefore the banner's 5s/30s timers fired. Removing it eliminates theeth_blockNumber+eth_getBlockByNumberpair per network. One small side effect: on a fresh app start, all networks begin withNetworkStatus.Unknown(not yet probed). Without a guard, the banner would fire a false positive on every cold start, treating "haven't asked yet" as "failing". The fix is to skipUnknownstatus the same way we skipAvailable, so the banner only triggers on real, confirmed failures.2. Wire
AccountTrackerController.isDeprecatedto theassetsUnifyStateflag. When the controller is listed in the flag'sdeprecatedControllersarray, it short-circuits all its logic on unlock and makes zero RPC calls. TheAssetsControllerhandles balances from that point. This removes theeth_blockNumber+eth_callpair per network.The end result: 0 RPC calls on wallet unlock. They only happen when the user actually does something that needs the RPC - at which point, if something is broken, the banner shows up exactly as before.
Changelog
CHANGELOG entry: [performance] remove 30+ unnecessary RPC API calls on wallet unlock
Related issues
Fixes: https://consensyssoftware.atlassian.net/browse/ASSETS-3622
Manual testing steps
Screenshots/Recordings
Before
After
Pre-merge author checklist
Performance checks (if applicable)
trace()for usage andaddTokenfor an exampleFor performance guidelines and tooling, see the Performance Guide.
Pre-merge reviewer checklist
Note
Medium Risk
Changes startup network UX (no proactive probes) and balance-fetch path when AccountTracker is flagged deprecated; incorrect Unknown handling or flag wiring could hide real RPC issues or skip needed RPC balances.
Overview
Stops eager RPC health probing on wallet mount by removing the
useNetworkConnectionBannereffect that calledEngine.lookupEnabledNetworks(), so enabled networks are no longer probed just to seed the connection banner timers.To avoid false-positive banners after that change, networks with
NetworkStatus.Unknown(unprobed or stale persisted metadata) are skipped the same way asAvailablewhen evaluating failures; tests now assert no mount probe and no banner when all statuses areUnknown.AccountTrackerController init now passes
isDeprecatedfrom theassetsUnifyStatefeature flag (selectIsControllerDeprecated('AccountTrackerController')), so when the controller is listed as deprecated it can short-circuit unlock-time RPC balance work in favor of unified assets handling.Reviewed by Cursor Bugbot for commit afba082. Bugbot is set up for automated code reviews on this repo. Configure here.