@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
@@ -13,43 +13,15 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
13
13
  var __importDefault = (this && this.__importDefault) || function (mod) {
14
14
  return (mod && mod.__esModule) ? mod : { "default": mod };
15
15
  };
16
- 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;
16
+ 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;
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.TokenDetectionController = exports.controllerName = exports.mapChainIdWithTokenListMap = exports.STATIC_MAINNET_TOKEN_LIST = void 0;
18
+ exports.TokenDetectionController = exports.controllerName = exports.STATIC_MAINNET_TOKEN_LIST = void 0;
19
19
  const contract_metadata_1 = __importDefault(require("@metamask/contract-metadata"));
20
20
  const controller_utils_1 = require("@metamask/controller-utils");
21
21
  const polling_controller_1 = require("@metamask/polling-controller");
22
- const lodash_1 = require("lodash");
23
22
  const assetsUtil_1 = require("./assetsUtil.cjs");
24
23
  const constants_1 = require("./constants.cjs");
25
24
  const DEFAULT_INTERVAL = 180000;
26
- /**
27
- * Canonical contract address for MetaMask USD (mUSD) — same across every
28
- * chain we deploy it to.
29
- */
30
- const MUSD_ADDRESS = '0xaca92e438df0b2401ff60da7e4337b687a2435da';
31
- /**
32
- * Pre-built Token entry for mUSD — used when seeding default state.
33
- */
34
- const MUSD_TOKEN = {
35
- address: MUSD_ADDRESS,
36
- decimals: 6,
37
- symbol: 'mUSD',
38
- name: 'MetaMask USD',
39
- };
40
- /**
41
- * Hex chain IDs on which mUSD is deployed and should be added by default.
42
- * - 0x1 — Ethereum mainnet (1)
43
- * - 0xe708 — Linea (59144)
44
- * - 0x8f — Monad mainnet (143)
45
- * - 0x279f — Monad testnet (10143)
46
- */
47
- const MUSD_SUPPORTED_CHAIN_IDS = new Set([
48
- '0x1',
49
- '0xe708',
50
- '0x8f',
51
- '0x279f',
52
- ]);
53
25
  exports.STATIC_MAINNET_TOKEN_LIST = Object.entries(contract_metadata_1.default).reduce((acc, [base, contract]) => {
54
26
  const { logo, erc20, erc721, ...tokenMetadata } = contract;
55
27
  return {
@@ -62,21 +34,7 @@ exports.STATIC_MAINNET_TOKEN_LIST = Object.entries(contract_metadata_1.default).
62
34
  },
63
35
  };
64
36
  }, {});
65
- /**
66
- * Function that takes a TokensChainsCache object and maps chainId with TokenListMap.
67
- *
68
- * @param tokensChainsCache - TokensChainsCache input object
69
- * @returns returns the map of chainId with TokenListMap
70
- */
71
- function mapChainIdWithTokenListMap(tokensChainsCache) {
72
- return (0, lodash_1.mapValues)(tokensChainsCache, (value) => {
73
- if ((0, lodash_1.isObject)(value) && 'data' in value) {
74
- return (0, lodash_1.get)(value, ['data']);
75
- }
76
- return value;
77
- });
78
- }
79
- exports.mapChainIdWithTokenListMap = mapChainIdWithTokenListMap;
37
+ const MUSD_TOKEN_DETECTION_CHAIN_ID_SET = new Set(constants_1.MUSD_TOKEN_DETECTION_CHAIN_IDS);
80
38
  exports.controllerName = 'TokenDetectionController';
81
39
  const MESSENGER_EXPOSED_METHODS = [
82
40
  'addDetectedTokensViaWs',
@@ -109,6 +67,7 @@ class TokenDetectionController extends (0, polling_controller_1.StaticIntervalPo
109
67
  *
110
68
  * @param options - The controller options.
111
69
  * @param options.messenger - The controller messenger.
70
+ * @param options.tokenListService - Shared service for fetching the token list per chain.
112
71
  * @param options.disabled - If set to true, all network requests are blocked.
113
72
  * @param options.interval - Polling interval used to fetch new token rates
114
73
  * @param options.getBalancesInSingleCall - Gets the balances of a list of tokens for the given address.
@@ -116,7 +75,7 @@ class TokenDetectionController extends (0, polling_controller_1.StaticIntervalPo
116
75
  * @param options.useTokenDetection - Feature Switch for using token detection (default: true)
117
76
  * @param options.useExternalServices - Feature Switch for using external services (default: false)
118
77
  */
119
- constructor({ interval = DEFAULT_INTERVAL, disabled = true, getBalancesInSingleCall, trackMetaMetricsEvent, messenger, useTokenDetection = () => true, useExternalServices = () => true, }) {
78
+ constructor({ interval = DEFAULT_INTERVAL, disabled = true, getBalancesInSingleCall, trackMetaMetricsEvent, messenger, tokenListService, useTokenDetection = () => true, useExternalServices = () => true, }) {
120
79
  super({
121
80
  name: exports.controllerName,
122
81
  messenger,
@@ -126,22 +85,19 @@ class TokenDetectionController extends (0, polling_controller_1.StaticIntervalPo
126
85
  _TokenDetectionController_instances.add(this);
127
86
  _TokenDetectionController_intervalId.set(this, void 0);
128
87
  _TokenDetectionController_selectedAccountId.set(this, void 0);
129
- _TokenDetectionController_tokensChainsCache.set(this, {});
88
+ _TokenDetectionController_tokenListService.set(this, void 0);
130
89
  _TokenDetectionController_disabled.set(this, void 0);
131
90
  _TokenDetectionController_isUnlocked.set(this, void 0);
132
91
  _TokenDetectionController_isDetectionEnabledFromPreferences.set(this, void 0);
133
92
  _TokenDetectionController_useTokenDetection.set(this, void 0);
134
93
  _TokenDetectionController_useExternalServices.set(this, void 0);
135
- /** Tracks whether default tokens (mUSD) have been seeded for the current session. */
136
- _TokenDetectionController_defaultTokensSeeded.set(this, false);
137
94
  _TokenDetectionController_getBalancesInSingleCall.set(this, void 0);
138
95
  _TokenDetectionController_trackMetaMetricsEvent.set(this, void 0);
139
96
  messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
140
97
  __classPrivateFieldSet(this, _TokenDetectionController_disabled, disabled, "f");
141
98
  this.setIntervalLength(interval);
142
99
  __classPrivateFieldSet(this, _TokenDetectionController_selectedAccountId, __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_getSelectedAccount).call(this).id, "f");
143
- const { tokensChainsCache } = this.messenger.call('TokenListController:getState');
144
- __classPrivateFieldSet(this, _TokenDetectionController_tokensChainsCache, tokensChainsCache, "f");
100
+ __classPrivateFieldSet(this, _TokenDetectionController_tokenListService, tokenListService, "f");
145
101
  const { useTokenDetection: defaultUseTokenDetection } = this.messenger.call('PreferencesController:getState');
146
102
  __classPrivateFieldSet(this, _TokenDetectionController_isDetectionEnabledFromPreferences, defaultUseTokenDetection, "f");
147
103
  __classPrivateFieldSet(this, _TokenDetectionController_getBalancesInSingleCall, getBalancesInSingleCall, "f");
@@ -177,9 +133,6 @@ class TokenDetectionController extends (0, polling_controller_1.StaticIntervalPo
177
133
  */
178
134
  async start() {
179
135
  this.enable();
180
- // Seed mUSD as a default token via TokensController:addTokens. Runs
181
- // once per session; idempotent because addTokens dedupes on address.
182
- await __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_seedDefaultTokens).call(this);
183
136
  await __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_startPolling).call(this);
184
137
  }
185
138
  /**
@@ -199,7 +152,7 @@ class TokenDetectionController extends (0, polling_controller_1.StaticIntervalPo
199
152
  });
200
153
  }
201
154
  /**
202
- * 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.
155
+ * 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.
203
156
  * 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.
204
157
  *
205
158
  * @param options - Options for token detection.
@@ -256,18 +209,28 @@ class TokenDetectionController extends (0, polling_controller_1.StaticIntervalPo
256
209
  if (!__classPrivateFieldGet(this, _TokenDetectionController_useExternalServices, "f").call(this)) {
257
210
  return;
258
211
  }
259
- // Refresh the token cache to ensure we have the latest token metadata
260
- // This fixes a bug where the cache from construction time could be stale/empty
261
- const { tokensChainsCache } = this.messenger.call('TokenListController:getState');
262
- __classPrivateFieldSet(this, _TokenDetectionController_tokensChainsCache, tokensChainsCache ?? {}, "f");
212
+ let tokenListMap;
213
+ try {
214
+ tokenListMap = await __classPrivateFieldGet(this, _TokenDetectionController_tokenListService, "f").fetchTokensByChainId(chainId);
215
+ }
216
+ catch {
217
+ // These methods return void; there is no token array to return.
218
+ // Gracefully exit so the caller is unaffected — the next polling cycle
219
+ // will retry the fetch.
220
+ return;
221
+ }
222
+ const chainCache = __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_applyMusdDefaultToTokensChainsCache).call(this, chainId, {
223
+ [chainId]: { data: tokenListMap, timestamp: Date.now() },
224
+ });
225
+ const effectiveSlice = __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_includeMusdInTokenDetectionSlice).call(this, tokensSlice, chainId);
263
226
  const tokensWithBalance = [];
264
227
  const eventTokensDetails = [];
265
- for (const tokenAddress of tokensSlice) {
228
+ for (const tokenAddress of effectiveSlice) {
266
229
  // Normalize addresses explicitly (don't assume input format)
267
230
  const lowercaseTokenAddress = tokenAddress.toLowerCase();
268
231
  const checksummedTokenAddress = (0, controller_utils_1.toChecksumHexAddress)(tokenAddress);
269
232
  // Check map of validated tokens (cache keys are lowercase)
270
- const tokenData = __classPrivateFieldGet(this, _TokenDetectionController_tokensChainsCache, "f")[chainId]?.data?.[lowercaseTokenAddress];
233
+ const tokenData = chainCache[chainId]?.data?.[lowercaseTokenAddress];
271
234
  if (!tokenData) {
272
235
  continue;
273
236
  }
@@ -323,18 +286,28 @@ class TokenDetectionController extends (0, polling_controller_1.StaticIntervalPo
323
286
  if (!__classPrivateFieldGet(this, _TokenDetectionController_useExternalServices, "f").call(this)) {
324
287
  return;
325
288
  }
326
- // Refresh the token cache to ensure we have the latest token metadata
327
- // This fixes a bug where the cache from construction time could be stale/empty
328
- const { tokensChainsCache } = this.messenger.call('TokenListController:getState');
329
- __classPrivateFieldSet(this, _TokenDetectionController_tokensChainsCache, tokensChainsCache ?? {}, "f");
289
+ let tokenListMap;
290
+ try {
291
+ tokenListMap = await __classPrivateFieldGet(this, _TokenDetectionController_tokenListService, "f").fetchTokensByChainId(chainId);
292
+ }
293
+ catch {
294
+ // These methods return void; there is no token array to return.
295
+ // Gracefully exit so the caller is unaffected — the next polling cycle
296
+ // will retry the fetch.
297
+ return;
298
+ }
299
+ const chainCache = __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_applyMusdDefaultToTokensChainsCache).call(this, chainId, {
300
+ [chainId]: { data: tokenListMap, timestamp: Date.now() },
301
+ });
330
302
  const selectedAddress = __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_getSelectedAddress).call(this);
331
303
  // Get current token states to filter out already tracked/ignored tokens
332
304
  const { allTokens, allIgnoredTokens } = this.messenger.call('TokensController:getState');
333
305
  const existingTokenAddresses = (allTokens[chainId]?.[selectedAddress] ?? []).map((token) => token.address.toLowerCase());
334
306
  const ignoredTokenAddresses = (allIgnoredTokens[chainId]?.[selectedAddress] ?? []).map((address) => address.toLowerCase());
307
+ const effectiveSlice = __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_includeMusdInTokenDetectionSlice).call(this, tokensSlice, chainId);
335
308
  const tokensWithBalance = [];
336
309
  const eventTokensDetails = [];
337
- for (const tokenAddress of tokensSlice) {
310
+ for (const tokenAddress of effectiveSlice) {
338
311
  const lowercaseTokenAddress = tokenAddress.toLowerCase();
339
312
  const checksummedTokenAddress = (0, controller_utils_1.toChecksumHexAddress)(tokenAddress);
340
313
  // Skip tokens already in allTokens
@@ -346,7 +319,7 @@ class TokenDetectionController extends (0, polling_controller_1.StaticIntervalPo
346
319
  continue;
347
320
  }
348
321
  // Check map of validated tokens (cache keys are lowercase)
349
- const tokenData = __classPrivateFieldGet(this, _TokenDetectionController_tokensChainsCache, "f")[chainId]?.data?.[lowercaseTokenAddress];
322
+ const tokenData = chainCache[chainId]?.data?.[lowercaseTokenAddress];
350
323
  if (!tokenData) {
351
324
  continue;
352
325
  }
@@ -380,7 +353,7 @@ class TokenDetectionController extends (0, polling_controller_1.StaticIntervalPo
380
353
  }
381
354
  }
382
355
  exports.TokenDetectionController = TokenDetectionController;
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 = (0, lodash_1.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 (!(0, assetsUtil_1.isTokenDetectionSupportedForNetwork)(chainId)) {
515
- return false;
465
+ return null;
516
466
  }
517
467
  if (!__classPrivateFieldGet(this, _TokenDetectionController_isDetectionEnabledFromPreferences, "f") &&
518
468
  chainId !== controller_utils_1.ChainId.mainnet) {
519
- return false;
469
+ return null;
520
470
  }
521
471
  const isMainnetDetectionInactive = !__classPrivateFieldGet(this, _TokenDetectionController_isDetectionEnabledFromPreferences, "f") && chainId === controller_utils_1.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, controller_utils_1.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 = constants_1.MUSD_TOKEN_METADATA_BY_CHAIN[chainId];
542
+ return {
543
+ address: constants_1.MUSD_ERC20_ADDRESS_LOWER,
544
+ name: meta.name,
545
+ symbol: meta.symbol,
546
+ decimals: meta.decimals,
547
+ aggregators: [...meta.aggregators],
548
+ iconUrl: (0, assetsUtil_1.formatIconUrlWithProxy)({
549
+ chainId,
550
+ tokenAddress: constants_1.MUSD_ERC20_ADDRESS_LOWER,
551
+ }),
552
+ occurrences: 999,
553
+ };
554
+ }, _TokenDetectionController_mergeMusdIntoTokenListMap = function _TokenDetectionController_mergeMusdIntoTokenListMap(chainId, data) {
555
+ return {
556
+ ...data,
557
+ [constants_1.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) => (0, controller_utils_1.isEqualCaseInsensitive)(a, constants_1.MUSD_ERC20_ADDRESS_LOWER))) {
576
+ return tokensSlice;
577
+ }
578
+ return [...tokensSlice, constants_1.MUSD_ERC20_ADDRESS_LOWER];
579
+ }, _TokenDetectionController_addDetectedTokens = async function _TokenDetectionController_addDetectedTokens({ tokensSlice, selectedAddress, networkClientId, chainId, chainCache, }) {
619
580
  await (0, controller_utils_1.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) => (0, controller_utils_1.isEqualCaseInsensitive)(addr, constants_1.MUSD_ERC20_ADDRESS_LOWER));
610
+ const musdHasNonZeroFromRpc = Object.keys(balances).some((addr) => (0, controller_utils_1.isEqualCaseInsensitive)(addr, constants_1.MUSD_ERC20_ADDRESS_LOWER));
611
+ if (musdInSlice && !musdHasNonZeroFromRpc) {
612
+ const musdListToken = Object.entries(chainData).find(([key]) => (0, controller_utils_1.isEqualCaseInsensitive)(key, constants_1.MUSD_ERC20_ADDRESS_LOWER))?.[1];
613
+ if (musdListToken) {
614
+ const { decimals, symbol, aggregators, iconUrl, name, rwaData } = musdListToken;
615
+ eventTokensDetails.push(`${symbol} - ${constants_1.MUSD_ERC20_ADDRESS_LOWER}`);
616
+ tokensWithBalance.push({
617
+ address: constants_1.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',