Skip to content

balance fetcher changes#8284

Open
bergarces wants to merge 18 commits intomainfrom
fix-rpc-balance-fetcher
Open

balance fetcher changes#8284
bergarces wants to merge 18 commits intomainfrom
fix-rpc-balance-fetcher

Conversation

@bergarces
Copy link
Contributor

@bergarces bergarces commented Mar 24, 2026

Explanation

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

Medium Risk
Updates the balance-fetching API used by RpcDataSource/BalanceFetcher, changing how native + custom ERC-20s are identified and batched; regressions could impact displayed balances across chains.

Overview
Refactors EVM balance polling/fetching to operate on explicit AssetFetchEntry objects (CAIP-19 assetId + on-chain address + optional decimals) via fetchBalancesForAssets, replacing the prior token-address + options/tokenInfo approach.

RpcDataSource.fetch now always builds a single native+custom-ERC20 entry list per chain (using ZERO_ADDRESS for native and pre-populating decimals from existing metadata/token list when available), and removes the helper that previously built TokenFetchInfo for custom tokens.

BalanceFetcher now maps multicall responses back to caller-provided asset IDs, deduplicates by address, and uses a shared ZERO_ADDRESS constant; tests are updated accordingly and ZERO_ADDRESS is centralized in utils/constants.ts (also adopted by MulticallClient).

Written by Cursor Bugbot for commit 1189e6b. This will update automatically on new commits. Configure here.

@bergarces bergarces requested review from a team as code owners March 24, 2026 14:23
Copy link
Contributor Author

@bergarces bergarces Mar 24, 2026

Choose a reason for hiding this comment

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

The goal here was to pass the assetId alongside the token addresses and the tokenInfo to the balance fetcher.

Since the last two were already being placed in two different arrays of the same length, as well as a setting to return native balance, it's all been simplified by just passing to balance fetcher a single array that contains everything needed:

  • AssetId (including the native token)
  • Address
  • TokenInfo

That way, BalanceFetcher does not need to know how to build the native assetId for every chain.

Also, the token address passed to balance fetcher is always the zero address, regardless of the chain, as that is how BalanceFetcher determines whether to fetch the balance of a token or the balance of the account.

* native assets even when the chain has a native asset with a non-zero address)
* and optional metadata
*/
export type BalanceFetchOptions = {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This wasn't being used at all, only to specify that we wanted to include native assets, which was already the default. In any case the native assets need to be added to the array of tokens to fetch now.

/** Token decimals (omit when unknown — balance fetcher returns raw balance for RpcDataSource to resolve). */
decimals?: number;
/** Token symbol (optional) */
symbol?: string;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This wasn't being used at all.

continue;
}

const isNative = assetNamespace === 'slip44';
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is not completely right, but this is how we are currently determining that an asset is native everywhere else, so this is consistent.

We already have a ticket and plans to discuss how to handle native tokens (by pushing the definition to an API).

const tokenMap = new Map<string, TokenFetchInfo>();

for (const assetId of Object.keys(accountBalances)) {
// Only process ERC20 tokens on the current chain
Copy link
Contributor Author

@bergarces bergarces Mar 24, 2026

Choose a reason for hiding this comment

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

We no longer need to filter only erc20 tokens, since we need to include all assets for that chain, including native assets.

This simplifies things as well.

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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

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.

chain: { reference: chainReference },
assetNamespace,
assetReference,
} = parseCaipAssetType(assetId);
Copy link

Choose a reason for hiding this comment

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

Missing try/catch around parseCaipAssetType in #getAssetsToFetch

Low Severity

The new #getAssetsToFetch calls parseCaipAssetType(assetId) on every key in accountBalances without a try/catch. If any state key is a malformed CAIP-19 string, this throws and crashes the entire polling cycle. The old getTokensToFetch used simple string operations (startsWith, includes) that never throw on bad input. Notably, RpcDataSource.ts already wraps its own parseCaipAssetType calls in try/catch for exactly this reason.

Fix in Cursor Fix in Web

@bergarces
Copy link
Contributor Author

@metamaskbot publish-preview

@github-actions
Copy link
Contributor

Preview builds have been published. Learn how to use preview builds in other projects.

Expand for full list of packages and versions.
@metamask-previews/account-tree-controller@5.0.1-preview-1189e6b40
@metamask-previews/accounts-controller@37.0.0-preview-1189e6b40
@metamask-previews/address-book-controller@7.1.0-preview-1189e6b40
@metamask-previews/ai-controllers@0.5.0-preview-1189e6b40
@metamask-previews/analytics-controller@1.0.0-preview-1189e6b40
@metamask-previews/analytics-data-regulation-controller@0.0.0-preview-1189e6b40
@metamask-previews/announcement-controller@8.0.0-preview-1189e6b40
@metamask-previews/app-metadata-controller@2.0.0-preview-1189e6b40
@metamask-previews/approval-controller@9.0.0-preview-1189e6b40
@metamask-previews/assets-controller@3.1.0-preview-1189e6b40
@metamask-previews/assets-controllers@101.0.1-preview-1189e6b40
@metamask-previews/base-controller@9.0.0-preview-1189e6b40
@metamask-previews/base-data-service@0.0.0-preview-1189e6b40
@metamask-previews/bridge-controller@69.2.1-preview-1189e6b40
@metamask-previews/bridge-status-controller@70.0.1-preview-1189e6b40
@metamask-previews/build-utils@3.0.4-preview-1189e6b40
@metamask-previews/chain-agnostic-permission@1.4.0-preview-1189e6b40
@metamask-previews/claims-controller@0.4.3-preview-1189e6b40
@metamask-previews/client-controller@1.0.0-preview-1189e6b40
@metamask-previews/compliance-controller@1.0.1-preview-1189e6b40
@metamask-previews/composable-controller@12.0.0-preview-1189e6b40
@metamask-previews/config-registry-controller@0.1.1-preview-1189e6b40
@metamask-previews/connectivity-controller@0.1.0-preview-1189e6b40
@metamask-previews/controller-utils@11.19.0-preview-1189e6b40
@metamask-previews/core-backend@6.2.0-preview-1189e6b40
@metamask-previews/delegation-controller@2.0.2-preview-1189e6b40
@metamask-previews/earn-controller@11.1.2-preview-1189e6b40
@metamask-previews/eip-5792-middleware@3.0.1-preview-1189e6b40
@metamask-previews/eip-7702-internal-rpc-middleware@0.1.0-preview-1189e6b40
@metamask-previews/eip1193-permission-middleware@1.0.3-preview-1189e6b40
@metamask-previews/ens-controller@19.1.0-preview-1189e6b40
@metamask-previews/error-reporting-service@3.0.1-preview-1189e6b40
@metamask-previews/eth-block-tracker@15.0.1-preview-1189e6b40
@metamask-previews/eth-json-rpc-middleware@23.1.0-preview-1189e6b40
@metamask-previews/eth-json-rpc-provider@6.0.0-preview-1189e6b40
@metamask-previews/foundryup@1.0.1-preview-1189e6b40
@metamask-previews/gas-fee-controller@26.1.0-preview-1189e6b40
@metamask-previews/gator-permissions-controller@2.1.1-preview-1189e6b40
@metamask-previews/geolocation-controller@0.1.1-preview-1189e6b40
@metamask-previews/json-rpc-engine@10.2.3-preview-1189e6b40
@metamask-previews/json-rpc-middleware-stream@8.0.8-preview-1189e6b40
@metamask-previews/keyring-controller@25.1.0-preview-1189e6b40
@metamask-previews/logging-controller@8.0.0-preview-1189e6b40
@metamask-previews/message-manager@14.1.0-preview-1189e6b40
@metamask-previews/messenger@0.3.0-preview-1189e6b40
@metamask-previews/multichain-account-service@7.1.0-preview-1189e6b40
@metamask-previews/multichain-api-middleware@1.2.7-preview-1189e6b40
@metamask-previews/multichain-network-controller@3.0.5-preview-1189e6b40
@metamask-previews/multichain-transactions-controller@7.0.2-preview-1189e6b40
@metamask-previews/name-controller@9.1.0-preview-1189e6b40
@metamask-previews/network-controller@30.0.0-preview-1189e6b40
@metamask-previews/network-enablement-controller@5.0.0-preview-1189e6b40
@metamask-previews/notification-services-controller@23.0.0-preview-1189e6b40
@metamask-previews/permission-controller@12.2.1-preview-1189e6b40
@metamask-previews/permission-log-controller@5.0.0-preview-1189e6b40
@metamask-previews/perps-controller@1.3.0-preview-1189e6b40
@metamask-previews/phishing-controller@17.0.0-preview-1189e6b40
@metamask-previews/polling-controller@16.0.3-preview-1189e6b40
@metamask-previews/preferences-controller@23.0.0-preview-1189e6b40
@metamask-previews/profile-metrics-controller@3.1.1-preview-1189e6b40
@metamask-previews/profile-sync-controller@28.0.0-preview-1189e6b40
@metamask-previews/ramps-controller@12.0.1-preview-1189e6b40
@metamask-previews/rate-limit-controller@7.0.0-preview-1189e6b40
@metamask-previews/react-data-query@0.0.0-preview-1189e6b40
@metamask-previews/remote-feature-flag-controller@4.1.0-preview-1189e6b40
@metamask-previews/sample-controllers@4.0.3-preview-1189e6b40
@metamask-previews/seedless-onboarding-controller@9.0.0-preview-1189e6b40
@metamask-previews/selected-network-controller@26.0.3-preview-1189e6b40
@metamask-previews/shield-controller@5.0.2-preview-1189e6b40
@metamask-previews/signature-controller@39.1.0-preview-1189e6b40
@metamask-previews/storage-service@1.0.0-preview-1189e6b40
@metamask-previews/subscription-controller@6.0.2-preview-1189e6b40
@metamask-previews/transaction-controller@63.1.0-preview-1189e6b40
@metamask-previews/transaction-pay-controller@18.1.0-preview-1189e6b40
@metamask-previews/user-operation-controller@41.1.0-preview-1189e6b40

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.

1 participant