Skip to content

chore: [performance] remove 30+ unnecessary RPC API calls on wallet unlock #33379

Open
juanmigdr wants to merge 1 commit into
chore/support-deprecating-each-controllerfrom
chore/remove-30-plus-api-calls-on-wallet-unlock
Open

chore: [performance] remove 30+ unnecessary RPC API calls on wallet unlock #33379
juanmigdr wants to merge 1 commit into
chore/support-deprecating-each-controllerfrom
chore/remove-30-plus-api-calls-on-wallet-unlock

Conversation

@juanmigdr

@juanmigdr juanmigdr commented Jul 15, 2026

Copy link
Copy Markdown
Member

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:
image

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!
image

Here's what each of those three calls was doing:

  • eth_blockNumber - called by AccountTrackerController's RPC balance fetcher before making a Multicall3 call. It needs a block reference to pass to eth_call.
  • eth_call to Multicall3 (0xca11bde...) - called by AccountTrackerController to fetch the user's native ETH balance on chains not covered by the Accounts API v4, using a batched aggregate3 call.
  • eth_blockNumber + eth_getBlockByNumber - called by Engine.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 AssetsController takes over balance fetching entirely when the assetsUnifyState flag is on. The old AccountTrackerController was still running its unlock handler and hitting RPC on chains the Accounts API should have covered, because isDeprecated was 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, NetworkController fires rpcEndpointChainUnavailable and 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. useNetworkConnectionBanner was calling Engine.lookupEnabledNetworks() on every mount, which probed all enabled networks just to seed networksMetadata before the banner's 5s/30s timers fired. Removing it eliminates the eth_blockNumber + eth_getBlockByNumber pair per network. One small side effect: on a fresh app start, all networks begin with NetworkStatus.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 skip Unknown status the same way we skip Available, so the banner only triggers on real, confirmed failures.

2. Wire AccountTrackerController.isDeprecated to the assetsUnifyState flag. When the controller is listed in the flag's deprecatedControllers array, it short-circuits all its logic on unlock and makes zero RPC calls. The AssetsController handles balances from that point. This removes the eth_blockNumber + eth_call pair 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

Feature: my feature name

  Scenario: user [verb for user action]
    Given [describe expected initial app state]

    When user [verb for user action]
    Then [describe expected outcome]

Screenshots/Recordings

Before

After

Pre-merge author checklist

Performance checks (if applicable)

  • I've tested on Android
    • Ideally on a mid-range device; emulator is acceptable
  • I've tested with a power user scenario
    • Use these power-user SRPs to import wallets with many accounts and tokens
  • I've instrumented key operations with Sentry traces for production performance metrics

For performance guidelines and tooling, see the Performance Guide.

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.

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 useNetworkConnectionBanner effect that called Engine.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 as Available when evaluating failures; tests now assert no mount probe and no banner when all statuses are Unknown.

AccountTrackerController init now passes isDeprecated from the assetsUnifyState feature 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.

@juanmigdr juanmigdr added the area-performance Issues relating to slowness of app, cpu usage, and/or blank screens. label Jul 15, 2026

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ 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) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit afba082. Configure here.

@github-actions

Copy link
Copy Markdown
Contributor

🔍 Smart E2E Test Selection

  • Selected E2E tags: SmokeAccounts, SmokeConfirmations, SmokeNetworkAbstractions, SmokeNetworkExpansion, SmokeSwap, SmokeStake, SmokeWalletPlatform, SmokeMoney, SmokePerps, SmokeMultiChainAPI, SmokePredictions, SmokeSeedlessOnboarding, SmokeBrowser, SmokeSnaps
  • Selected Performance tags: None (no tests recommended)
  • Risk Level: high
  • AI Confidence: 100%
click to see 🤖 AI reasoning details

E2E Test Selection:
Hard rule (global-infrastructure-change): Global infrastructure changed: app/components/hooks/useNetworkConnectionBanner/useNetworkConnectionBanner.test.tsx, app/components/hooks/useNetworkConnectionBanner/useNetworkConnectionBanner.ts. Running all tests.

Performance Test Selection:
No performance impact detected

View GitHub Actions results

@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-performance Issues relating to slowness of app, cpu usage, and/or blank screens. size-S team-assets

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant