@metamask/assets-controllers 105.1.0 → 106.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/CHANGELOG.md +49 -1
  2. package/dist/AccountTrackerController-method-action-types.cjs.map +1 -1
  3. package/dist/AccountTrackerController-method-action-types.d.cts +24 -1
  4. package/dist/AccountTrackerController-method-action-types.d.cts.map +1 -1
  5. package/dist/AccountTrackerController-method-action-types.d.mts +24 -1
  6. package/dist/AccountTrackerController-method-action-types.d.mts.map +1 -1
  7. package/dist/AccountTrackerController-method-action-types.mjs.map +1 -1
  8. package/dist/AccountTrackerController.cjs +2 -0
  9. package/dist/AccountTrackerController.cjs.map +1 -1
  10. package/dist/AccountTrackerController.d.cts.map +1 -1
  11. package/dist/AccountTrackerController.d.mts.map +1 -1
  12. package/dist/AccountTrackerController.mjs +2 -0
  13. package/dist/AccountTrackerController.mjs.map +1 -1
  14. package/dist/TokenBalancesController-method-action-types.cjs.map +1 -1
  15. package/dist/TokenBalancesController-method-action-types.d.cts +9 -1
  16. package/dist/TokenBalancesController-method-action-types.d.cts.map +1 -1
  17. package/dist/TokenBalancesController-method-action-types.d.mts +9 -1
  18. package/dist/TokenBalancesController-method-action-types.d.mts.map +1 -1
  19. package/dist/TokenBalancesController-method-action-types.mjs.map +1 -1
  20. package/dist/TokenBalancesController.cjs +19 -2
  21. package/dist/TokenBalancesController.cjs.map +1 -1
  22. package/dist/TokenBalancesController.d.cts.map +1 -1
  23. package/dist/TokenBalancesController.d.mts.map +1 -1
  24. package/dist/TokenBalancesController.mjs +20 -3
  25. package/dist/TokenBalancesController.mjs.map +1 -1
  26. package/dist/TokenDetectionController-method-action-types.cjs.map +1 -1
  27. package/dist/TokenDetectionController-method-action-types.d.cts +1 -1
  28. package/dist/TokenDetectionController-method-action-types.d.mts +1 -1
  29. package/dist/TokenDetectionController-method-action-types.mjs.map +1 -1
  30. package/dist/TokenDetectionController.cjs +137 -145
  31. package/dist/TokenDetectionController.cjs.map +1 -1
  32. package/dist/TokenDetectionController.d.cts +9 -13
  33. package/dist/TokenDetectionController.d.cts.map +1 -1
  34. package/dist/TokenDetectionController.d.mts +9 -13
  35. package/dist/TokenDetectionController.d.mts.map +1 -1
  36. package/dist/TokenDetectionController.mjs +138 -146
  37. package/dist/TokenDetectionController.mjs.map +1 -1
  38. package/dist/TokenListService.cjs +107 -0
  39. package/dist/TokenListService.cjs.map +1 -0
  40. package/dist/TokenListService.d.cts +40 -0
  41. package/dist/TokenListService.d.cts.map +1 -0
  42. package/dist/TokenListService.d.mts +40 -0
  43. package/dist/TokenListService.d.mts.map +1 -0
  44. package/dist/TokenListService.mjs +102 -0
  45. package/dist/TokenListService.mjs.map +1 -0
  46. package/dist/TokensController.cjs +49 -131
  47. package/dist/TokensController.cjs.map +1 -1
  48. package/dist/TokensController.d.cts +8 -6
  49. package/dist/TokensController.d.cts.map +1 -1
  50. package/dist/TokensController.d.mts +8 -6
  51. package/dist/TokensController.d.mts.map +1 -1
  52. package/dist/TokensController.mjs +49 -131
  53. package/dist/TokensController.mjs.map +1 -1
  54. package/dist/constants.cjs +32 -1
  55. package/dist/constants.cjs.map +1 -1
  56. package/dist/constants.d.cts +15 -0
  57. package/dist/constants.d.cts.map +1 -1
  58. package/dist/constants.d.mts +15 -0
  59. package/dist/constants.d.mts.map +1 -1
  60. package/dist/constants.mjs +31 -0
  61. package/dist/constants.mjs.map +1 -1
  62. package/dist/index.cjs +4 -1
  63. package/dist/index.cjs.map +1 -1
  64. package/dist/index.d.cts +3 -2
  65. package/dist/index.d.cts.map +1 -1
  66. package/dist/index.d.mts +3 -2
  67. package/dist/index.d.mts.map +1 -1
  68. package/dist/index.mjs +1 -0
  69. package/dist/index.mjs.map +1 -1
  70. package/dist/token-prices-service/codefi-v2.d.cts +1 -1
  71. package/dist/token-prices-service/codefi-v2.d.mts +1 -1
  72. package/package.json +7 -6
@@ -9,7 +9,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
9
9
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
10
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
11
  };
12
- var _TokenDetectionController_instances, _TokenDetectionController_intervalId, _TokenDetectionController_selectedAccountId, _TokenDetectionController_tokensChainsCache, _TokenDetectionController_disabled, _TokenDetectionController_isUnlocked, _TokenDetectionController_isDetectionEnabledFromPreferences, _TokenDetectionController_useTokenDetection, _TokenDetectionController_useExternalServices, _TokenDetectionController_defaultTokensSeeded, _TokenDetectionController_getBalancesInSingleCall, _TokenDetectionController_trackMetaMetricsEvent, _TokenDetectionController_registerEventListeners, _TokenDetectionController_stopPolling, _TokenDetectionController_startPolling, _TokenDetectionController_compareTokensChainsCache, _TokenDetectionController_getCorrectNetworkClientIdByChainId, _TokenDetectionController_restartTokenDetection, _TokenDetectionController_shouldDetectTokens, _TokenDetectionController_detectTokensUsingRpc, _TokenDetectionController_getSlicesOfTokensToDetect, _TokenDetectionController_getConvertedStaticMainnetTokenList, _TokenDetectionController_seedDefaultTokens, _TokenDetectionController_addDetectedTokens, _TokenDetectionController_getSelectedAccount, _TokenDetectionController_getSelectedAddress;
12
+ var _TokenDetectionController_instances, _TokenDetectionController_intervalId, _TokenDetectionController_selectedAccountId, _TokenDetectionController_tokenListService, _TokenDetectionController_disabled, _TokenDetectionController_isUnlocked, _TokenDetectionController_isDetectionEnabledFromPreferences, _TokenDetectionController_useTokenDetection, _TokenDetectionController_useExternalServices, _TokenDetectionController_getBalancesInSingleCall, _TokenDetectionController_trackMetaMetricsEvent, _TokenDetectionController_registerEventListeners, _TokenDetectionController_stopPolling, _TokenDetectionController_startPolling, _TokenDetectionController_getCorrectNetworkClientIdByChainId, _TokenDetectionController_restartTokenDetection, _TokenDetectionController_getChainCacheForDetection, _TokenDetectionController_detectTokensUsingRpc, _TokenDetectionController_getSlicesOfTokensToDetect, _TokenDetectionController_getConvertedStaticMainnetTokenList, _TokenDetectionController_buildMusdTokenListToken, _TokenDetectionController_mergeMusdIntoTokenListMap, _TokenDetectionController_applyMusdDefaultToTokensChainsCache, _TokenDetectionController_includeMusdInTokenDetectionSlice, _TokenDetectionController_addDetectedTokens, _TokenDetectionController_getSelectedAccount, _TokenDetectionController_getSelectedAddress;
13
13
  function $importDefault(module) {
14
14
  if (module?.__esModule) {
15
15
  return module.default;
@@ -20,38 +20,9 @@ import $contractMap from "@metamask/contract-metadata";
20
20
  const contractMap = $importDefault($contractMap);
21
21
  import { ASSET_TYPES, ChainId, ERC20, safelyExecute, isEqualCaseInsensitive, toChecksumHexAddress } from "@metamask/controller-utils";
22
22
  import { StaticIntervalPollingController } from "@metamask/polling-controller";
23
- import $lodash from "lodash";
24
- const { isEqual, mapValues, isObject, get } = $lodash;
25
- import { isTokenDetectionSupportedForNetwork } from "./assetsUtil.mjs";
26
- import { SUPPORTED_NETWORKS_ACCOUNTS_API_V4 } from "./constants.mjs";
23
+ import { formatIconUrlWithProxy, isTokenDetectionSupportedForNetwork } from "./assetsUtil.mjs";
24
+ import { MUSD_ERC20_ADDRESS_LOWER, MUSD_TOKEN_DETECTION_CHAIN_IDS, MUSD_TOKEN_METADATA_BY_CHAIN, SUPPORTED_NETWORKS_ACCOUNTS_API_V4 } from "./constants.mjs";
27
25
  const DEFAULT_INTERVAL = 180000;
28
- /**
29
- * Canonical contract address for MetaMask USD (mUSD) — same across every
30
- * chain we deploy it to.
31
- */
32
- const MUSD_ADDRESS = '0xaca92e438df0b2401ff60da7e4337b687a2435da';
33
- /**
34
- * Pre-built Token entry for mUSD — used when seeding default state.
35
- */
36
- const MUSD_TOKEN = {
37
- address: MUSD_ADDRESS,
38
- decimals: 6,
39
- symbol: 'mUSD',
40
- name: 'MetaMask USD',
41
- };
42
- /**
43
- * Hex chain IDs on which mUSD is deployed and should be added by default.
44
- * - 0x1 — Ethereum mainnet (1)
45
- * - 0xe708 — Linea (59144)
46
- * - 0x8f — Monad mainnet (143)
47
- * - 0x279f — Monad testnet (10143)
48
- */
49
- const MUSD_SUPPORTED_CHAIN_IDS = new Set([
50
- '0x1',
51
- '0xe708',
52
- '0x8f',
53
- '0x279f',
54
- ]);
55
26
  export const STATIC_MAINNET_TOKEN_LIST = Object.entries(contractMap).reduce((acc, [base, contract]) => {
56
27
  const { logo, erc20, erc721, ...tokenMetadata } = contract;
57
28
  return {
@@ -64,20 +35,7 @@ export const STATIC_MAINNET_TOKEN_LIST = Object.entries(contractMap).reduce((acc
64
35
  },
65
36
  };
66
37
  }, {});
67
- /**
68
- * Function that takes a TokensChainsCache object and maps chainId with TokenListMap.
69
- *
70
- * @param tokensChainsCache - TokensChainsCache input object
71
- * @returns returns the map of chainId with TokenListMap
72
- */
73
- export function mapChainIdWithTokenListMap(tokensChainsCache) {
74
- return mapValues(tokensChainsCache, (value) => {
75
- if (isObject(value) && 'data' in value) {
76
- return get(value, ['data']);
77
- }
78
- return value;
79
- });
80
- }
38
+ const MUSD_TOKEN_DETECTION_CHAIN_ID_SET = new Set(MUSD_TOKEN_DETECTION_CHAIN_IDS);
81
39
  export const controllerName = 'TokenDetectionController';
82
40
  const MESSENGER_EXPOSED_METHODS = [
83
41
  'addDetectedTokensViaWs',
@@ -110,6 +68,7 @@ export class TokenDetectionController extends StaticIntervalPollingController()
110
68
  *
111
69
  * @param options - The controller options.
112
70
  * @param options.messenger - The controller messenger.
71
+ * @param options.tokenListService - Shared service for fetching the token list per chain.
113
72
  * @param options.disabled - If set to true, all network requests are blocked.
114
73
  * @param options.interval - Polling interval used to fetch new token rates
115
74
  * @param options.getBalancesInSingleCall - Gets the balances of a list of tokens for the given address.
@@ -117,7 +76,7 @@ export class TokenDetectionController extends StaticIntervalPollingController()
117
76
  * @param options.useTokenDetection - Feature Switch for using token detection (default: true)
118
77
  * @param options.useExternalServices - Feature Switch for using external services (default: false)
119
78
  */
120
- constructor({ interval = DEFAULT_INTERVAL, disabled = true, getBalancesInSingleCall, trackMetaMetricsEvent, messenger, useTokenDetection = () => true, useExternalServices = () => true, }) {
79
+ constructor({ interval = DEFAULT_INTERVAL, disabled = true, getBalancesInSingleCall, trackMetaMetricsEvent, messenger, tokenListService, useTokenDetection = () => true, useExternalServices = () => true, }) {
121
80
  super({
122
81
  name: controllerName,
123
82
  messenger,
@@ -127,22 +86,19 @@ export class TokenDetectionController extends StaticIntervalPollingController()
127
86
  _TokenDetectionController_instances.add(this);
128
87
  _TokenDetectionController_intervalId.set(this, void 0);
129
88
  _TokenDetectionController_selectedAccountId.set(this, void 0);
130
- _TokenDetectionController_tokensChainsCache.set(this, {});
89
+ _TokenDetectionController_tokenListService.set(this, void 0);
131
90
  _TokenDetectionController_disabled.set(this, void 0);
132
91
  _TokenDetectionController_isUnlocked.set(this, void 0);
133
92
  _TokenDetectionController_isDetectionEnabledFromPreferences.set(this, void 0);
134
93
  _TokenDetectionController_useTokenDetection.set(this, void 0);
135
94
  _TokenDetectionController_useExternalServices.set(this, void 0);
136
- /** Tracks whether default tokens (mUSD) have been seeded for the current session. */
137
- _TokenDetectionController_defaultTokensSeeded.set(this, false);
138
95
  _TokenDetectionController_getBalancesInSingleCall.set(this, void 0);
139
96
  _TokenDetectionController_trackMetaMetricsEvent.set(this, void 0);
140
97
  messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
141
98
  __classPrivateFieldSet(this, _TokenDetectionController_disabled, disabled, "f");
142
99
  this.setIntervalLength(interval);
143
100
  __classPrivateFieldSet(this, _TokenDetectionController_selectedAccountId, __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_getSelectedAccount).call(this).id, "f");
144
- const { tokensChainsCache } = this.messenger.call('TokenListController:getState');
145
- __classPrivateFieldSet(this, _TokenDetectionController_tokensChainsCache, tokensChainsCache, "f");
101
+ __classPrivateFieldSet(this, _TokenDetectionController_tokenListService, tokenListService, "f");
146
102
  const { useTokenDetection: defaultUseTokenDetection } = this.messenger.call('PreferencesController:getState');
147
103
  __classPrivateFieldSet(this, _TokenDetectionController_isDetectionEnabledFromPreferences, defaultUseTokenDetection, "f");
148
104
  __classPrivateFieldSet(this, _TokenDetectionController_getBalancesInSingleCall, getBalancesInSingleCall, "f");
@@ -178,9 +134,6 @@ export class TokenDetectionController extends StaticIntervalPollingController()
178
134
  */
179
135
  async start() {
180
136
  this.enable();
181
- // Seed mUSD as a default token via TokensController:addTokens. Runs
182
- // once per session; idempotent because addTokens dedupes on address.
183
- await __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_seedDefaultTokens).call(this);
184
137
  await __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_startPolling).call(this);
185
138
  }
186
139
  /**
@@ -200,7 +153,7 @@ export class TokenDetectionController extends StaticIntervalPollingController()
200
153
  });
201
154
  }
202
155
  /**
203
- * For each token in the token list provided by the TokenListController, checks the token's balance for the selected account address on the active network.
156
+ * For each token in the token list provided by the TokenListService, checks the token's balance for the selected account address on the active network.
204
157
  * On mainnet, if token detection is disabled in preferences, ERC20 token auto detection will be triggered for each contract address in the legacy token list from the @metamask/contract-metadata repo.
205
158
  *
206
159
  * @param options - Options for token detection.
@@ -257,18 +210,28 @@ export class TokenDetectionController extends StaticIntervalPollingController()
257
210
  if (!__classPrivateFieldGet(this, _TokenDetectionController_useExternalServices, "f").call(this)) {
258
211
  return;
259
212
  }
260
- // Refresh the token cache to ensure we have the latest token metadata
261
- // This fixes a bug where the cache from construction time could be stale/empty
262
- const { tokensChainsCache } = this.messenger.call('TokenListController:getState');
263
- __classPrivateFieldSet(this, _TokenDetectionController_tokensChainsCache, tokensChainsCache ?? {}, "f");
213
+ let tokenListMap;
214
+ try {
215
+ tokenListMap = await __classPrivateFieldGet(this, _TokenDetectionController_tokenListService, "f").fetchTokensByChainId(chainId);
216
+ }
217
+ catch {
218
+ // These methods return void; there is no token array to return.
219
+ // Gracefully exit so the caller is unaffected — the next polling cycle
220
+ // will retry the fetch.
221
+ return;
222
+ }
223
+ const chainCache = __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_applyMusdDefaultToTokensChainsCache).call(this, chainId, {
224
+ [chainId]: { data: tokenListMap, timestamp: Date.now() },
225
+ });
226
+ const effectiveSlice = __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_includeMusdInTokenDetectionSlice).call(this, tokensSlice, chainId);
264
227
  const tokensWithBalance = [];
265
228
  const eventTokensDetails = [];
266
- for (const tokenAddress of tokensSlice) {
229
+ for (const tokenAddress of effectiveSlice) {
267
230
  // Normalize addresses explicitly (don't assume input format)
268
231
  const lowercaseTokenAddress = tokenAddress.toLowerCase();
269
232
  const checksummedTokenAddress = toChecksumHexAddress(tokenAddress);
270
233
  // Check map of validated tokens (cache keys are lowercase)
271
- const tokenData = __classPrivateFieldGet(this, _TokenDetectionController_tokensChainsCache, "f")[chainId]?.data?.[lowercaseTokenAddress];
234
+ const tokenData = chainCache[chainId]?.data?.[lowercaseTokenAddress];
272
235
  if (!tokenData) {
273
236
  continue;
274
237
  }
@@ -324,18 +287,28 @@ export class TokenDetectionController extends StaticIntervalPollingController()
324
287
  if (!__classPrivateFieldGet(this, _TokenDetectionController_useExternalServices, "f").call(this)) {
325
288
  return;
326
289
  }
327
- // Refresh the token cache to ensure we have the latest token metadata
328
- // This fixes a bug where the cache from construction time could be stale/empty
329
- const { tokensChainsCache } = this.messenger.call('TokenListController:getState');
330
- __classPrivateFieldSet(this, _TokenDetectionController_tokensChainsCache, tokensChainsCache ?? {}, "f");
290
+ let tokenListMap;
291
+ try {
292
+ tokenListMap = await __classPrivateFieldGet(this, _TokenDetectionController_tokenListService, "f").fetchTokensByChainId(chainId);
293
+ }
294
+ catch {
295
+ // These methods return void; there is no token array to return.
296
+ // Gracefully exit so the caller is unaffected — the next polling cycle
297
+ // will retry the fetch.
298
+ return;
299
+ }
300
+ const chainCache = __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_applyMusdDefaultToTokensChainsCache).call(this, chainId, {
301
+ [chainId]: { data: tokenListMap, timestamp: Date.now() },
302
+ });
331
303
  const selectedAddress = __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_getSelectedAddress).call(this);
332
304
  // Get current token states to filter out already tracked/ignored tokens
333
305
  const { allTokens, allIgnoredTokens } = this.messenger.call('TokensController:getState');
334
306
  const existingTokenAddresses = (allTokens[chainId]?.[selectedAddress] ?? []).map((token) => token.address.toLowerCase());
335
307
  const ignoredTokenAddresses = (allIgnoredTokens[chainId]?.[selectedAddress] ?? []).map((address) => address.toLowerCase());
308
+ const effectiveSlice = __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_includeMusdInTokenDetectionSlice).call(this, tokensSlice, chainId);
336
309
  const tokensWithBalance = [];
337
310
  const eventTokensDetails = [];
338
- for (const tokenAddress of tokensSlice) {
311
+ for (const tokenAddress of effectiveSlice) {
339
312
  const lowercaseTokenAddress = tokenAddress.toLowerCase();
340
313
  const checksummedTokenAddress = toChecksumHexAddress(tokenAddress);
341
314
  // Skip tokens already in allTokens
@@ -347,7 +320,7 @@ export class TokenDetectionController extends StaticIntervalPollingController()
347
320
  continue;
348
321
  }
349
322
  // Check map of validated tokens (cache keys are lowercase)
350
- const tokenData = __classPrivateFieldGet(this, _TokenDetectionController_tokensChainsCache, "f")[chainId]?.data?.[lowercaseTokenAddress];
323
+ const tokenData = chainCache[chainId]?.data?.[lowercaseTokenAddress];
351
324
  if (!tokenData) {
352
325
  continue;
353
326
  }
@@ -380,7 +353,7 @@ export class TokenDetectionController extends StaticIntervalPollingController()
380
353
  }
381
354
  }
382
355
  }
383
- _TokenDetectionController_intervalId = new WeakMap(), _TokenDetectionController_selectedAccountId = new WeakMap(), _TokenDetectionController_tokensChainsCache = new WeakMap(), _TokenDetectionController_disabled = new WeakMap(), _TokenDetectionController_isUnlocked = new WeakMap(), _TokenDetectionController_isDetectionEnabledFromPreferences = new WeakMap(), _TokenDetectionController_useTokenDetection = new WeakMap(), _TokenDetectionController_useExternalServices = new WeakMap(), _TokenDetectionController_defaultTokensSeeded = new WeakMap(), _TokenDetectionController_getBalancesInSingleCall = new WeakMap(), _TokenDetectionController_trackMetaMetricsEvent = new WeakMap(), _TokenDetectionController_instances = new WeakSet(), _TokenDetectionController_registerEventListeners = function _TokenDetectionController_registerEventListeners() {
356
+ _TokenDetectionController_intervalId = new WeakMap(), _TokenDetectionController_selectedAccountId = new WeakMap(), _TokenDetectionController_tokenListService = new WeakMap(), _TokenDetectionController_disabled = new WeakMap(), _TokenDetectionController_isUnlocked = new WeakMap(), _TokenDetectionController_isDetectionEnabledFromPreferences = new WeakMap(), _TokenDetectionController_useTokenDetection = new WeakMap(), _TokenDetectionController_useExternalServices = new WeakMap(), _TokenDetectionController_getBalancesInSingleCall = new WeakMap(), _TokenDetectionController_trackMetaMetricsEvent = new WeakMap(), _TokenDetectionController_instances = new WeakSet(), _TokenDetectionController_registerEventListeners = function _TokenDetectionController_registerEventListeners() {
384
357
  this.messenger.subscribe('KeyringController:unlock', () => {
385
358
  __classPrivateFieldSet(this, _TokenDetectionController_isUnlocked, true, "f");
386
359
  __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_restartTokenDetection).call(this).catch(() => {
@@ -391,14 +364,6 @@ _TokenDetectionController_intervalId = new WeakMap(), _TokenDetectionController_
391
364
  __classPrivateFieldSet(this, _TokenDetectionController_isUnlocked, false, "f");
392
365
  __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_stopPolling).call(this);
393
366
  });
394
- this.messenger.subscribe('TokenListController:stateChange', ({ tokensChainsCache }) => {
395
- const isEqualValues = __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_compareTokensChainsCache).call(this, tokensChainsCache, __classPrivateFieldGet(this, _TokenDetectionController_tokensChainsCache, "f"));
396
- if (!isEqualValues) {
397
- __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_restartTokenDetection).call(this).catch(() => {
398
- // Silently handle token detection errors
399
- });
400
- }
401
- });
402
367
  this.messenger.subscribe('PreferencesController:stateChange', ({ useTokenDetection }) => {
403
368
  const selectedAccount = __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_getSelectedAccount).call(this);
404
369
  const isDetectionChangedFromPreferences = __classPrivateFieldGet(this, _TokenDetectionController_isDetectionEnabledFromPreferences, "f") !== useTokenDetection;
@@ -417,13 +382,6 @@ _TokenDetectionController_intervalId = new WeakMap(), _TokenDetectionController_
417
382
  const isSelectedAccountIdChanged = __classPrivateFieldGet(this, _TokenDetectionController_selectedAccountId, "f") !== selectedAccount.id;
418
383
  if (isSelectedAccountIdChanged) {
419
384
  __classPrivateFieldSet(this, _TokenDetectionController_selectedAccountId, selectedAccount.id, "f");
420
- // Re-seed mUSD for the newly selected account. addTokens only adds
421
- // tokens for the currently selected account, so we need to re-run
422
- // it whenever the active account changes.
423
- __classPrivateFieldSet(this, _TokenDetectionController_defaultTokensSeeded, false, "f");
424
- __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_seedDefaultTokens).call(this).catch(() => {
425
- // Silently handle default-token seeding errors
426
- });
427
385
  __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_restartTokenDetection).call(this, {
428
386
  selectedAddress: selectedAccount.address,
429
387
  chainIds,
@@ -439,19 +397,6 @@ _TokenDetectionController_intervalId = new WeakMap(), _TokenDetectionController_
439
397
  // Silently handle token detection errors
440
398
  });
441
399
  });
442
- // Re-seed mUSD whenever a network is added. Covers the case where the
443
- // user adds a supported chain (e.g. Monad testnet) after the controller
444
- // has already started — the chain wasn't configured at start() time so
445
- // findNetworkClientIdByChainId would have skipped it.
446
- this.messenger.subscribe('NetworkController:networkAdded', ({ chainId }) => {
447
- if (!MUSD_SUPPORTED_CHAIN_IDS.has(chainId)) {
448
- return;
449
- }
450
- __classPrivateFieldSet(this, _TokenDetectionController_defaultTokensSeeded, false, "f");
451
- __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_seedDefaultTokens).call(this).catch(() => {
452
- // Silently handle default-token seeding errors
453
- });
454
- });
455
400
  }, _TokenDetectionController_stopPolling = function _TokenDetectionController_stopPolling() {
456
401
  if (__classPrivateFieldGet(this, _TokenDetectionController_intervalId, "f")) {
457
402
  clearInterval(__classPrivateFieldGet(this, _TokenDetectionController_intervalId, "f"));
@@ -471,11 +416,6 @@ async function _TokenDetectionController_startPolling() {
471
416
  __classPrivateFieldSet(this, _TokenDetectionController_intervalId, setInterval(async () => {
472
417
  await this.detectTokens();
473
418
  }, this.getIntervalLength()), "f");
474
- }, _TokenDetectionController_compareTokensChainsCache = function _TokenDetectionController_compareTokensChainsCache(tokensChainsCache, previousTokensChainsCache) {
475
- const cleanPreviousTokensChainsCache = mapChainIdWithTokenListMap(previousTokensChainsCache);
476
- const cleanTokensChainsCache = mapChainIdWithTokenListMap(tokensChainsCache);
477
- const isEqualValues = isEqual(cleanTokensChainsCache, cleanPreviousTokensChainsCache);
478
- return isEqualValues;
479
419
  }, _TokenDetectionController_getCorrectNetworkClientIdByChainId = function _TokenDetectionController_getCorrectNetworkClientIdByChainId(chainIds) {
480
420
  const { networkConfigurationsByChainId, selectedNetworkClientId } = this.messenger.call('NetworkController:getState');
481
421
  if (!chainIds) {
@@ -510,30 +450,41 @@ async function _TokenDetectionController_restartTokenDetection({ selectedAddress
510
450
  selectedAddress,
511
451
  });
512
452
  this.setIntervalLength(DEFAULT_INTERVAL);
513
- }, _TokenDetectionController_shouldDetectTokens = function _TokenDetectionController_shouldDetectTokens(chainId) {
453
+ }, _TokenDetectionController_getChainCacheForDetection =
454
+ /**
455
+ * Returns the token cache for `chainId` if detection should proceed, or `null` if it
456
+ * should be skipped. Each call fetches a fresh snapshot from `TokenListService` (which
457
+ * may serve from its in-memory cache) so concurrent calls for different chains never
458
+ * overwrite each other's data.
459
+ *
460
+ * @param chainId - The chain ID to build a detection cache for.
461
+ * @returns A `TokensChainsCache` scoped to `chainId`, or `null` when detection should be skipped.
462
+ */
463
+ async function _TokenDetectionController_getChainCacheForDetection(chainId) {
514
464
  if (!isTokenDetectionSupportedForNetwork(chainId)) {
515
- return false;
465
+ return null;
516
466
  }
517
467
  if (!__classPrivateFieldGet(this, _TokenDetectionController_isDetectionEnabledFromPreferences, "f") &&
518
468
  chainId !== ChainId.mainnet) {
519
- return false;
469
+ return null;
520
470
  }
521
471
  const isMainnetDetectionInactive = !__classPrivateFieldGet(this, _TokenDetectionController_isDetectionEnabledFromPreferences, "f") && chainId === ChainId.mainnet;
522
472
  if (isMainnetDetectionInactive) {
523
- __classPrivateFieldSet(this, _TokenDetectionController_tokensChainsCache, __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_getConvertedStaticMainnetTokenList).call(this), "f");
524
- }
525
- else {
526
- const { tokensChainsCache } = this.messenger.call('TokenListController:getState');
527
- __classPrivateFieldSet(this, _TokenDetectionController_tokensChainsCache, tokensChainsCache ?? {}, "f");
473
+ return __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_getConvertedStaticMainnetTokenList).call(this);
528
474
  }
529
- return true;
475
+ const tokenListMap = await __classPrivateFieldGet(this, _TokenDetectionController_tokenListService, "f").fetchTokensByChainId(chainId);
476
+ return __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_applyMusdDefaultToTokensChainsCache).call(this, chainId, {
477
+ [chainId]: { data: tokenListMap, timestamp: Date.now() },
478
+ });
530
479
  }, _TokenDetectionController_detectTokensUsingRpc = async function _TokenDetectionController_detectTokensUsingRpc(chainsToDetectUsingRpc, addressToDetect) {
531
480
  for (const { chainId, networkClientId } of chainsToDetectUsingRpc) {
532
- if (!__classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_shouldDetectTokens).call(this, chainId)) {
481
+ const chainCache = await __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_getChainCacheForDetection).call(this, chainId);
482
+ if (!chainCache) {
533
483
  continue;
534
484
  }
535
485
  const tokenCandidateSlices = __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_getSlicesOfTokensToDetect).call(this, {
536
486
  chainId,
487
+ chainCache,
537
488
  selectedAddress: addressToDetect,
538
489
  });
539
490
  const tokenDetectionPromises = tokenCandidateSlices.map((tokensSlice) => __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_addDetectedTokens).call(this, {
@@ -541,10 +492,11 @@ async function _TokenDetectionController_restartTokenDetection({ selectedAddress
541
492
  selectedAddress: addressToDetect,
542
493
  networkClientId,
543
494
  chainId,
495
+ chainCache,
544
496
  }));
545
497
  await Promise.all(tokenDetectionPromises);
546
498
  }
547
- }, _TokenDetectionController_getSlicesOfTokensToDetect = function _TokenDetectionController_getSlicesOfTokensToDetect({ chainId, selectedAddress, }) {
499
+ }, _TokenDetectionController_getSlicesOfTokensToDetect = function _TokenDetectionController_getSlicesOfTokensToDetect({ chainId, chainCache, selectedAddress, }) {
548
500
  const { allTokens, allDetectedTokens, allIgnoredTokens } = this.messenger.call('TokensController:getState');
549
501
  const [tokensAddresses, detectedTokensAddresses, ignoredTokensAddresses] = [
550
502
  allTokens,
@@ -552,7 +504,7 @@ async function _TokenDetectionController_restartTokenDetection({ selectedAddress
552
504
  allIgnoredTokens,
553
505
  ].map((tokens) => (tokens[chainId]?.[selectedAddress] ?? []).map((value) => typeof value === 'string' ? value : value.address));
554
506
  const tokensToDetect = [];
555
- for (const tokenAddress of Object.keys(__classPrivateFieldGet(this, _TokenDetectionController_tokensChainsCache, "f")?.[chainId]?.data || {})) {
507
+ for (const tokenAddress of Object.keys(chainCache[chainId]?.data ?? {})) {
556
508
  if ([
557
509
  tokensAddresses,
558
510
  detectedTokensAddresses,
@@ -578,50 +530,66 @@ async function _TokenDetectionController_restartTokenDetection({ selectedAddress
578
530
  iconUrl: value?.iconUrl,
579
531
  },
580
532
  }), {});
533
+ const dataWithMusd = __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_mergeMusdIntoTokenListMap).call(this, ChainId.mainnet, data);
581
534
  return {
582
535
  '0x1': {
583
- data,
536
+ data: dataWithMusd,
584
537
  timestamp: 0,
585
538
  },
586
539
  };
587
- }, _TokenDetectionController_seedDefaultTokens =
588
- /**
589
- * Seed mUSD into `TokensController.allTokens` via the public `addTokens`
590
- * action for every supported chain that is currently configured in
591
- * `NetworkController`.
592
- *
593
- * Runs once per session (idempotent guard via `#defaultTokensSeeded`), but
594
- * `addTokens` itself dedupes by contract address so re-running is safe.
595
- *
596
- * @returns Promise that resolves once seeding has been attempted on every
597
- * supported chain.
598
- */
599
- async function _TokenDetectionController_seedDefaultTokens() {
600
- if (__classPrivateFieldGet(this, _TokenDetectionController_defaultTokensSeeded, "f")) {
601
- return;
540
+ }, _TokenDetectionController_buildMusdTokenListToken = function _TokenDetectionController_buildMusdTokenListToken(chainId) {
541
+ const meta = MUSD_TOKEN_METADATA_BY_CHAIN[chainId];
542
+ return {
543
+ address: MUSD_ERC20_ADDRESS_LOWER,
544
+ name: meta.name,
545
+ symbol: meta.symbol,
546
+ decimals: meta.decimals,
547
+ aggregators: [...meta.aggregators],
548
+ iconUrl: formatIconUrlWithProxy({
549
+ chainId,
550
+ tokenAddress: MUSD_ERC20_ADDRESS_LOWER,
551
+ }),
552
+ occurrences: 999,
553
+ };
554
+ }, _TokenDetectionController_mergeMusdIntoTokenListMap = function _TokenDetectionController_mergeMusdIntoTokenListMap(chainId, data) {
555
+ return {
556
+ ...data,
557
+ [MUSD_ERC20_ADDRESS_LOWER]: __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_buildMusdTokenListToken).call(this, chainId),
558
+ };
559
+ }, _TokenDetectionController_applyMusdDefaultToTokensChainsCache = function _TokenDetectionController_applyMusdDefaultToTokensChainsCache(chainId, cache) {
560
+ if (!MUSD_TOKEN_DETECTION_CHAIN_ID_SET.has(chainId)) {
561
+ return cache;
602
562
  }
603
- __classPrivateFieldSet(this, _TokenDetectionController_defaultTokensSeeded, true, "f");
604
- const { networkConfigurationsByChainId } = this.messenger.call('NetworkController:getState');
605
- for (const supportedChainId of MUSD_SUPPORTED_CHAIN_IDS) {
606
- if (!networkConfigurationsByChainId[supportedChainId]) {
607
- continue;
608
- }
609
- try {
610
- const networkClientId = this.messenger.call('NetworkController:findNetworkClientIdByChainId', supportedChainId);
611
- await this.messenger.call('TokensController:addTokens', [MUSD_TOKEN], networkClientId);
612
- }
613
- catch {
614
- // Silently handle per-chain seeding errors so one failure does not
615
- // block seeding on the remaining supported chains.
616
- }
563
+ const existing = cache[chainId];
564
+ return {
565
+ ...cache,
566
+ [chainId]: {
567
+ data: __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_mergeMusdIntoTokenListMap).call(this, chainId, existing?.data ?? {}),
568
+ timestamp: existing?.timestamp ?? 0,
569
+ },
570
+ };
571
+ }, _TokenDetectionController_includeMusdInTokenDetectionSlice = function _TokenDetectionController_includeMusdInTokenDetectionSlice(tokensSlice, chainId) {
572
+ if (!MUSD_TOKEN_DETECTION_CHAIN_ID_SET.has(chainId)) {
573
+ return tokensSlice;
617
574
  }
618
- }, _TokenDetectionController_addDetectedTokens = async function _TokenDetectionController_addDetectedTokens({ tokensSlice, selectedAddress, networkClientId, chainId, }) {
575
+ if (tokensSlice.some((a) => isEqualCaseInsensitive(a, MUSD_ERC20_ADDRESS_LOWER))) {
576
+ return tokensSlice;
577
+ }
578
+ return [...tokensSlice, MUSD_ERC20_ADDRESS_LOWER];
579
+ }, _TokenDetectionController_addDetectedTokens = async function _TokenDetectionController_addDetectedTokens({ tokensSlice, selectedAddress, networkClientId, chainId, chainCache, }) {
619
580
  await safelyExecute(async () => {
620
581
  const balances = await __classPrivateFieldGet(this, _TokenDetectionController_getBalancesInSingleCall, "f").call(this, selectedAddress, tokensSlice, networkClientId);
582
+ const chainData = chainCache[chainId]?.data ?? {};
621
583
  const tokensWithBalance = [];
622
584
  const eventTokensDetails = [];
623
585
  for (const nonZeroTokenAddress of Object.keys(balances)) {
624
- const { decimals, symbol, aggregators, iconUrl, name, rwaData } = __classPrivateFieldGet(this, _TokenDetectionController_tokensChainsCache, "f")[chainId].data[nonZeroTokenAddress];
586
+ // chainData keys are lowercase (normalised by buildTokenListMap);
587
+ // balance keys are checksummed, so normalise before lookup.
588
+ const tokenListEntry = chainData[nonZeroTokenAddress.toLowerCase()];
589
+ if (!tokenListEntry) {
590
+ continue;
591
+ }
592
+ const { decimals, symbol, aggregators, iconUrl, name, rwaData } = tokenListEntry;
625
593
  eventTokensDetails.push(`${symbol} - ${nonZeroTokenAddress}`);
626
594
  tokensWithBalance.push({
627
595
  address: nonZeroTokenAddress,
@@ -634,6 +602,30 @@ async function _TokenDetectionController_seedDefaultTokens() {
634
602
  ...(rwaData && { rwaData }),
635
603
  });
636
604
  }
605
+ // mUSD is always in the chain token cache on supported networks, but
606
+ // getBalancesInSingleCall omits zero balances; still add mUSD so the wallet
607
+ // shows the asset (balance updates via the usual balance pipeline).
608
+ if (MUSD_TOKEN_DETECTION_CHAIN_ID_SET.has(chainId)) {
609
+ const musdInSlice = tokensSlice.some((addr) => isEqualCaseInsensitive(addr, MUSD_ERC20_ADDRESS_LOWER));
610
+ const musdHasNonZeroFromRpc = Object.keys(balances).some((addr) => isEqualCaseInsensitive(addr, MUSD_ERC20_ADDRESS_LOWER));
611
+ if (musdInSlice && !musdHasNonZeroFromRpc) {
612
+ const musdListToken = Object.entries(chainData).find(([key]) => isEqualCaseInsensitive(key, MUSD_ERC20_ADDRESS_LOWER))?.[1];
613
+ if (musdListToken) {
614
+ const { decimals, symbol, aggregators, iconUrl, name, rwaData } = musdListToken;
615
+ eventTokensDetails.push(`${symbol} - ${MUSD_ERC20_ADDRESS_LOWER}`);
616
+ tokensWithBalance.push({
617
+ address: MUSD_ERC20_ADDRESS_LOWER,
618
+ decimals,
619
+ symbol,
620
+ aggregators,
621
+ image: iconUrl,
622
+ isERC721: false,
623
+ name,
624
+ ...(rwaData && { rwaData }),
625
+ });
626
+ }
627
+ }
628
+ }
637
629
  if (tokensWithBalance.length) {
638
630
  __classPrivateFieldGet(this, _TokenDetectionController_trackMetaMetricsEvent, "f").call(this, {
639
631
  event: 'Token Detected',