MetaMask / MetaMask/metamask-mobile

Stabilize `selectEarnTokens` and `selectBridgeHistoryForAccount` (allocating result functions on hot paths)

Open
#31,496 2 comments 0 reactions 0 assignees View on GitHub
area-performance Sev2-normal ta-needs-engineer-escalation ta-triaged team-mobile-platform
Dominant language
TypeScript
Stars
3k
Forks
1.7k
Avg merge
1d 14h
Merged PRs (30d)
669

Description

> **Performance audit finding** · Severity: **Medium** · Effort: Small-Medium · Fix risk: Simple · Test safety net: Partial
> Owner: Earn / Bridge CODEOWNERS
> Files: `app/selectors/earnController/earn/index.ts:122`, `app/selectors/bridgeStatusController/index.ts:14`

### What is this about?

A per-selector triage of the ten highest-subscription components (51 selector definitions read; ruled-out verdicts in this issue's edit history and #31493) left two selectors standing — both allocate heavily on hot, always-visible paths:

1. **`selectEarnTokens`** (`app/selectors/earnController/earn/index.ts:122`) — a `createDeepEqualSelector` that rebuilds a large nested structure (earn tokens × markets × chains via `.reduce`) on every recompute, and deep-compares its sizable inputs on **every check**. Consumed by both `WalletActions` and `TradeWalletActions`, which sit on the primary action surfaces.
2. **`selectBridgeHistoryForAccount`** (`app/selectors/bridgeStatusController/index.ts:14`) — a plain `createSelector` whose result function allocates a fresh object via `.reduce` per recompute, handing consumers a new reference whenever the upstream recomputes even if the history is unchanged.

**Why it matters**

Both costs scale with power-user data (token count, bridge history length) and run at the batched-flush cadence for as long as their screens are mounted. These are the *confirmed* offenders out of 80+ candidate reads — the rest were verified stable/cheap and ruled out.

### Scenario

N/A — see Technical Details.

### Design

N/A — internal performance change; no UI/design impact.

### Technical Details

For `selectEarnTokens`: narrow the inputs to the slices that actually change, split the mega-aggregation into composed memoized layers so a price tick doesn't rebuild the world, and verify whether the deep-equal input compare is still needed once inputs are narrow (see the reference-stability contract test in the `mms-performance` skill's `mm-selector-cascade`). For `selectBridgeHistoryForAccount`: stabilize the output with a `resultEqualityCheck` or narrow inputs so recomputes track actual history changes.

### Threat Modeling Framework

N/A — performance-only change; behavior is preserved, no new data flow / trust boundary / attack surface.

### Acceptance Criteria

- Both selectors return the same reference across unrelated flushes with unchanged data (assert `toBe` across two equal-content states).
- Profiler on a power-user profile: `WalletActions`/`TradeWalletActions` and bridge history consumers stop re-rendering on unrelated dispatches.

### References

- Files: `app/selectors/earnController/earn/index.ts:122`, `app/selectors/bridgeStatusController/index.ts:14`
- Related: #31499 (`selectLocalTransactions`, split out as dedicated), #31492 (per-row parameterized misses), #31493 (Wallet home ruled out)
- Source: `mms-performance` per-selector triage (MetaMask/skills#49)
- Status: **UNVALIDATED** (static evidence; needs profiler confirmation)

Contributor guide

Open the contributing guide

Research direction

Read app/selectors/earnController/earn/index.ts:122 and app/selectors/bridgeStatusController/index.ts:14, then review the reference-stability contract in the mms-performance skill's mm-selector-cascade. Preserve selector behavior while ensuring both return the same reference across unrelated flushes with unchanged data; confirm the WalletActions, TradeWalletActions, and bridge history consumers stop re-rendering on unrelated dispatches.

Written by the indexing model from the issue text.

Assessment

Tech stack
react-native, typescript
Domain
frontend, performance
Issue type
Refactor
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.