Skip to content

Conversation

@salimtb
Copy link
Contributor

@salimtb salimtb commented Jan 28, 2026

Explanation

Current State

The AssetsController was storing raw balance amounts (e.g., "39779499997060000" for 0.0398 ETH) in the assetsBalance state. This made it difficult for consumers to display human-readable values without additional conversion logic. Additionally, native token metadata (symbol, name, decimals) was not being stored in assetsMetadata, which is required for proper balance formatting and display.

Solution

This PR updates all data sources to:

  1. Convert raw balances to human-readable format using the token's decimals before storing in state
  2. Store native token metadata in assetsMetadata with decimals: 18 derived from NetworkController chain status

Changes by Data Source:

RpcDataSource:

  • Generates native token metadata from NetworkController chain status (symbol, name, decimals: 18)
  • Looks up ERC20 metadata from existing assetsMetadata state or falls back to TokenListController
  • Converts all balances to human-readable format using BigNumberJS before returning

AccountsApiDataSource:

  • Extracts metadata (type, symbol, name, decimals) from the V5 API response
  • Converts balances to human-readable format using decimals from API
  • Includes assetsMetadata in the response and merges it in the middleware

BackendWebsocketDataSource:

  • Converts balances to human-readable format using asset.decimals from the websocket message

Example Result

Before:
{
  "assetsBalance": {
    "account-id": {
      "eip155:1/slip44:60": { "amount": "39779499997060000" },
      "eip155:1/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48": { "amount": "8741994" }
    }
  },
  "assetsMetadata": {}
}

After:

{
  "assetsBalance": {
    "account-id": {
      "eip155:1/slip44:60": { "amount": "0.039779499997060000" },
      "eip155:1/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48": { "amount": "8.741994" }
    }
  },
  "assetsMetadata": {
    "eip155:1/slip44:60": { "type": "native", "symbol": "ETH", "name": "ETH", "decimals": 18 },
    "eip155:1/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48": { "type": "erc20", "symbol": "USDC", "name": "USD Coin", "decimals": 6 }
  }
}

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

Converts all asset balances to human-readable amounts and propagates token metadata alongside balance updates.

  • Assets are now returned/stored with balance.amount in human-readable format; fiat is computed directly as balance.amount * price. README and CHANGELOG updated
  • RpcDataSource converts raw balances using decimals, includes assetsMetadata (native from NetworkController, ERC20 from state or token list), and merges via middleware
  • Refactors polling services: BalanceFetcher and TokenDetector now use minimal messenger adapters, return human-readable balances, and no longer rely on user token lists; tests updated accordingly
  • Middleware/subscribe flow updated to merge assetsMetadata; subscription updates restart polling; disabled-chain balances are retained for historical display

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

@salimtb salimtb marked this pull request as ready for review January 28, 2026 09:40
@salimtb salimtb requested review from a team as code owners January 28, 2026 09:40
@salimtb salimtb marked this pull request as draft January 28, 2026 16:00
@salimtb salimtb marked this pull request as ready for review January 28, 2026 16:40
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 3 potential issues.

accountAddress: Address,
options?: BalanceFetchOptions,
): Promise<BalanceFetchResult> {
const tokens = this.getTokensToFetch(chainId, accountAddress);
Copy link

Choose a reason for hiding this comment

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

User-imported tokens never fetched on RPC-only chains

High Severity

The refactored getTokensToFetch() now reads from assetsBalance to determine which ERC20 tokens to fetch, but this creates a circular dependency. User-imported tokens aren't in assetsBalance until their balance is fetched, but they can't be fetched because they're not in assetsBalance. Additionally, RpcDataSource.fetch() always passes an empty array to fetchBalancesForTokens(), only fetching native token balance and ignoring request.customAssets. This breaks balance fetching for user-imported tokens on RPC-only chains.

Additional Locations (1)

Fix in Cursor Fix in Web

options?: BalanceFetchOptions,
): Promise<BalanceFetchResult> {
const tokens = this.getTokensToFetch(chainId, accountAddress);
const tokens = this.getTokensToFetch(chainId, accountId);
Copy link

Choose a reason for hiding this comment

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

Unused formattedBalance computed by BalanceFetcher

Low Severity

BalanceFetcher computes formattedBalance for every balance result (line 286) and includes it in the returned object (line 300). However, RpcDataSource.#handleBalanceUpdate completely ignores formattedBalance and instead uses balance.balance (the raw value) to recompute the human-readable format via #convertToHumanReadable. This wastes computation and creates misleading code where a field is populated but never consumed.

Additional Locations (1)

Fix in Cursor Fix in Web

);

newBalances[balance.assetId] = {
amount: humanReadableAmount,
Copy link

Choose a reason for hiding this comment

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

Duplicated detection result processing code in RpcDataSource

Low Severity

The code for building metadata from detected assets and converting detected balances to human-readable format is duplicated nearly identically in two places within RpcDataSource: in #handleDetectionUpdate (lines 530-565) and in detectTokens (lines 1048-1076). Both blocks iterate over result.detectedAssets to build metadata objects and over result.detectedBalances to convert balances using #convertToHumanReadable. This pattern could be extracted into a shared helper method.

Additional Locations (1)

Fix in Cursor Fix in Web

Copy link
Contributor

@Prithpal-Sooriya Prithpal-Sooriya left a comment

Choose a reason for hiding this comment

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

Approved.

Lets just keep in mind that if people want to use the string decimals, they need to be careful with BigNumber and >15 decimals to avoid this error:

new BigNumber() number type has more than 15 significant digits

Example sentry log:
https://metamask.sentry.io/issues/7082247853/events/8f9c90d2a5e24c22956de16441731605/?project=273505

@salimtb salimtb added this pull request to the merge queue Jan 28, 2026
Merged via the queue into main with commit e6acfe6 Jan 28, 2026
302 checks passed
@salimtb salimtb deleted the feat/human-readable-asset-balances branch January 28, 2026 17:19
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.

3 participants