Skip to content

perf(account-selector): cut list render churn and per-cell selector cost - #33877

Merged
gantunesr merged 3 commits into
mainfrom
gar/perf/account-list
Jul 28, 2026
Merged

perf(account-selector): cut list render churn and per-cell selector cost#33877
gantunesr merged 3 commits into
mainfrom
gar/perf/account-list

Conversation

@gantunesr

@gantunesr gantunesr commented Jul 27, 2026

Copy link
Copy Markdown
Member

Description

The AccountSelector should render within the navigation animation so switching accounts feels instant. Profiling a power-user wallet (45 account groups) on a Samsung Galaxy A22 showed the open was dominated by the per-cell render pipeline, not by data/balance computation. Two low-risk fixes target that:

  1. Memoize the selectedAccountGroups array passed to the list. Previously a new [selectedAccountGroup] literal was allocated on every parent render, breaking React.memo on the list and rebuilding its selectedIdSet / renderItem — forcing full re-renders of the visible cells across FlashList's fill passes. A stable reference lets those passes do far less redundant work.
  2. Collapse the per-cell icon-seed selector. Each cell instantiated its own selectIconSeedAddressByAccountGroupId deep-equal selector factory, so N cells each deep-compared the full account tree state on every store update. This is replaced with one shared map selector (selectAllAccountGroupIconSeedAddresses) read O(1) per cell. The chainId-only selectInternalAccountByAccountGroupAndScope subscription is moved into a child rendered only when a chainId is provided, so it never runs on the account list (which passes no chainId).

Measured deltas (dev build + React.Profiler, so absolute wall-clock is inflated — the relative deltas are the signal; Samsung Galaxy A22, 45 account groups):

Metric Before After
Worst single render commit ~160 ms ~80 ms
Total list React render ~630 ms ~390 ms
Tap → settled ~1300 ms ~770 ms

The array memoization (fix 1) contributes almost all of the win; the selector change (fix 2) is a smaller, targeted reduction plus removes an unused per-cell subscription. No behavior change.

Changelog

CHANGELOG entry: N/A

Related issues

Refs: https://consensyssoftware.atlassian.net/browse/MUL-2010

Manual testing steps

Feature: Account list performance

  Scenario: Opening the account list with many accounts
    Given a wallet imported with many accounts (power-user SRP, e.g. 50 accounts)
    When the user taps the account picker in the wallet header
    Then the account list opens and renders correctly under the navigation animation
    And each account row shows its name, avatar, balance and selection state as before

Screenshots/Recordings

N/A

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
    • Profiling for this PR was done locally with React.Profiler; the ShowAccountList Sentry span already exists. Production trace instrumentation not added here.

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.

🤖 Generated with Claude Code


Note

Low Risk
Render-performance refactor with parity tests; no auth, payments, or data-model changes beyond selector wiring.

Overview
Improves account picker open time for wallets with many account groups by reducing unnecessary list and cell re-renders, with no intended UI or behavior change.

AccountSelector now passes a memoized selectedAccountGroups array into MultichainAccountSelectorList instead of a fresh [selectedAccountGroup] on every parent render, so memoized list props (selectedIdSet, renderItem) stay stable.

AccountCell reads avatar icon seed addresses from a new shared Redux selector selectAllAccountGroupIconSeedAddresses (one map build per store update, O(1) lookup per row) instead of each row creating its own deep-equal selectIconSeedAddressByAccountGroupId factory. Network-specific address text and the selectInternalAccountByAccountGroupAndScope subscription move into AccountNetworkAddressRow, which only mounts when chainId is set—so the main account list avoids that subscription entirely. Network badge images are derived from chainId without an extra store read.

Selector unit tests cover the new map selector; connected-list tests mock it alongside existing account selector mocks.

Reviewed by Cursor Bugbot for commit 2181ddc. Bugbot is set up for automated code reviews on this repo. Configure here.

Two low-risk changes that reduce the AccountSelector ("Account List")
open cost, validated by React.Profiler on a Samsung Galaxy A22 with a
180-account / 45-group wallet:

- Memoize the `selectedAccountGroups` array passed to the list so a parent
  re-render no longer breaks the list's memoization (rebuilding selectedIdSet
  and renderItem, forcing full re-renders of the visible cells).
- Replace the per-cell `selectIconSeedAddressByAccountGroupId` deep-equal
  selector factory with a single shared map selector
  (`selectAllAccountGroupIconSeedAddresses`) read O(1) per cell, and move the
  chainId-only `selectInternalAccountByAccountGroupAndScope` subscription into
  a child rendered only when a chainId is provided (never on the account list).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@metamask-ci metamask-ci Bot added the team-accounts-framework Accounts team label Jul 27, 2026
@metamask-ci

metamask-ci Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

PR template — items to address before "Ready for review"

Warnings — informational, address before merging:

  • Pre-merge author checklist has unchecked items (e.g. "I've instrumented key operations with Sentry traces for production performance metrics"). Every box must be consciously checked — see docs/readme/ready-for-review.md.

See docs/readme/ready-for-review.md for the full Definition of Ready for Review.

@gantunesr gantunesr added the no-changelog no-changelog Indicates no external facing user changes, therefore no changelog documentation needed label Jul 27, 2026
@gantunesr
gantunesr marked this pull request as ready for review July 27, 2026 20:55
@gantunesr
gantunesr requested review from a team as code owners July 27, 2026 20:55
@github-actions github-actions Bot added the risk:medium AI analysis: medium risk label Jul 27, 2026
…n mock

- Add unit tests for `selectAllAccountGroupIconSeedAddresses` (EVM preference,
  non-EVM fallback, multi-wallet map, empty/unresolved groups, missing tree).
- Add the new selector to MultichainAccountsConnectedList's full module mock so
  AccountCell (which now reads it) no longer calls undefined.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added risk:low AI analysis: low risk and removed risk:medium AI analysis: medium risk labels Jul 27, 2026
@sonarqubecloud

Copy link
Copy Markdown

@javiergarciavera

Copy link
Copy Markdown
Contributor

@metamaskbot app-profiling-check --all --run 30313222010

@github-actions

Copy link
Copy Markdown
Contributor

🔬 App profiling check started for run 30313222010. View workflow runs

@github-actions

Copy link
Copy Markdown
Contributor

🔬 App Profiling Check: Import SRP with +50 accounts, SRP 1, SRP 2, SRP 3

Device: Google Pixel 8 Pro (v14.0) · Platform: Android

Current: run 30313222010

⚠️ No green baseline found on main (within recent aggregated-reports retention) for this scenario + device.

Current profilingSummary

{
  "status": "success",
  "issues": 2,
  "criticalIssues": 1,
  "appSizeMb": 318.21,
  "cpu": {
    "avg": 4.8,
    "max": 22.18,
    "unit": "%"
  },
  "memory": {
    "avg": 682.78,
    "max": 719.81,
    "unit": "MB"
  },
  "battery": {
    "total": 3.53,
    "percentage": 2.291,
    "unit": "mAh"
  },
  "diskIO": {
    "reads": 0,
    "writes": 29170,
    "unit": "kb"
  },
  "networkIO": {
    "upload": 3833.02,
    "download": 10601.28,
    "unit": "kb"
  },
  "uiRendering": {
    "slowFrames": 17.71,
    "frozenFrames": 0,
    "anrs": 0
  }
}

🔬 App Profiling Check: Fresh SRP wallet creation performance

Device: Google Pixel 8 Pro (v14.0) · Platform: Android

Current: run 30313222010

⚠️ No green baseline found on main (within recent aggregated-reports retention) for this scenario + device.

Current profilingSummary

{
  "status": "success",
  "issues": 3,
  "criticalIssues": 3,
  "appSizeMb": 318.21,
  "cpu": {
    "avg": 9.73,
    "max": 22.72,
    "unit": "%"
  },
  "memory": {
    "avg": 567.94,
    "max": 837.66,
    "unit": "MB"
  },
  "battery": {
    "total": 2.14,
    "percentage": 1.949,
    "unit": "mAh"
  },
  "diskIO": {
    "reads": 0,
    "writes": 33740,
    "unit": "kb"
  },
  "networkIO": {
    "upload": 1552.21,
    "download": 4280.1,
    "unit": "kb"
  },
  "uiRendering": {
    "slowFrames": 43.88,
    "frozenFrames": 0,
    "anrs": 0
  }
}

@github-actions github-actions Bot added risk:medium AI analysis: medium risk and removed risk:low AI analysis: low risk labels Jul 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🔍 Smart E2E Test Selection

  • Selected E2E tags: SmokeAccounts, SmokeConfirmations, SmokeNetworkAbstractions, SmokeNetworkExpansion, SmokeMultiChainAPI, SmokeWalletPlatform
  • Selected Performance tags: @PerformanceAccountList
  • Risk Level: medium
  • AI Confidence: 82%
click to see 🤖 AI reasoning details

E2E Test Selection:
The PR introduces performance optimizations for the multichain account list rendering:

  1. accounts.ts: New selectAllAccountGroupIconSeedAddresses selector computes icon seed addresses for ALL account groups in a single pass (O(1) lookup per cell vs. N deep-equal selectors). This is a new export that replaces per-cell selector instantiation.

  2. AccountCell.tsx: Refactored to use the shared map selector instead of per-cell selectIconSeedAddressByAccountGroupId. Also extracted AccountNetworkAddressRow as a separate component to isolate store subscriptions (only subscribes when chainId is provided).

  3. AccountSelector.tsx: Added useMemo to stabilize the selectedAccountGroups array reference, preventing unnecessary re-renders of the memoized list.

  4. Test files updated to reflect the new selector mock.

Why these tags:

  • SmokeAccounts: AccountCell and AccountSelector are core account management components. Account list, account switching, multi-account workflows are directly affected.
  • SmokeConfirmations: AccountSelector is used in confirmation flows for account selection during transactions/signatures.
  • SmokeNetworkAbstractions: MultichainAccountsConnectedList (which uses AccountCell) is part of dApp chain permission flows.
  • SmokeNetworkExpansion: AccountCell handles multi-chain account groups (EVM + Solana), directly relevant to non-EVM account display.
  • SmokeMultiChainAPI: Connected accounts list is part of CAIP-25 session management where account cells are displayed.
  • SmokeWalletPlatform: Account management is a core wallet platform feature.

The changes are refactors/optimizations rather than feature additions, but they touch critical rendering paths for account selection used across many flows. Risk is medium because the logic is preserved but the selector architecture changed.

Performance Test Selection:
The PR's primary motivation is performance optimization of the account list rendering. The new selectAllAccountGroupIconSeedAddresses selector collapses N per-cell deep-equal selectors into a single shared memoized selector, and AccountSelector.tsx stabilizes array references to prevent unnecessary re-renders. These changes directly target account list rendering performance — exactly what @PerformanceAccountList measures (account selector rendering, multi-account scenarios, token load impact). Running this performance tag will validate that the optimization actually improves render times.

View GitHub Actions results

@github-actions

Copy link
Copy Markdown
Contributor

⚡ Performance Test Results

ℹ️ Performance test results are currently non-blocking and will not block this PR.

1 test failed · 2 tests · 1 device

📱 Devices tested (1)

Android: Google Pixel 8 Pro (v14.0)

❌ Failed Tests (1)

🔬 To compare BrowserStack app profiling vs the last green run on main, copy/paste the App profiling check command for a failed test as a PR comment.

@Accounts-team

Test Platform Device Reason Recording App profiling check
Import SRP with +50 accounts, SRP 1, SRP 2, SRP 3 Android Google Pixel 8 Pro (v14.0) Test error 📹 Watch @metamaskbot app-profiling-check --test "Import SRP with +50 accounts, SRP 1, SRP 2, SRP 3" --platform Android --device "Google Pixel 8 Pro+14.0" --run 30362507171
Compare all failed scenarios

@metamaskbot app-profiling-check --all --run 30362507171

✅ Passed Tests (1)
Test Platform Device Duration Team Recording
Account creation after fresh install Android Google Pixel 8 Pro (v14.0) 2.76s @metamask-onboarding-team 📹 Watch

Branch: gar/perf/account-list · Build: Normal · Commit: 46aa659 · View full run

@vinnyhoward vinnyhoward 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.

Oh wow nice improvement. LGTM!

@gantunesr
gantunesr added this pull request to the merge queue Jul 28, 2026
Merged via the queue into main with commit caf4b9d Jul 28, 2026
489 of 494 checks passed
@gantunesr
gantunesr deleted the gar/perf/account-list branch July 28, 2026 18:05
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 28, 2026
@metamask-ci metamask-ci Bot added the release-8.6.0 Issue or pull request that will be included in release 8.6.0 label Jul 28, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

no-changelog no-changelog Indicates no external facing user changes, therefore no changelog documentation needed release-8.6.0 Issue or pull request that will be included in release 8.6.0 risk:medium AI analysis: medium risk size-M team-accounts-framework Accounts team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants