Skip to content

Conversation

salimtb
Copy link
Contributor

@salimtb salimtb commented Aug 4, 2025

You're absolutely right! Let me update that section to be more precise about the fallback strategy:

Explanation

Current State and Problem

Previously, the TokenBalancesController fetched token balances using individual RPC calls for each token-account combination. This approach was inefficient and slow, especially for users with multiple accounts and many tokens across different networks. Each balance check required a separate balanceOf call, leading to:

  • High number of RPC requests
  • Slower balance updates
  • Potential rate limiting issues
  • Poor user experience during balance refreshes

Solution

This PR implements a two-tier balance fetching strategy with intelligent fallback:

  1. Primary: Accounts API - Uses MetaMask's external API service for supported networks (faster, more efficient)
  2. Fallback: RPC with Aggregate3 - Uses Multicall3's aggregate3 function for efficient batch RPC calls when:
    • The chain is not supported by the Accounts API
    • The Accounts API service is unavailable or fails
    • Any other API-related errors occur

Key Technical Changes

Smart Fallback Architecture

  • TokenBalancesController first attempts to fetch balances via the Accounts API for supported networks
  • For chains not supported by the API or when the API fails, it automatically falls back to RPC calls using aggregate3
  • Successfully processed chains are removed from subsequent fetchers, ensuring no duplicate work
  • Each fetcher only processes chains it can handle

Accounts API Integration

  • Leverages MetaMask's backend infrastructure for faster balance retrieval
  • Supports a curated list of networks (SUPPORTED_NETWORKS_ACCOUNTS_API_V4)
  • Handles multi-account and multi-chain requests efficiently

Aggregate3 Fallback Implementation

When the Accounts API can't handle certain networks, the system uses Multicall3's aggregate3 function to:

  • Batch multiple balanceOf calls into a single RPC request
  • Handle both ERC20 token balances and native token balances
  • Support staking contract balance queries
  • Process results efficiently with individual failure handling

Performance Optimizations

  • API-first strategy: Fastest possible retrieval for supported networks
  • Efficient RPC fallback: Up to 300 calls per batch to avoid gas/size limits when API isn't available
  • Serial batch execution: Processes batches sequentially to prevent overwhelming the RPC
  • Smart chain routing: Each chain uses the most appropriate fetching method
  • Selective updates: Only updates state when balance changes are detected

Technical Details

The fallback mechanism ensures comprehensive network coverage:

  • Supported networks → Accounts API (fastest)
  • Unsupported networks or API failures → RPC with aggregate3 (still much faster than individual calls)

The aggregate3 function provides significant improvements over individual RPC calls:

  • Each call can fail individually without affecting others (allowFailure: true)
  • Supports different call types (ERC20, native, staking) in the same batch
  • Automatically falls back to individual calls if multicall fails entirely

Impact

  • Optimal performance: Uses fastest available method for each network
  • Complete network coverage: No network is left without efficient balance fetching
  • Reduced RPC calls: For non-API networks, reduces from N individual calls to 1 call per batch of up to 300 operations
  • Better reliability: Multiple fetching strategies ensure balance data is always available
  • Graceful degradation: Seamless fallback when services are unavailable

References

Addresses performance issues with token balance fetching by implementing Accounts API integration with aggregate3 RPC fallback for comprehensive and efficient balance retrieval across all networks.

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, highlighting breaking changes as necessary
  • I've prepared draft pull requests for clients and consumer packages to resolve any breaking changes

@salimtb salimtb force-pushed the feat/use-aggregate3-on-balances-state-fetch-account-api branch from 9d24a01 to 28235f7 Compare August 4, 2025 14:26
@salimtb salimtb changed the title Feat/use aggregate3 on balances state fetch account api feat: use aggregate3 or accountAPI to fetch balances state Aug 4, 2025
@salimtb salimtb changed the title feat: use aggregate3 or accountAPI to fetch balances state feat: use aggregate3 or accountAPI to fetch balances Aug 4, 2025
@salimtb salimtb force-pushed the feat/use-aggregate3-on-balances-state-fetch-account-api branch 2 times, most recently from 1ca38cc to a830bd8 Compare August 4, 2025 15:11
@salimtb salimtb added team-assets area-performance Issues relating to slowness of app, cpu usage, and/or blank screens. labels Aug 4, 2025
@salimtb salimtb marked this pull request as ready for review August 4, 2025 15:23
@salimtb salimtb requested review from a team as code owners August 4, 2025 15:23
cursor[bot]

This comment was marked as outdated.

cursor[bot]

This comment was marked as outdated.

Comment on lines 351 to 356
if (includeStakedAssets) {
await this.refreshStakedBalances(networkClientIds);
}
if (includeNativeAssets) {
await this.refreshNativeBalances(networkClientIds);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice separation here. Only caveat is that both of these have independent state updates. Meaning we could end up with multiple state updates (if we need to update staked assets then native assets).

This is a minor nit, we can clean up any additional state updates in a future design.

Copy link
Contributor

Choose a reason for hiding this comment

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

(discuss) why are we including changes in the AccountTrackerController? I thought the new design would have everything in the TokenBalancesController?

* @param includeStaked - Whether to include staked balances
* @returns Map of token address to map of user address to balance
*/
const processBalanceResults = (
Copy link
Contributor

Choose a reason for hiding this comment

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

Guessing these changes are the same as this PR:
#6212

chainId: ChainIdHex;
};

export type BalanceFetcher = {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this meant to be a shared interface between different BalanceFetchers?

cursor[bot]

This comment was marked as outdated.

@salimtb
Copy link
Contributor Author

salimtb commented Aug 4, 2025

@metamaskbot publish-preview

Copy link
Contributor

github-actions bot commented Aug 4, 2025

Preview builds have been published. See these instructions for more information about preview builds.

Expand for full list of packages and versions.
{
  "@metamask-previews/account-tree-controller": "0.7.0-preview-bf50b46b",
  "@metamask-previews/accounts-controller": "32.0.1-preview-bf50b46b",
  "@metamask-previews/address-book-controller": "6.1.1-preview-bf50b46b",
  "@metamask-previews/announcement-controller": "7.0.3-preview-bf50b46b",
  "@metamask-previews/app-metadata-controller": "1.0.0-preview-bf50b46b",
  "@metamask-previews/approval-controller": "7.1.3-preview-bf50b46b",
  "@metamask-previews/assets-controllers": "73.0.1-preview-bf50b46b",
  "@metamask-previews/base-controller": "8.0.1-preview-bf50b46b",
  "@metamask-previews/bridge-controller": "37.1.0-preview-bf50b46b",
  "@metamask-previews/bridge-status-controller": "37.0.0-preview-bf50b46b",
  "@metamask-previews/build-utils": "3.0.3-preview-bf50b46b",
  "@metamask-previews/chain-agnostic-permission": "1.0.0-preview-bf50b46b",
  "@metamask-previews/composable-controller": "11.0.0-preview-bf50b46b",
  "@metamask-previews/controller-utils": "11.11.0-preview-bf50b46b",
  "@metamask-previews/delegation-controller": "0.6.0-preview-bf50b46b",
  "@metamask-previews/earn-controller": "4.0.0-preview-bf50b46b",
  "@metamask-previews/eip1193-permission-middleware": "1.0.0-preview-bf50b46b",
  "@metamask-previews/ens-controller": "17.0.1-preview-bf50b46b",
  "@metamask-previews/error-reporting-service": "2.0.0-preview-bf50b46b",
  "@metamask-previews/eth-json-rpc-provider": "4.1.8-preview-bf50b46b",
  "@metamask-previews/foundryup": "1.0.1-preview-bf50b46b",
  "@metamask-previews/gas-fee-controller": "24.0.0-preview-bf50b46b",
  "@metamask-previews/json-rpc-engine": "10.0.3-preview-bf50b46b",
  "@metamask-previews/json-rpc-middleware-stream": "8.0.7-preview-bf50b46b",
  "@metamask-previews/keyring-controller": "22.1.0-preview-bf50b46b",
  "@metamask-previews/logging-controller": "6.0.4-preview-bf50b46b",
  "@metamask-previews/message-manager": "12.0.2-preview-bf50b46b",
  "@metamask-previews/messenger": "0.0.0-preview-bf50b46b",
  "@metamask-previews/multichain-account-service": "0.3.0-preview-bf50b46b",
  "@metamask-previews/multichain-api-middleware": "1.0.0-preview-bf50b46b",
  "@metamask-previews/multichain-network-controller": "0.11.0-preview-bf50b46b",
  "@metamask-previews/multichain-transactions-controller": "4.0.0-preview-bf50b46b",
  "@metamask-previews/name-controller": "8.0.3-preview-bf50b46b",
  "@metamask-previews/network-controller": "24.0.1-preview-bf50b46b",
  "@metamask-previews/notification-services-controller": "16.0.0-preview-bf50b46b",
  "@metamask-previews/permission-controller": "11.0.6-preview-bf50b46b",
  "@metamask-previews/permission-log-controller": "4.0.0-preview-bf50b46b",
  "@metamask-previews/phishing-controller": "13.1.0-preview-bf50b46b",
  "@metamask-previews/polling-controller": "14.0.0-preview-bf50b46b",
  "@metamask-previews/preferences-controller": "18.4.1-preview-bf50b46b",
  "@metamask-previews/profile-sync-controller": "23.0.0-preview-bf50b46b",
  "@metamask-previews/rate-limit-controller": "6.0.3-preview-bf50b46b",
  "@metamask-previews/remote-feature-flag-controller": "1.6.0-preview-bf50b46b",
  "@metamask-previews/sample-controllers": "1.0.0-preview-bf50b46b",
  "@metamask-previews/seedless-onboarding-controller": "2.5.0-preview-bf50b46b",
  "@metamask-previews/selected-network-controller": "23.0.0-preview-bf50b46b",
  "@metamask-previews/signature-controller": "32.0.0-preview-bf50b46b",
  "@metamask-previews/token-search-discovery-controller": "3.3.0-preview-bf50b46b",
  "@metamask-previews/transaction-controller": "59.0.0-preview-bf50b46b",
  "@metamask-previews/user-operation-controller": "38.0.0-preview-bf50b46b"
}

cursor[bot]

This comment was marked as outdated.

cursor[bot]

This comment was marked as outdated.

cursor[bot]

This comment was marked as outdated.

@salimtb salimtb force-pushed the feat/use-aggregate3-on-balances-state-fetch-account-api branch 2 times, most recently from 89a94d5 to cd0d817 Compare August 5, 2025 23:46
expect(refreshSpy).toHaveBeenCalledTimes(5);
expect(refreshSpy).toHaveBeenNthCalledWith(3, [networkClientId1]);
expect(refreshSpy).toHaveBeenNthCalledWith(3, [networkClientId1]);
expect(refreshSpy).toHaveBeenCalledTimes(3);
Copy link

Choose a reason for hiding this comment

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

Bug: Duplicate Spy Call Verification

A test expects refreshSpy.toHaveBeenNthCalledWith(3, [networkClientId1]) twice consecutively. This is logically impossible as a spy call can only occur once for a given call number, causing the test to fail. The second expectation should likely target a different call number or arguments.

Fix in Cursor Fix in Web

account: acct as ChecksumAddress,
token: checksum(tokenAddr),
chainId,
});
Copy link

Choose a reason for hiding this comment

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

Bug: Null Balance Handling Fails

The RpcBalanceFetcher's fetch method incorrectly casts null balance values to BN type. While the success flag correctly reflects a null balance, the value property is unconditionally cast as BN. This creates ProcessedBalance objects where success is false but value is null (typed as BN), violating the value?: BN type definition and potentially causing runtime errors.

Fix in Cursor Fix in Web

}
if (includeNativeAssets) {
await this.refreshNativeBalances(networkClientIds);
}
Copy link

Choose a reason for hiding this comment

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

Bug: Balance Refresh Flags Required

The _executePoll method in AccountTrackerController no longer updates account balances by default. It now conditionally calls refreshStakedBalances and refreshNativeBalances only if includeStakedAssets or includeNativeAssets flags are explicitly set to true in AccountTrackerPollingInput. As these flags are optional, existing callers that do not specify them will result in undefined values, causing no balance updates to occur. This is a breaking change from the previous behavior where _executePoll always refreshed all balances.

Fix in Cursor Fix in Web

Copy link

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

Bug: Multicall Contract Missing on Fraxtal Chain

The multicall contract address for Fraxtal (chain ID 0xfc / 252) was unintentionally removed from the MULTICALL_CONTRACT_BY_CHAINID map. This breaks multicall functionality for Fraxtal users, forcing them to rely on less efficient individual RPC calls.

packages/assets-controllers/src/multicall.ts#L200-L201

'0x12c': '0xF9cda624FBC7e059355ce98a31693d299FACd963',
'0x18995f': '0xF9cda624FBC7e059355ce98a31693d299FACd963',

Fix in Cursor Fix in Web

@salimtb salimtb force-pushed the feat/use-aggregate3-on-balances-state-fetch-account-api branch from 01db552 to 8fbdbfa Compare August 11, 2025 15:03
Copy link

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

Bug: Fraxtal Network Multicall Support Broken

The entry for Fraxtal (chain ID 0xfc) was unintentionally removed from the MULTICALL_CONTRACT_BY_CHAINID map. This breaks multicall support for the Fraxtal network, forcing it to fall back to less efficient individual RPC calls. The removal appears accidental, as noted by a PR reviewer's comment "was this meant to be nuked?".

packages/assets-controllers/src/multicall.ts#L200-L201

'0x12c': '0xF9cda624FBC7e059355ce98a31693d299FACd963',
'0x18995f': '0xF9cda624FBC7e059355ce98a31693d299FACd963',

Fix in Cursor Fix in Web

@salimtb salimtb force-pushed the feat/use-aggregate3-on-balances-state-fetch-account-api branch from 8fbdbfa to 4830bdb Compare August 11, 2025 15:22
amitabh94
amitabh94 previously approved these changes Aug 11, 2025
@salimtb salimtb force-pushed the feat/use-aggregate3-on-balances-state-fetch-account-api branch from 4830bdb to 23561b1 Compare August 11, 2025 21:21
Copy link

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

Bug: Multicall Contract Missing for Fraxtal Mainnet

The multicall contract address for Fraxtal Mainnet (chain ID 0xfc / 252) was accidentally removed from MULTICALL_CONTRACT_BY_CHAINID. This omission, questioned by a PR reviewer, disables multicall functionality for Fraxtal, causing getTokenBalancesForMultipleAddresses to fall back to less efficient individual RPC calls.

packages/assets-controllers/src/multicall.ts#L200-L201

'0x12c': '0xF9cda624FBC7e059355ce98a31693d299FACd963',
'0x18995f': '0xF9cda624FBC7e059355ce98a31693d299FACd963',

Fix in Cursor Fix in Web

@salimtb
Copy link
Contributor Author

salimtb commented Aug 11, 2025

@metamaskbot publish-preview

Copy link
Contributor

Preview builds have been published. See these instructions for more information about preview builds.

Expand for full list of packages and versions.
{
  "@metamask-previews/account-tree-controller": "0.8.0-preview-23561b15",
  "@metamask-previews/accounts-controller": "32.0.2-preview-23561b15",
  "@metamask-previews/address-book-controller": "6.1.1-preview-23561b15",
  "@metamask-previews/announcement-controller": "7.0.3-preview-23561b15",
  "@metamask-previews/app-metadata-controller": "1.0.0-preview-23561b15",
  "@metamask-previews/approval-controller": "7.1.3-preview-23561b15",
  "@metamask-previews/assets-controllers": "73.1.0-preview-23561b15",
  "@metamask-previews/base-controller": "8.1.0-preview-23561b15",
  "@metamask-previews/bridge-controller": "38.0.0-preview-23561b15",
  "@metamask-previews/bridge-status-controller": "37.0.1-preview-23561b15",
  "@metamask-previews/build-utils": "3.0.3-preview-23561b15",
  "@metamask-previews/chain-agnostic-permission": "1.1.0-preview-23561b15",
  "@metamask-previews/composable-controller": "11.0.0-preview-23561b15",
  "@metamask-previews/controller-utils": "11.11.0-preview-23561b15",
  "@metamask-previews/delegation-controller": "0.6.0-preview-23561b15",
  "@metamask-previews/earn-controller": "4.0.0-preview-23561b15",
  "@metamask-previews/eip1193-permission-middleware": "1.0.0-preview-23561b15",
  "@metamask-previews/ens-controller": "17.0.1-preview-23561b15",
  "@metamask-previews/error-reporting-service": "2.0.0-preview-23561b15",
  "@metamask-previews/eth-json-rpc-provider": "4.1.8-preview-23561b15",
  "@metamask-previews/foundryup": "1.0.1-preview-23561b15",
  "@metamask-previews/gas-fee-controller": "24.0.0-preview-23561b15",
  "@metamask-previews/json-rpc-engine": "10.0.3-preview-23561b15",
  "@metamask-previews/json-rpc-middleware-stream": "8.0.7-preview-23561b15",
  "@metamask-previews/keyring-controller": "22.1.1-preview-23561b15",
  "@metamask-previews/logging-controller": "6.0.4-preview-23561b15",
  "@metamask-previews/message-manager": "12.0.2-preview-23561b15",
  "@metamask-previews/messenger": "0.0.0-preview-23561b15",
  "@metamask-previews/multichain-account-service": "0.4.0-preview-23561b15",
  "@metamask-previews/multichain-api-middleware": "1.0.0-preview-23561b15",
  "@metamask-previews/multichain-network-controller": "0.11.1-preview-23561b15",
  "@metamask-previews/multichain-transactions-controller": "4.0.1-preview-23561b15",
  "@metamask-previews/name-controller": "8.0.3-preview-23561b15",
  "@metamask-previews/network-controller": "24.0.1-preview-23561b15",
  "@metamask-previews/network-enablement-controller": "0.1.0-preview-23561b15",
  "@metamask-previews/notification-services-controller": "16.0.0-preview-23561b15",
  "@metamask-previews/permission-controller": "11.0.6-preview-23561b15",
  "@metamask-previews/permission-log-controller": "4.0.0-preview-23561b15",
  "@metamask-previews/phishing-controller": "13.1.0-preview-23561b15",
  "@metamask-previews/polling-controller": "14.0.0-preview-23561b15",
  "@metamask-previews/preferences-controller": "18.4.1-preview-23561b15",
  "@metamask-previews/profile-sync-controller": "23.0.0-preview-23561b15",
  "@metamask-previews/rate-limit-controller": "6.0.3-preview-23561b15",
  "@metamask-previews/remote-feature-flag-controller": "1.7.0-preview-23561b15",
  "@metamask-previews/sample-controllers": "1.0.0-preview-23561b15",
  "@metamask-previews/seedless-onboarding-controller": "2.5.1-preview-23561b15",
  "@metamask-previews/selected-network-controller": "23.0.0-preview-23561b15",
  "@metamask-previews/signature-controller": "32.0.0-preview-23561b15",
  "@metamask-previews/token-search-discovery-controller": "3.3.0-preview-23561b15",
  "@metamask-previews/transaction-controller": "59.1.0-preview-23561b15",
  "@metamask-previews/user-operation-controller": "38.0.0-preview-23561b15"
}

amitabh94
amitabh94 previously approved these changes Aug 12, 2025
@salimtb salimtb force-pushed the feat/use-aggregate3-on-balances-state-fetch-account-api branch from 23561b1 to 494b74f Compare August 14, 2025 17:41
@salimtb salimtb merged commit e4feac8 into main Aug 14, 2025
223 checks passed
@salimtb salimtb deleted the feat/use-aggregate3-on-balances-state-fetch-account-api branch August 14, 2025 17:51
@salimtb
Copy link
Contributor Author

salimtb commented Aug 14, 2025

@metamaskbot publish-preview

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. team-assets
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants