Skip to content

fix: refresh token list immediately after import when assetsUnifyState is enabled#40919

Merged
salimtb merged 3 commits intomainfrom
fix/assets-unify-state-token-list-refresh-after-import
Mar 18, 2026
Merged

fix: refresh token list immediately after import when assetsUnifyState is enabled#40919
salimtb merged 3 commits intomainfrom
fix/assets-unify-state-token-list-refresh-after-import

Conversation

@salimtb
Copy link
Contributor

@salimtb salimtb commented Mar 16, 2026

Description

refresh token list immediately after import when assetsUnifyState is enabled

Open in GitHub Codespaces

Changelog

CHANGELOG entry: refresh token list immediately after import when assetsUnifyState is enabled

Related issues

Fixes:

Manual testing steps

  1. Go to this page...

Screenshots/Recordings

Before

After

after02.mov
after03.mov

Pre-merge author checklist

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
Changes selector logic that shapes EVM token balances and currency rates when assetsUnifyState is enabled, which can affect asset display across networks. Risk is moderate due to added fallback behavior and placeholder balances, but scope is limited to derived state and is covered by new tests.

Overview
When assetsUnifyState is enabled, getTokenBalancesControllerTokenBalances now merges customAssets into the derived token balances by inserting zero-balance placeholders for custom EVM tokens that have metadata but no entry in assetsBalance yet, without overriding real balances and while skipping non-EVM custom assets.

getCurrencyRateControllerCurrencyRates now falls back to any chain-native slip44:* price key for the same EVM chain when the direct assetsInfo native-asset ID lookup is missing, improving native rate derivation for chains where pricing uses a chain-specific slip44.

Tests were updated/added to cover the new placeholder-balance behavior and guardrails (no overwrite, ignore non-EVM).

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

@github-actions
Copy link
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.

@metamaskbotv2
Copy link
Contributor

metamaskbotv2 bot commented Mar 16, 2026

Builds ready [c9c6e7a]
⚡ Performance Benchmarks
👆 Interaction Benchmarks
BenchmarkMetricMean (ms)Min (ms)Max (ms)Std Dev (ms)P75 (ms)P95 (ms)
Load New Accountload_new_account27826829310278293
total27826829310278293
Confirm Txconfirm_tx6014600060411560216041
total6014600060411560216041
Bridge User Actionsbridge_load_page23420028832240288
bridge_load_asset_picker25522528320270283
bridge_search_token75974378115772781
total1238122612481012461248
🔌 Startup Benchmarks
BenchmarkMetricMean (ms)Min (ms)Max (ms)Std Dev (ms)P75 (ms)P95 (ms)
Standard HomeuiStartup15011260183011515521693
load12461030156810913051437
domContentLoaded12391023156410712991431
domInteractive3118133232692
firstPaint187711511213218398
backgroundConnect21819827815221255
firstReactRender20135152126
initialActions106124
loadScripts1038831135710510981227
setupStore1364161521
numNetworkReqs403182164078
Power User HomeuiStartup61572184173602828654510940
load13371125306025713401671
domContentLoaded13181122305225513271612
domInteractive38201663332134
firstPaint235821227170286396
backgroundConnect285537214588277437698215
firstReactRender24174852731
initialActions104113
loadScripts1094919280024910891366
setupStore1575161726
numNetworkReqs20910335656232321
🧭 User Journey Benchmarks
BenchmarkMetricMean (ms)Min (ms)Max (ms)Std Dev (ms)P75 (ms)P95 (ms)
Onboarding Import WalletimportWalletToSocialScreen2182172191219219
srpButtonToSrpForm95939619596
confirmSrpToPwForm23222412424
pwFormToMetricsScreen15151601616
metricsToWalletReadyScreen17161811718
doneButtonToHomeScreen58757360916609609
openAccountMenuToAccountListLoaded29862709316016231373160
total3915388639642939093964
Onboarding New WalletcreateWalletToSocialScreen2192182211220221
srpButtonToPwForm1131121141114114
createPwToRecoveryScreen999099
skipBackupToMetricsScreen41394214142
agreeButtonToOnboardingSuccess17161801718
doneButtonToAssetList53449557127542571
total94989799436965994
Asset DetailsassetClickToPriceChart79768338383
total79768338383
Solana Asset DetailsassetClickToPriceChart1091071122112112
total1091071122112112
Import Srp HomeloginToHomeScreen2303223223584423392358
openAccountMenuAfterLogin62517176871
homeAfterImportWithNewWallet1323453243391024332433
total36882882483689447234836
Send TransactionsopenSendPageFromHome442474216574
selectTokenToSendFormLoaded472976216876
reviewTransactionToConfirmationPage1136949141622113981416
total12271016153122614741531
SwapopenSwapPageFromHome852812836115128
fetchAndDisplaySwapQuotes268826862691226872691
total2762271628073427792807
🌐 Dapp Page Load Benchmarks

Current Commit: c9c6e7a | Date: 3/16/2026

📄 Localhost MetaMask Test Dapp

Samples: 100

Summary

  • pageLoadTime-> current mean value: 1.05s (±40ms) 🟡 | historical mean value: 1.05s ⬇️ (historical data)
  • domContentLoaded-> current mean value: 736ms (±38ms) 🟢 | historical mean value: 742ms ⬇️ (historical data)
  • firstContentfulPaint-> current mean value: 80ms (±10ms) 🟢 | historical mean value: 85ms ⬇️ (historical data)

📈 Detailed Results

Metric Mean Std Dev Min Max P95 P99
pageLoadTime 1.05s 40ms 1.02s 1.34s 1.10s 1.34s
domContentLoaded 736ms 38ms 712ms 1.02s 765ms 1.02s
firstPaint 80ms 10ms 64ms 160ms 88ms 160ms
firstContentfulPaint 80ms 10ms 64ms 160ms 88ms 160ms
largestContentfulPaint 0ms 0ms 0ms 0ms 0ms 0ms
Bundle size diffs [🚨 Warning! Bundle size has increased!]
  • background: 58 Bytes (0%)
  • ui: 5.27 KiB (0.06%)
  • common: 923 Bytes (0.01%)

github-merge-queue bot pushed a commit to MetaMask/core that referenced this pull request Mar 16, 2026
…balance on import (#8202)

## Explanation

**Current state / problem:**

UI PR: MetaMask/metamask-extension#40919

When a user imports a custom ERC20 token via `wallet_watchAsset` (e.g. a
self-deployed token on Sepolia that is not indexed by the MetaMask
tokens API), `addCustomAsset` is called with `pendingMetadata`
containing the correct `symbol`, `name`, `decimals`, and `image`. This
metadata is immediately persisted into `assetsInfo` so the UI can render
the token without waiting for a backend fetch.

However, `addCustomAsset` then calls `getAssets()` with `forceUpdate:
true`, which triggers `#updateState`. The tokens API has no knowledge of
this custom token and returns `{ symbol: "", name: "", decimals: 18 }`.
The unconditional `metadata[key] = value` assignment in `#updateState`
blindly overwrites the correct `pendingMetadata` with this empty API
response.

This causes three cascading issues:

1. **Wrong balance** — the raw on-chain balance is divided by `10^18`
instead of the token's real decimals (e.g. `10^4`), producing a value
like `1e-13` instead of `10`
2. **BigInt crash** — the scientific-notation balance string crashes
`BigInt` parsing downstream: `SyntaxError: Cannot convert 1e-13 to a
BigInt`
3. **Missing display data** — the token renders with no symbol and no
name

A second issue compounds this: `RpcDataSource.fetch()` was passing an
empty `[]` for token addresses to `fetchBalancesForTokens`, meaning only
the native token balance (e.g. ETH) was fetched via RPC. Custom ERC20
tokens in `request.customAssets` were completely ignored, so the
on-chain ERC20 balance was never actually fetched during the initial
import flow.

**Solution:**

Two fixes:

1. **Preserve `pendingMetadata` in `#updateState`**
(`AssetsController.ts`): Before overwriting an entry in `assetsInfo`,
check whether the incoming API response has both empty `symbol` and
empty `name`. This is a reliable signal that the data source has no real
knowledge of the token. When that condition is true and richer metadata
already exists in state (from `pendingMetadata`), the existing `symbol`,
`name`, `decimals`, and `image` are preserved while still merging any
supplementary fields the API may provide (e.g. `aggregators`,
`occurrences`). When the API returns real data (non-empty symbol or
name), behaviour is unchanged.

2. **Forward custom asset addresses to `RpcDataSource.fetch()`**
(`RpcDataSource.ts`): During a fetch, the method now extracts ERC20
token addresses from `request.customAssets` that match the chain
currently being fetched, and passes them to `fetchBalancesForTokens`.
This ensures the multicall includes the custom token so its on-chain
balance is retrieved immediately after import.

## References

* Fixes the `wallet_watchAsset` / custom token import bug reported
internally
* Related to the `pendingTokens` flow in the MetaMask extension

## Checklist

- [x] I've updated the test suite for new or updated code as appropriate
- [x] I've updated documentation (JSDoc, Markdown, etc.) for new or
updated code as appropriate
- [x] I've communicated my changes to consumers by [updating changelogs
for packages I've
changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md)
- [ ] I've introduced [breaking
changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md)
in this PR and have prepared draft pull requests for clients and
consumer packages to resolve them

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Changes how `AssetsController` merges incoming token metadata and how
`RpcDataSource` computes/requests ERC20 balances, which can affect
displayed balances and requires extra RPC calls (including a new
`decimals()` call fallback).
> 
> **Overview**
> Fixes custom token import flow so user-provided metadata isn’t
overwritten by empty token-API responses:
`AssetsController.#updateState` now preserves existing `symbol`/`name`
(and keeps richer `decimals`/`image`) when the incoming metadata has
both fields empty.
> 
> Updates `RpcDataSource` to immediately fetch balances for imported
custom ERC20s by extracting matching-chain ERC20 addresses from
`request.customAssets` and passing them into
`BalanceFetcher.fetchBalancesForTokens`; balance handling now normalizes
CAIP-19 IDs, allows async balance-update callbacks, and adds an RPC
`decimals()` fallback when decimals are missing. Tests and changelog are
updated accordingly.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
9e298fe. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
@salimtb salimtb marked this pull request as ready for review March 17, 2026 15:36
@metamaskbotv2
Copy link
Contributor

metamaskbotv2 bot commented Mar 17, 2026

Builds ready [c56e2e5]
⚡ Performance Benchmarks
👆 Interaction Benchmarks
BenchmarkMetricMean (ms)Min (ms)Max (ms)Std Dev (ms)P75 (ms)P95 (ms)
Load New Accountload_new_account29226833526309335
total29226833526309335
Confirm Txconfirm_tx601760076028860196028
total601760076028860196028
Bridge User Actionsbridge_load_page26121630432270304
bridge_load_asset_picker18615820923207209
bridge_search_token73369976225761762
total1184111112746112011274
🔌 Startup Benchmarks
BenchmarkMetricMean (ms)Min (ms)Max (ms)Std Dev (ms)P75 (ms)P95 (ms)
Standard HomeuiStartup1442123217189414931615
load119099014318712401339
domContentLoaded118498314238712331331
domInteractive3018130232687
firstPaint172701250168198341
backgroundConnect2091952449212221
firstReactRender19123452129
initialActions105124
loadScripts98979012278710411136
setupStore1362951622
numNetworkReqs393182153476
Power User HomeuiStartup5545225318207297665779906
load13231124186013913881633
domContentLoaded13021115181213113611580
domInteractive38191963532122
firstPaint240911170144310418
backgroundConnect207133115015258130245783
firstReactRender25174352736
initialActions105114
loadScripts1078911153511911241349
setupStore1684571927
numNetworkReqs1606731047179265
🧭 User Journey Benchmarks
BenchmarkMetricMean (ms)Min (ms)Max (ms)Std Dev (ms)P75 (ms)P95 (ms)
Onboarding Import WalletimportWalletToSocialScreen2212192232223223
srpButtonToSrpForm98941054101105
confirmSrpToPwForm23222412424
pwFormToMetricsScreen16151811718
metricsToWalletReadyScreen17161911819
doneButtonToHomeScreen61059862310617623
openAccountMenuToAccountListLoaded2928290429662329432966
total3915389339391739233939
Onboarding New WalletcreateWalletToSocialScreen2222182252224225
srpButtonToPwForm1131131141113114
createPwToRecoveryScreen999099
skipBackupToMetricsScreen40384114141
agreeButtonToOnboardingSuccess17161701717
doneButtonToAssetList4864804914487491
total90088392817910928
Asset DetailsassetClickToPriceChart483562105162
total483562105162
Solana Asset DetailsassetClickToPriceChart1307517837158178
total1307517837158178
Import Srp HomeloginToHomeScreen2296224823584022972358
openAccountMenuAfterLogin985517448133174
homeAfterImportWithNewWallet2345228923923923692392
total4720467247613547464761
Send TransactionsopenSendPageFromHome29184183141
selectTokenToSendFormLoaded32214483544
reviewTransactionToConfirmationPage1182734158127812961581
total1250789166528913471665
SwapopenSwapPageFromHome1197014527141145
fetchAndDisplaySwapQuotes268926892690126902690
total2809276028352828312835
🌐 Dapp Page Load Benchmarks

Current Commit: c56e2e5 | Date: 3/17/2026

📄 Localhost MetaMask Test Dapp

Samples: 100

Summary

  • pageLoadTime-> current mean value: 1.04s (±41ms) 🟡 | historical mean value: 1.04s ⬇️ (historical data)
  • domContentLoaded-> current mean value: 734ms (±39ms) 🟢 | historical mean value: 736ms ⬇️ (historical data)
  • firstContentfulPaint-> current mean value: 80ms (±10ms) 🟢 | historical mean value: 87ms ⬇️ (historical data)

📈 Detailed Results

Metric Mean Std Dev Min Max P95 P99
pageLoadTime 1.04s 41ms 1.02s 1.36s 1.07s 1.36s
domContentLoaded 734ms 39ms 710ms 1.03s 761ms 1.03s
firstPaint 80ms 10ms 64ms 168ms 84ms 168ms
firstContentfulPaint 80ms 10ms 64ms 168ms 84ms 168ms
largestContentfulPaint 0ms 0ms 0ms 0ms 0ms 0ms
Bundle size diffs
  • background: 58 Bytes (0%)
  • ui: 5 Bytes (0%)
  • common: 687 Bytes (0.01%)

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
72.7% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@metamaskbotv2
Copy link
Contributor

metamaskbotv2 bot commented Mar 17, 2026

Builds ready [a4e8638]
⚡ Performance Benchmarks
👆 Interaction Benchmarks
BenchmarkMetricMean (ms)Min (ms)Max (ms)Std Dev (ms)P75 (ms)P95 (ms)
Load New Accountload_new_account2832782936280293
total2832782936280293
Confirm Txconfirm_tx606060526069760646069
total606060526069760646069
Bridge User Actionsbridge_load_page26721731936295319
bridge_load_asset_picker23519727129265271
bridge_search_token7597517686761768
total127612721279312791279
🔌 Startup Benchmarks
BenchmarkMetricMean (ms)Min (ms)Max (ms)Std Dev (ms)P75 (ms)P95 (ms)
Standard HomeuiStartup15241230196812016141707
load12661023166310613441423
domContentLoaded12591016160710413371414
domInteractive3218138242795
firstPaint176741145126218323
backgroundConnect22119831715226241
firstReactRender2213175162130
initialActions206225
loadScripts1054812139710211271198
setupStore1384051621
numNetworkReqs393185164078
Power User HomeuiStartup525921288931178465127972
load13021135176412313231576
domContentLoaded12831127172211413071539
domInteractive3521189263269
firstPaint2208745189286348
backgroundConnect16663015826129424864232
firstReactRender24174152635
initialActions104113
loadScripts1053919147910910731315
setupStore1676491830
numNetworkReqs1366531843149244
🧭 User Journey Benchmarks
BenchmarkMetricMean (ms)Min (ms)Max (ms)Std Dev (ms)P75 (ms)P95 (ms)
Onboarding Import WalletimportWalletToSocialScreen2192182211219221
srpButtonToSrpForm95949619596
confirmSrpToPwForm22222202222
pwFormToMetricsScreen16151601616
metricsToWalletReadyScreen16161711717
doneButtonToHomeScreen62759066828630668
openAccountMenuToAccountListLoaded2925290729421529402942
total3923390239763139103976
Onboarding New WalletcreateWalletToSocialScreen2192172212219221
srpButtonToPwForm1131111183115118
createPwToRecoveryScreen999099
skipBackupToMetricsScreen42414414344
agreeButtonToOnboardingSuccess17171701717
doneButtonToAssetList51149654218506542
total91589595323915953
Asset DetailsassetClickToPriceChart894416045125160
total894416045125160
Solana Asset DetailsassetClickToPriceChart1618422147191221
total1618422147191221
Import Srp HomeloginToHomeScreen2310229723321523322332
openAccountMenuAfterLogin624076157676
homeAfterImportWithNewWallet235223412361823612361
total4691463347344247344734
Send TransactionsopenSendPageFromHome27262912829
selectTokenToSendFormLoaded28253023030
reviewTransactionToConfirmationPage1001850127617011241276
total1063908133016511751330
SwapopenSwapPageFromHome11810113214129132
fetchAndDisplaySwapQuotes269226842702726992702
total2810278428251528192825
🌐 Dapp Page Load Benchmarks

Current Commit: a4e8638 | Date: 3/17/2026

📄 Localhost MetaMask Test Dapp

Samples: 100

Summary

  • pageLoadTime-> current mean value: 1.07s (±43ms) 🟡 | historical mean value: 1.04s ⬆️ (historical data)
  • domContentLoaded-> current mean value: 759ms (±62ms) 🟢 | historical mean value: 736ms ⬆️ (historical data)
  • firstContentfulPaint-> current mean value: 96ms (±130ms) 🟢 | historical mean value: 87ms ⬆️ (historical data)

📈 Detailed Results

Metric Mean Std Dev Min Max P95 P99
pageLoadTime 1.07s 43ms 1.05s 1.40s 1.10s 1.40s
domContentLoaded 759ms 62ms 731ms 1.32s 784ms 1.32s
firstPaint 96ms 130ms 72ms 1.39s 92ms 1.39s
firstContentfulPaint 96ms 130ms 72ms 1.39s 92ms 1.39s
largestContentfulPaint 0ms 0ms 0ms 0ms 0ms 0ms
Bundle size diffs
  • background: 58 Bytes (0%)
  • ui: 5 Bytes (0%)
  • common: 796 Bytes (0.01%)

@salimtb salimtb added this pull request to the merge queue Mar 18, 2026
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Mar 18, 2026
@salimtb salimtb added this pull request to the merge queue Mar 18, 2026
Merged via the queue into main with commit 94854da Mar 18, 2026
182 of 183 checks passed
@salimtb salimtb deleted the fix/assets-unify-state-token-list-refresh-after-import branch March 18, 2026 11:28
@github-actions github-actions bot locked and limited conversation to collaborators Mar 18, 2026
@metamaskbot metamaskbot added the release-13.24.0 Issue or pull request that will be included in release 13.24.0 label Mar 18, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

release-13.24.0 Issue or pull request that will be included in release 13.24.0 size-M team-assets

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants