chore: integrate storage service with tokenListController#39250
chore: integrate storage service with tokenListController#39250sahar-fehri merged 25 commits intomainfrom
Conversation
|
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. |
Builds ready [13dcdbf]
📊 Page Load Benchmark ResultsCurrent Commit: 📄 Localhost MetaMask Test DappSamples: 100 Summary
📈 Detailed Results
Bundle size diffs [🚨 Warning! Bundle size has increased!]
|
package.json
Outdated
| "yarn-binary:hydrate": "corepack hydrate .yarn/yarn-corepack.tgz --activate" | ||
| }, | ||
| "resolutions": { | ||
| "@metamask/assets-controllers@^95.1.0": "npm:@metamask-previews/assets-controllers@95.1.0-preview-009e026d", |
There was a problem hiding this comment.
Should be updated once core is released
Builds ready [a06c10a]
📊 Page Load Benchmark ResultsCurrent Commit: 📄 Localhost MetaMask Test DappSamples: 100 Summary
📈 Detailed Results
Bundle size diffs [🚨 Warning! Bundle size has increased!]
|
Builds ready [a06c10a]
📊 Page Load Benchmark ResultsCurrent Commit: 📄 Localhost MetaMask Test DappSamples: 100 Summary
📈 Detailed Results
Bundle size diffs [🚨 Warning! Bundle size has increased!]
|
|
All alerts resolved. Learn more about Socket for GitHub. This PR previously contained dependency changes with security issues that have been resolved, removed, or ignored. |
Builds ready [855d67d]
UI Startup Metrics (1315 ± 119 ms)
📊 Page Load Benchmark ResultsCurrent Commit: 📄 Localhost MetaMask Test DappSamples: 100 Summary
📈 Detailed Results
Bundle size diffs [🚨 Warning! Bundle size has increased!]
|
Builds ready [432378c]
UI Startup Metrics (1346 ± 136 ms)
📊 Page Load Benchmark ResultsCurrent Commit: 📄 Localhost MetaMask Test DappSamples: 100 Summary
📈 Detailed Results
Bundle size diffs [🚨 Warning! Bundle size has increased!]
|
Builds ready [62be895]
UI Startup Metrics (1300 ± 113 ms)
📊 Page Load Benchmark ResultsCurrent Commit: 📄 Localhost MetaMask Test DappSamples: 100 Summary
📈 Detailed Results
Bundle size diffs [🚨 Warning! Bundle size has increased!]
|
Do not merge before this gets in MetaMask/core#7413
Description
Performance Comparison: Per-Chain Token Cache Storage
This PR implements per-chain file storage for
tokensChainsCacheinTokenListController, replacing the single-file approach. Each chain's token list is now stored in a separate file via StorageService, reducing write amplification during incremental updates.📊 Summary
Key Results:
🧪 Test Scenarios
Scenario 1: Cold Restart (Existing User)
Setup: Extension with 10 networks cached (8 popular + Monad + Avalanche), then browser restart.
This PR
Controller Storage:
StorageService - getAllKeys:
StorageService - Per-chain getItem (parallel reads):
Total cache size: ~4.60MB across 10 chains
Main Branch
Cold restart with 10 networks cached (8 popular + Monad + Avalanche):
Comparison
Key insight: The main state loads 158ms faster on this PR because it's ~5MB smaller. The token cache is loaded separately via StorageService in parallel during controller initialization.
Scenario 2: Fresh Onboarding
Setup: Fresh wallet creation, enable all popular networks, wait for token lists to fetch.
This PR
Initial state (fresh install):
Per-chain writes as networks are enabled:
Summary:
Main Branch
Initial state (fresh install):
After clicking "All Popular Networks":
Summary:
Comparison
Key insight: Both branches have continuous background saves. On main branch, every save includes the ~4.4MB token cache. On this PR, the token cache is stored separately via StorageService, making each background save ~5MB smaller.
Scenario 3: Add New Chain
Setup: Existing wallet with cached networks, add a new network.
This PR
Avalanche (0xa86a):
Monad (0x8f):
zkSync Era (0x144):
Polygon (0x89):
Main Branch
Adding Monad (0x8f) to existing cache:
Adding Avalanche (0xa86a) to existing cache:
Note: Each chain addition triggers a full state save that includes ALL TokenListController cache (~4.6MB) plus ALL other controllers (~23MB) = ~28MB total.
Comparison
Token cache write for new chain:
Total state save triggered:
📋 Raw Logs
This PR - Cold Restart
This PR - Onboarding
This PR - Add New Chain
Main Branch - Cold Restart
Main Branch - Onboarding
Main Branch - Add New Chain (Monad + Avalanche)
🔧 How Performance Was Measured
Performance logging was added to:
BrowserStorageAdapter (
app/scripts/lib/stores/browser-storage-adapter.ts)getItem,setItem,getAllKeysoperationsExtensionStore (
app/scripts/lib/stores/extension-store.ts)getAllPersistedStateand controller state writesTo enable logging, set
PERF_LOGGING_ENABLED = truein both files.📝 Logging Code Reference (Main Branch)
The following code was added to
extension-store.tson main branch to capture performance metrics:Helper Functions (add at top of file after imports)
In
get()method - Add at start of method:In
get()method - Add after data is loaded:In
set()method - Add logging:Expected Log Output
Cold Restart:
Write (adding chain or background save):
💡 Key Takeaways
Write amplification eliminated: Adding a single chain now writes only that chain's data (~30-200KB) instead of the entire cache (~4MB)
Faster incremental updates: Per-chain writes are significantly faster than full cache rewrites
Cold restart trade-off: Parallel file reads + getAllKeys adds some overhead vs single file read, but the difference is minimal
Onboarding improvement: Total data written during onboarding is reduced by avoiding cumulative rewrites
✅ PR Branch: Background Writes No Longer Include Token Cache
On this PR branch, background writes show:
Key proof: TokenListController is only 0.04KB in the main state because the ~4.4MB token cache is stored separately in StorageService.
During testing on main branch, we observed that the entire 27.8MB state is being rewritten repeatedly even when the user is idle:
Why This Happens
MetaMask has background processes that trigger state saves:
Each time ANY controller state changes, the entire state (~27.8MB) is serialized and written to storage, including the 4.4MB TokenListController cache that hasn't changed.
Impact Comparison
How This PR Helps
By moving
tokensChainsCacheto StorageService:Changelog
CHANGELOG entry: No user facing changes; this only updates the storage location for tokenListController.
Related issues
Fixes:
Manual testing steps
Screenshots/Recordings
Before
After
Pre-merge author checklist
Pre-merge reviewer checklist
Note
Medium Risk
Medium risk because it changes how
TokenListControllerpersists cached token lists and adds a state migration writing tobrowser.storage.local, which could impact startup/migration behavior if keys or storage operations fail.Overview
Moves
TokenListControllertoken list caching to StorageService. The controller messenger now allowsStorageService:*actions andTokenListControllerInitfirescontroller.initialize()on startup to load cached lists from storage (logging errors but not failing init).Adds migration #190 to preserve existing caches. Migration
190copiestokensChainsCacheentries into per-chainstorageService:TokenListController:tokensChainsCache:{chainId}keys without overwriting existing entries, then clears the in-state cache and bumps fixtures/snapshots to version190.Updates tests and deps for the new storage behavior. Jest mocks for
webextension-polyfillare made async/shared across imports, e2e state persistence ignores StorageService-prefixed keys, and@metamask/assets-controllersis bumped to^98.0.0.Written by Cursor Bugbot for commit 62be895. This will update automatically on new commits. Configure here.