Skip to content

fix(assets-controller): avoid recomputing getStateForTransactionPay on every transaction state change#9546

Open
Kureev wants to merge 2 commits into
mainfrom
kureev/memoize-format-state-for-transaction-pay
Open

fix(assets-controller): avoid recomputing getStateForTransactionPay on every transaction state change#9546
Kureev wants to merge 2 commits into
mainfrom
kureev/memoize-format-state-for-transaction-pay

Conversation

@Kureev

@Kureev Kureev commented Jul 17, 2026

Copy link
Copy Markdown

Explanation

formatStateForTransactionPay converts the full AssetsController state into the legacy five-controller shape for transaction-pay-controller, running toChecksumAddress (keccak256) for every asset and parseCaipAssetType for every asset ID on every call. It is exposed as the AssetsController:getStateForTransactionPay messenger action, which transaction-pay-controller invokes on every TransactionController:stateChange — many times during a single transaction approval, and at least twice per event through messenger delegation — while its inputs only change when the assets pipeline updates.

In a CPU profile of MetaMask Mobile (RC) captured while triggering a transaction approval in the MetaMask Pay deposit flow, keccak256 via toChecksumAddress accounted for 10.4% of all samples (~23% of active CPU), the single largest active bucket, with GC pauses (4.1%) as a symptom of the per-event object churn. See #9545 for the full breakdown.

This change memoizes the last result on input identity: the assetsBalance / assetsInfo / assetsPrice state slices and networkConfigurationsByChainId are compared by reference (BaseController state updates are immutable, so unchanged slices retain identity), selectedCurrency by value, and the per-call-rebuilt accounts array and nativeAssetIdentifiers record element-wise. Repeat calls with unchanged inputs return the cached object; any real input change recomputes. The conversion body is unchanged, moved to an internal computeStateForTransactionPay.

MetaMask Mobile currently carries this fix as a Yarn patch (MetaMask/metamask-mobile#33466), which can be dropped once this lands and a release is picked up.

References

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them

Note

Low Risk
Performance-only change with identical output when inputs change; single-entry module cache is appropriate for the hot path but assumes callers pass stable slice references as today.

Overview
formatStateForTransactionPay (used by AssetsController:getStateForTransactionPay) now memoizes the last result when inputs are unchanged, so repeated calls during TransactionController:stateChange no longer redo the full legacy conversion (including per-asset toChecksumAddress / keccak256).

Cache hits compare state slices by reference (assetsBalance, assetsInfo, assetsPrice, networkConfigurationsByChainId), selectedCurrency by value, and accounts / nativeAssetIdentifiers with shallow equality; any real change still runs computeStateForTransactionPay. The conversion logic is unchanged, only split behind the cache layer.

Exports the new FormatStateForTransactionPayParams type and adds memoization unit tests plus a changelog entry.

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

@Kureev Kureev self-assigned this Jul 17, 2026
@Kureev
Kureev marked this pull request as ready for review July 17, 2026 13:56
@Kureev
Kureev requested review from a team as code owners July 17, 2026 13:56
@Kureev
Kureev temporarily deployed to default-branch July 17, 2026 13:56 — with GitHub Actions Inactive
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

formatStateForTransactionPay runs keccak256 per asset on every TransactionController:stateChange

1 participant