Skip to content

perf: token details performance tweaking v1#30214

Merged
Prithpal-Sooriya merged 6 commits into
mainfrom
perf/improve-token-details-perf-v1
May 15, 2026
Merged

perf: token details performance tweaking v1#30214
Prithpal-Sooriya merged 6 commits into
mainfrom
perf/improve-token-details-perf-v1

Conversation

@Prithpal-Sooriya
Copy link
Copy Markdown
Contributor

@Prithpal-Sooriya Prithpal-Sooriya commented May 14, 2026

Description

Audited the token details page.

  • The token details page had the JS thread consistently drop drown from 60fps to 30/40fps every 5-ish seconds.
  • Profiling showed that every 5-ish seconds some hooks and components clogged JS thread (many frames with +100ms before rendering - see image)

This was not due to the chart - the chart is buttery smooth!
Its due to our state update (we have a few things, even when idle, that produce state updates) and poor selector + hook design.
Biggest culprit to slow down was the useSwapBridgeNavigation - yes... a navigation management file 😄

Changes:

  1. Small memoization wins on some hooks
  2. Atomised the token actions instead of maintaining a god hook. Easier to test, debug, audit perf, and consume what you need!
  3. Optimised the useSwapBridgeNavigation flow, this indirectly consumed some really heavy computations that were never used for the navigation process. More details on useSwapBridgeNavigation audit below - we may need a sync with bridge team to optimise.
`useSwapBridgeNavigation` audit summary

The hook itself looks small, but it transitively pulls in very heavy hook subtrees that all run on every redux state change.
Previous flow:
useSwapBridgeNavigation(...) →
  useInitialBridgeTokens() →
   useBalancesByAssetId() →
    useTokensWithBalance() (biggest cost)

useTokensWithBalance contains a useMemo which is practically useless, it takes in many dependencies, all of which are subject to frequent changes. The logic contain in the useMemo also is not cheap - many bigNumber.js calculations on a loop of tokens.

Worst part is the navigation flow never even uses this data, waste of CPU cycles.

Audit steps going forward:

  1. audit to see if these balances are "actually" needed, there may be more cases that don't require this data.
  2. audit to see if we really need all balances for tokens, or can this be indexed or slices to a smaller subset (e.g. maybe only calc for a single token, or subset of tokens on a chain).

Changelog

CHANGELOG entry: perf: token details performance tweaking

Related issues

Fixes: N/A

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

Screenshot 2026-05-14 at 18 16 33 Screenshot 2026-05-14 at 22 12 30

After

Screenshot 2026-05-14 at 22 15 15

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
Refactors Token Details action handling and bridge-token prefetching, which can subtly affect navigation/analytics and swap routing logic. Changes are well-covered by new unit tests but touch user-facing flows.

Overview
Improves Token Details and swap/bridge navigation performance by removing heavy, always-on computations from common hooks and shifting them to lightweight selectors/callbacks.

Introduces useFetchPopularTokens as a standalone cached fetcher for Bridge getTokens/popular, and updates useInitialBridgeTokens + useSwapBridgeNavigation to use it (prefetch now uses selectAllowedChainRanking and avoids pulling in balance-heavy hook trees).

Splits Token Details actions into atomic hooks in useTokenAtomicActions (buy/send/receive/swap) and composes them via a new useStickyTokenActions for the sticky footer; useTokenActions is simplified to page actions only. Adds selectHasEligibleSwapSource selector for a cheap swap-eligibility check, and memoizes the return value of useCurrentNetworkInfo to reduce churn. Extensive tests are added/updated to match the new hook boundaries and behaviors.

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

there are places that do not destructure this object so passing around causes renders on components and hooks
will replace the in-hook computations that require complex state maangement. Use reselect memoisation
this was really imperformant, and made it hard to tease out performance insights. Now the hooks are atomic - easier to test, debug, and reduces scope of what components need to consume (they dont need entire return type, only things they need)
This hook had a chain of other hooks and selectors that are incredibly expensive.

The full path found was:
useSwapBridgeNavigation(...) → useInitialBridgeTokens() → useBalancesByAssetId() → useTokensWithBalance() (biggest cost)

I was not able to improve performance of `useTokensWithBalance`, but instead cleaned up the `useSwapBridgeNavigation(...) → useInitialBridgeTokens()` link - as navigation does not need the complex state computations.

Created an isolated `useFetchPopularTokens` which allows the navigation to be decoupled from the rest of the expensive logic.

NOTE - we need to sync with the bridge team to see if the useTokensWithBalance is needed.
@Prithpal-Sooriya Prithpal-Sooriya requested review from a team as code owners May 14, 2026 21:40
@Prithpal-Sooriya Prithpal-Sooriya added the area-performance Issues relating to slowness of app, cpu usage, and/or blank screens. label May 14, 2026
@github-actions
Copy link
Copy Markdown
Contributor

CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes.

Comment thread app/components/hooks/useCurrentNetworkInfo.ts
Comment on lines +164 to +176
const enabledChainRanking = useSelector(selectAllowedChainRanking);

const fetchPopularTokens = useFetchPopularTokens();
const prefetchPopularTokens = useCallback(() => {
if (!enabledChainRanking?.length) {
return;
}
fetchPopularTokens({
chainIds: enabledChainRanking.map(
(chain: { chainId: CaipChainId }) => chain.chainId,
),
}).catch(() => undefined);
}, [enabledChainRanking, fetchPopularTokens]);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fetching tokens have been decoupled from the really expensive useInitialBridgeTokens() hook.

Navigation does not need all the data coming from that expensive hook, it only needs a fetch method (we are prefetching before navigating).

* Lightweight fetcher hook for the Bridge `/getTokens/popular` endpoint.
* @returns A callback that performs the cached fetch for the supplied
*/
export const useFetchPopularTokens = () => {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hook logic is a lift from the useInitialBridgeTokens hook.

[includeAssetsObject],
);

useEffect(() => {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This logic is decoupled and placed into useFetchPopularTokens hook mentioned above.

Comment on lines +118 to +123
const { onBuy, onSwap, hasEligibleSwapTokens, networkModal } =
useStickyTokenActions({
token,
currentTokenBalance,
sourcePage,
});
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Atomised the god hook useTokenActions, now we have composability and only use return values we need.

};
};

export default useStickyTokenActions;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

sticky actions hooks is a very thin wrapper that composes the different atomic handlers.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

the page action hooks is now a very thin wrapper that composes the different atomic handlers.

* 2. Native token with highest USD value across other chains
* 3. Fallback: highest USD-value token across any chain
*/
export const computeBuySourceToken = (
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

note for self - exported so other teams can leverage (the "smart defaults" are a really nice win and can be used on other swap navigation flows)

Comment thread app/components/UI/Bridge/hooks/useFetchPopularTokens.ts
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

atomic action hooks. Mostly gutted the existing logic from useTokenActions god hook.
Some tweaks for performance.

  • instead of useSelectors at top level in hooks, we can actually dynamically pick state inside callbacks. Keeps useCallback references for longer. Useful to prevent callback re-renders

Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

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.

There are 2 total unresolved issues (including 1 from previous review).

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 d215889. Configure here.

Comment thread app/components/UI/TokenDetails/hooks/useTokenAtomicActions.ts
Comment thread app/components/UI/Bridge/hooks/useSwapBridgeNavigation/index.ts Outdated
Co-authored-by: Prithpal Sooriya <prithpal.sooriya@gmail.com>
@github-actions
Copy link
Copy Markdown
Contributor

🔍 Smart E2E Test Selection

  • Selected E2E tags: SmokeSwap, SmokeConfirmations, SmokeWalletPlatform, SmokeMoney
  • Selected Performance tags: None (no tests recommended)
  • Risk Level: medium
  • AI Confidence: 80%
click to see 🤖 AI reasoning details

E2E Test Selection:
The PR refactors several key hooks in the Bridge and TokenDetails areas:

  1. Bridge hooks (useFetchPopularTokens, useInitialBridgeTokens, useSwapBridgeNavigation): The popular token fetching was extracted into a dedicated hook. useSwapBridgeNavigation now uses useFetchPopularTokens directly with a new prefetchPopularTokens callback. E2E tests in swap-trending-tokens.spec.ts and unified-ui-wallet-actions.spec.ts mock the /getTokens/popular endpoint via mockSwapPopularTokens. These tests exercise the swap/bridge navigation flow that was refactored → SmokeSwap required.

  2. TokenDetails hooks (useTokenActions, useTokenAtomicActions, useStickyTokenActions, TokenDetails.tsx, TokenDetailsStickyFooter.tsx): The monolithic useTokenActions was split into atomic hooks. The sticky footer now uses useStickyTokenActions with a new selectHasEligibleSwapSource selector. The send flow (useHandleOnSend) and buy flow (useHandleOnBuy) were refactored with behavioral changes (e.g., useStore instead of useSelector for lazy state reads, richer analytics data for buy). This affects token send → SmokeConfirmations, token buy → SmokeMoney, and general token detail interactions → SmokeWalletPlatform.

  3. useCurrentNetworkInfo: Wrapped return in useMemo for performance. Used in useSwapBridgeNavigation, Wallet view, Activity view — a broad hook but the change is a pure optimization (no behavioral change). Covered by the above tags.

  4. assets-list.ts: New selectHasEligibleSwapSource selector used by useStickyTokenActions to determine swap eligibility. This is a new selector with no behavioral regression risk to existing selectors.

Per tag descriptions: SmokeSwap → also select SmokeConfirmations (transaction confirmations are part of the flow). SmokeMoney → also select SmokeWalletPlatform when changes touch wallet home or actions entry to buy/sell.

Performance Test Selection:
The changes are primarily refactoring hooks (extracting logic into smaller hooks, splitting monolithic hooks into atomic ones) and a memoization optimization in useCurrentNetworkInfo. While the useMemo addition in useCurrentNetworkInfo could theoretically improve render performance, these are code organization changes rather than changes that would meaningfully impact measurable performance metrics like app launch time, account list rendering, or swap flow timing. No performance test tags are warranted.

View GitHub Actions results

@sonarqubecloud
Copy link
Copy Markdown

@bergarces
Copy link
Copy Markdown
Contributor

Quality Gate Passed Quality Gate passed

Issues 3 New issues 0 Accepted issues

Measures 0 Security Hotspots 88.1% Coverage on New Code 0.0% Duplication on New Code

See analysis details on SonarQube Cloud

Apparently there are some unneeded assertions. Worth removing them if true.

@Prithpal-Sooriya
Copy link
Copy Markdown
Contributor Author

@bergarces good callout, these asserts came from mostly unchanged code that I gutted from the original god hook.
I think I can do a fast follow on this in a diff PR.

@Prithpal-Sooriya Prithpal-Sooriya added this pull request to the merge queue May 15, 2026
Merged via the queue into main with commit afc5b0e May 15, 2026
182 of 185 checks passed
@Prithpal-Sooriya Prithpal-Sooriya deleted the perf/improve-token-details-perf-v1 branch May 15, 2026 10:46
@github-actions github-actions Bot locked and limited conversation to collaborators May 15, 2026
@metamaskbotv2 metamaskbotv2 Bot added the release-7.79.0 Issue or pull request that will be included in release 7.79.0 label May 15, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-performance Issues relating to slowness of app, cpu usage, and/or blank screens. release-7.79.0 Issue or pull request that will be included in release 7.79.0 size-XL team-assets

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants