@metamask-previews/assets-controllers 93.1.0-preview-c92c27f1 → 93.1.0-preview-d717276a

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 (42) hide show
  1. package/CHANGELOG.md +8 -10
  2. package/dist/MultichainAssetsRatesController/MultichainAssetsRatesController.cjs +14 -9
  3. package/dist/MultichainAssetsRatesController/MultichainAssetsRatesController.cjs.map +1 -1
  4. package/dist/MultichainAssetsRatesController/MultichainAssetsRatesController.d.cts.map +1 -1
  5. package/dist/MultichainAssetsRatesController/MultichainAssetsRatesController.d.mts.map +1 -1
  6. package/dist/MultichainAssetsRatesController/MultichainAssetsRatesController.mjs +14 -9
  7. package/dist/MultichainAssetsRatesController/MultichainAssetsRatesController.mjs.map +1 -1
  8. package/dist/NftDetectionController.cjs +10 -6
  9. package/dist/NftDetectionController.cjs.map +1 -1
  10. package/dist/NftDetectionController.d.cts +4 -0
  11. package/dist/NftDetectionController.d.cts.map +1 -1
  12. package/dist/NftDetectionController.d.mts +4 -0
  13. package/dist/NftDetectionController.d.mts.map +1 -1
  14. package/dist/NftDetectionController.mjs +10 -6
  15. package/dist/NftDetectionController.mjs.map +1 -1
  16. package/dist/Standards/NftStandards/ERC721/ERC721Standard.cjs +1 -1
  17. package/dist/Standards/NftStandards/ERC721/ERC721Standard.cjs.map +1 -1
  18. package/dist/Standards/NftStandards/ERC721/ERC721Standard.mjs +1 -1
  19. package/dist/Standards/NftStandards/ERC721/ERC721Standard.mjs.map +1 -1
  20. package/dist/TokenBalancesController.cjs +377 -337
  21. package/dist/TokenBalancesController.cjs.map +1 -1
  22. package/dist/TokenBalancesController.d.cts +38 -17
  23. package/dist/TokenBalancesController.d.cts.map +1 -1
  24. package/dist/TokenBalancesController.d.mts +38 -17
  25. package/dist/TokenBalancesController.d.mts.map +1 -1
  26. package/dist/TokenBalancesController.mjs +377 -337
  27. package/dist/TokenBalancesController.mjs.map +1 -1
  28. package/dist/TokenDetectionController.cjs +205 -44
  29. package/dist/TokenDetectionController.cjs.map +1 -1
  30. package/dist/TokenDetectionController.d.cts +10 -12
  31. package/dist/TokenDetectionController.d.cts.map +1 -1
  32. package/dist/TokenDetectionController.d.mts +10 -12
  33. package/dist/TokenDetectionController.d.mts.map +1 -1
  34. package/dist/TokenDetectionController.mjs +206 -45
  35. package/dist/TokenDetectionController.mjs.map +1 -1
  36. package/dist/rpc-service/rpc-balance-fetcher.cjs +0 -4
  37. package/dist/rpc-service/rpc-balance-fetcher.cjs.map +1 -1
  38. package/dist/rpc-service/rpc-balance-fetcher.d.cts.map +1 -1
  39. package/dist/rpc-service/rpc-balance-fetcher.d.mts.map +1 -1
  40. package/dist/rpc-service/rpc-balance-fetcher.mjs +0 -4
  41. package/dist/rpc-service/rpc-balance-fetcher.mjs.map +1 -1
  42. package/package.json +3 -3
@@ -13,16 +13,18 @@ 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_getBalancesInSingleCall, _TokenDetectionController_trackMetaMetricsEvent, _TokenDetectionController_registerEventListeners, _TokenDetectionController_stopPolling, _TokenDetectionController_startPolling, _TokenDetectionController_compareTokensChainsCache, _TokenDetectionController_getCorrectNetworkClientIdByChainId, _TokenDetectionController_restartTokenDetection, _TokenDetectionController_shouldDetectTokens, _TokenDetectionController_detectTokensUsingRpc, _TokenDetectionController_getSlicesOfTokensToDetect, _TokenDetectionController_getConvertedStaticMainnetTokenList, _TokenDetectionController_addDetectedTokens, _TokenDetectionController_getSelectedAccount, _TokenDetectionController_getSelectedAddress;
16
+ var _TokenDetectionController_instances, _TokenDetectionController_intervalId, _TokenDetectionController_selectedAccountId, _TokenDetectionController_tokensChainsCache, _TokenDetectionController_disabled, _TokenDetectionController_isUnlocked, _TokenDetectionController_isDetectionEnabledFromPreferences, _TokenDetectionController_useTokenDetection, _TokenDetectionController_useExternalServices, _TokenDetectionController_getBalancesInSingleCall, _TokenDetectionController_trackMetaMetricsEvent, _TokenDetectionController_accountsAPI, _TokenDetectionController_registerEventListeners, _TokenDetectionController_stopPolling, _TokenDetectionController_startPolling, _TokenDetectionController_compareTokensChainsCache, _TokenDetectionController_getCorrectNetworkClientIdByChainId, _TokenDetectionController_restartTokenDetection, _TokenDetectionController_getChainsToDetect, _TokenDetectionController_attemptAccountAPIDetection, _TokenDetectionController_addChainsToRpcDetection, _TokenDetectionController_shouldDetectTokens, _TokenDetectionController_detectTokensUsingRpc, _TokenDetectionController_getSlicesOfTokensToDetect, _TokenDetectionController_getConvertedStaticMainnetTokenList, _TokenDetectionController_addDetectedTokensViaAPI, _TokenDetectionController_filterAndBuildTokensWithBalance, _TokenDetectionController_addDetectedTokens, _TokenDetectionController_getSelectedAccount, _TokenDetectionController_getSelectedAddress;
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
18
  exports.TokenDetectionController = exports.controllerName = exports.mapChainIdWithTokenListMap = 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 utils_1 = require("@metamask/utils");
22
23
  const lodash_1 = require("lodash");
23
24
  const assetsUtil_1 = require("./assetsUtil.cjs");
24
- const constants_1 = require("./constants.cjs");
25
+ const multi_chain_accounts_service_1 = require("./multi-chain-accounts-service/index.cjs");
25
26
  const DEFAULT_INTERVAL = 180000;
27
+ const ACCOUNTS_API_TIMEOUT_MS = 10000;
26
28
  exports.STATIC_MAINNET_TOKEN_LIST = Object.entries(contract_metadata_1.default).reduce((acc, [base, contract]) => {
27
29
  const { logo, erc20, erc721, ...tokenMetadata } = contract;
28
30
  return {
@@ -77,10 +79,12 @@ class TokenDetectionController extends (0, polling_controller_1.StaticIntervalPo
77
79
  * @param options.interval - Polling interval used to fetch new token rates
78
80
  * @param options.getBalancesInSingleCall - Gets the balances of a list of tokens for the given address.
79
81
  * @param options.trackMetaMetricsEvent - Sets options for MetaMetrics event tracking.
82
+ * @param options.useAccountsAPI - Feature Switch for using the accounts API when detecting tokens (default: true)
80
83
  * @param options.useTokenDetection - Feature Switch for using token detection (default: true)
81
84
  * @param options.useExternalServices - Feature Switch for using external services (default: false)
85
+ * @param options.platform - Indicates whether the platform is extension or mobile
82
86
  */
83
- constructor({ interval = DEFAULT_INTERVAL, disabled = true, getBalancesInSingleCall, trackMetaMetricsEvent, messenger, useTokenDetection = () => true, useExternalServices = () => true, }) {
87
+ constructor({ interval = DEFAULT_INTERVAL, disabled = true, getBalancesInSingleCall, trackMetaMetricsEvent, messenger, useAccountsAPI = true, useTokenDetection = () => true, useExternalServices = () => true, platform, }) {
84
88
  super({
85
89
  name: exports.controllerName,
86
90
  messenger,
@@ -98,8 +102,38 @@ class TokenDetectionController extends (0, polling_controller_1.StaticIntervalPo
98
102
  _TokenDetectionController_useExternalServices.set(this, void 0);
99
103
  _TokenDetectionController_getBalancesInSingleCall.set(this, void 0);
100
104
  _TokenDetectionController_trackMetaMetricsEvent.set(this, void 0);
105
+ _TokenDetectionController_accountsAPI.set(this, {
106
+ isAccountsAPIEnabled: true,
107
+ supportedNetworksCache: null,
108
+ platform: '',
109
+ async getSupportedNetworks() {
110
+ /* istanbul ignore next */
111
+ if (!this.isAccountsAPIEnabled) {
112
+ throw new Error('Accounts API Feature Switch is disabled');
113
+ }
114
+ /* istanbul ignore next */
115
+ if (this.supportedNetworksCache) {
116
+ return this.supportedNetworksCache;
117
+ }
118
+ const result = await (0, multi_chain_accounts_service_1.fetchSupportedNetworks)().catch(() => null);
119
+ this.supportedNetworksCache = result;
120
+ return result;
121
+ },
122
+ async getMultiNetworksBalances(address, chainIds, supportedNetworks, jwtToken) {
123
+ const chainIdNumbers = chainIds.map((chainId) => (0, utils_1.hexToNumber)(chainId));
124
+ if (!supportedNetworks ||
125
+ !chainIdNumbers.every((id) => supportedNetworks.includes(id))) {
126
+ const supportedNetworksErrStr = (supportedNetworks ?? []).toString();
127
+ throw new Error(`Unsupported Network: supported networks ${supportedNetworksErrStr}, requested networks: ${chainIdNumbers.toString()}`);
128
+ }
129
+ const result = await (0, multi_chain_accounts_service_1.fetchMultiChainBalances)(address, {
130
+ networks: chainIdNumbers,
131
+ }, this.platform, jwtToken);
132
+ // Return the full response including unprocessedNetworks
133
+ return result;
134
+ },
135
+ });
101
136
  this.messenger.registerActionHandler(`${exports.controllerName}:addDetectedTokensViaWs`, this.addDetectedTokensViaWs.bind(this));
102
- this.messenger.registerActionHandler(`${exports.controllerName}:detectTokens`, this.detectTokens.bind(this));
103
137
  __classPrivateFieldSet(this, _TokenDetectionController_disabled, disabled, "f");
104
138
  this.setIntervalLength(interval);
105
139
  __classPrivateFieldSet(this, _TokenDetectionController_selectedAccountId, __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_getSelectedAccount).call(this).id, "f");
@@ -111,8 +145,10 @@ class TokenDetectionController extends (0, polling_controller_1.StaticIntervalPo
111
145
  __classPrivateFieldSet(this, _TokenDetectionController_trackMetaMetricsEvent, trackMetaMetricsEvent, "f");
112
146
  const { isUnlocked } = this.messenger.call('KeyringController:getState');
113
147
  __classPrivateFieldSet(this, _TokenDetectionController_isUnlocked, isUnlocked, "f");
148
+ __classPrivateFieldGet(this, _TokenDetectionController_accountsAPI, "f").isAccountsAPIEnabled = useAccountsAPI;
114
149
  __classPrivateFieldSet(this, _TokenDetectionController_useTokenDetection, useTokenDetection, "f");
115
150
  __classPrivateFieldSet(this, _TokenDetectionController_useExternalServices, useExternalServices, "f");
151
+ __classPrivateFieldGet(this, _TokenDetectionController_accountsAPI, "f").platform = platform;
116
152
  __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_registerEventListeners).call(this);
117
153
  }
118
154
  /**
@@ -165,33 +201,43 @@ class TokenDetectionController extends (0, polling_controller_1.StaticIntervalPo
165
201
  * @param options - Options for token detection.
166
202
  * @param options.chainIds - The chain IDs of the network client to use.
167
203
  * @param options.selectedAddress - the selectedAddress against which to detect for token balances.
168
- * @param options.forceRpc - Force RPC-based token detection for all specified chains,
169
- * bypassing external services check and ensuring RPC is used even for chains
170
- * that might otherwise be handled by the Accounts API.
171
204
  */
172
- async detectTokens({ chainIds, selectedAddress, forceRpc = false, } = {}) {
205
+ async detectTokens({ chainIds, selectedAddress, } = {}) {
173
206
  if (!this.isActive) {
174
207
  return;
175
208
  }
176
- // When forceRpc is true, bypass the useTokenDetection check to ensure RPC detection runs
177
- if (!forceRpc && !__classPrivateFieldGet(this, _TokenDetectionController_useTokenDetection, "f").call(this)) {
178
- return;
179
- }
180
- // If external services are disabled and not forcing RPC, skip all detection
181
- if (!forceRpc && !__classPrivateFieldGet(this, _TokenDetectionController_useExternalServices, "f").call(this)) {
209
+ if (!__classPrivateFieldGet(this, _TokenDetectionController_useTokenDetection, "f").call(this)) {
182
210
  return;
183
211
  }
184
212
  const addressToDetect = selectedAddress ?? __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_getSelectedAddress).call(this);
185
213
  const clientNetworks = __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_getCorrectNetworkClientIdByChainId).call(this, chainIds);
186
- // If forceRpc is true, use RPC for all chains
187
- // Otherwise, skip chains supported by Accounts API (they are handled by TokenBalancesController)
188
- const chainsToDetectUsingRpc = forceRpc
189
- ? clientNetworks
190
- : clientNetworks.filter(({ chainId }) => !constants_1.SUPPORTED_NETWORKS_ACCOUNTS_API_V4.includes(chainId));
191
- if (chainsToDetectUsingRpc.length === 0) {
192
- return;
214
+ const jwtToken = await (0, controller_utils_1.safelyExecuteWithTimeout)(() => {
215
+ return this.messenger.call('AuthenticationController:getBearerToken');
216
+ }, false, 5000);
217
+ let supportedNetworks;
218
+ if (__classPrivateFieldGet(this, _TokenDetectionController_accountsAPI, "f").isAccountsAPIEnabled && __classPrivateFieldGet(this, _TokenDetectionController_useExternalServices, "f").call(this)) {
219
+ supportedNetworks = await __classPrivateFieldGet(this, _TokenDetectionController_accountsAPI, "f").getSupportedNetworks();
220
+ }
221
+ const { chainsToDetectUsingRpc, chainsToDetectUsingAccountAPI } = __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_getChainsToDetect).call(this, clientNetworks, supportedNetworks);
222
+ // Try detecting tokens via Account API first if conditions allow
223
+ if (supportedNetworks && chainsToDetectUsingAccountAPI.length > 0) {
224
+ const apiResult = await __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_attemptAccountAPIDetection).call(this, chainsToDetectUsingAccountAPI, addressToDetect, supportedNetworks, jwtToken);
225
+ // If the account API call failed or returned undefined, have those chains fall back to RPC detection
226
+ if (!apiResult || apiResult.result === 'failed') {
227
+ __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_addChainsToRpcDetection).call(this, chainsToDetectUsingRpc, chainsToDetectUsingAccountAPI, clientNetworks);
228
+ }
229
+ else if (apiResult?.result === 'success' &&
230
+ apiResult.unprocessedNetworks &&
231
+ apiResult.unprocessedNetworks.length > 0) {
232
+ // Handle unprocessed networks by adding them to RPC detection
233
+ const unprocessedChainIds = apiResult.unprocessedNetworks.map((chainId) => (0, controller_utils_1.toHex)(chainId));
234
+ __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_addChainsToRpcDetection).call(this, chainsToDetectUsingRpc, unprocessedChainIds, clientNetworks);
235
+ }
236
+ }
237
+ // Proceed with RPC detection if there are chains remaining in chainsToDetectUsingRpc
238
+ if (chainsToDetectUsingRpc.length > 0) {
239
+ await __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_detectTokensUsingRpc).call(this, chainsToDetectUsingRpc, addressToDetect);
193
240
  }
194
- await __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_detectTokensUsingRpc).call(this, chainsToDetectUsingRpc, addressToDetect);
195
241
  }
196
242
  /**
197
243
  * Add tokens detected from websocket balance updates
@@ -248,56 +294,46 @@ class TokenDetectionController extends (0, polling_controller_1.StaticIntervalPo
248
294
  }
249
295
  }
250
296
  exports.TokenDetectionController = TokenDetectionController;
251
- _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_getBalancesInSingleCall = new WeakMap(), _TokenDetectionController_trackMetaMetricsEvent = new WeakMap(), _TokenDetectionController_instances = new WeakSet(), _TokenDetectionController_registerEventListeners = function _TokenDetectionController_registerEventListeners() {
252
- this.messenger.subscribe('KeyringController:unlock', () => {
297
+ _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_getBalancesInSingleCall = new WeakMap(), _TokenDetectionController_trackMetaMetricsEvent = new WeakMap(), _TokenDetectionController_accountsAPI = new WeakMap(), _TokenDetectionController_instances = new WeakSet(), _TokenDetectionController_registerEventListeners = function _TokenDetectionController_registerEventListeners() {
298
+ this.messenger.subscribe('KeyringController:unlock', async () => {
253
299
  __classPrivateFieldSet(this, _TokenDetectionController_isUnlocked, true, "f");
254
- __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_restartTokenDetection).call(this).catch(() => {
255
- // Silently handle token detection errors
256
- });
300
+ await __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_restartTokenDetection).call(this);
257
301
  });
258
302
  this.messenger.subscribe('KeyringController:lock', () => {
259
303
  __classPrivateFieldSet(this, _TokenDetectionController_isUnlocked, false, "f");
260
304
  __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_stopPolling).call(this);
261
305
  });
262
- this.messenger.subscribe('TokenListController:stateChange', ({ tokensChainsCache }) => {
306
+ this.messenger.subscribe('TokenListController:stateChange', async ({ tokensChainsCache }) => {
263
307
  const isEqualValues = __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_compareTokensChainsCache).call(this, tokensChainsCache, __classPrivateFieldGet(this, _TokenDetectionController_tokensChainsCache, "f"));
264
308
  if (!isEqualValues) {
265
- __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_restartTokenDetection).call(this).catch(() => {
266
- // Silently handle token detection errors
267
- });
309
+ await __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_restartTokenDetection).call(this);
268
310
  }
269
311
  });
270
- this.messenger.subscribe('PreferencesController:stateChange', ({ useTokenDetection }) => {
312
+ this.messenger.subscribe('PreferencesController:stateChange', async ({ useTokenDetection }) => {
271
313
  const selectedAccount = __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_getSelectedAccount).call(this);
272
314
  const isDetectionChangedFromPreferences = __classPrivateFieldGet(this, _TokenDetectionController_isDetectionEnabledFromPreferences, "f") !== useTokenDetection;
273
315
  __classPrivateFieldSet(this, _TokenDetectionController_isDetectionEnabledFromPreferences, useTokenDetection, "f");
274
316
  if (isDetectionChangedFromPreferences) {
275
- __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_restartTokenDetection).call(this, {
317
+ await __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_restartTokenDetection).call(this, {
276
318
  selectedAddress: selectedAccount.address,
277
- }).catch(() => {
278
- // Silently handle token detection errors
279
319
  });
280
320
  }
281
321
  });
282
- this.messenger.subscribe('AccountsController:selectedEvmAccountChange', (selectedAccount) => {
322
+ this.messenger.subscribe('AccountsController:selectedEvmAccountChange', async (selectedAccount) => {
283
323
  const { networkConfigurationsByChainId } = this.messenger.call('NetworkController:getState');
284
324
  const chainIds = Object.keys(networkConfigurationsByChainId);
285
325
  const isSelectedAccountIdChanged = __classPrivateFieldGet(this, _TokenDetectionController_selectedAccountId, "f") !== selectedAccount.id;
286
326
  if (isSelectedAccountIdChanged) {
287
327
  __classPrivateFieldSet(this, _TokenDetectionController_selectedAccountId, selectedAccount.id, "f");
288
- __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_restartTokenDetection).call(this, {
328
+ await __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_restartTokenDetection).call(this, {
289
329
  selectedAddress: selectedAccount.address,
290
330
  chainIds,
291
- }).catch(() => {
292
- // Silently handle token detection errors
293
331
  });
294
332
  }
295
333
  });
296
- this.messenger.subscribe('TransactionController:transactionConfirmed', (transactionMeta) => {
297
- this.detectTokens({
334
+ this.messenger.subscribe('TransactionController:transactionConfirmed', async (transactionMeta) => {
335
+ await this.detectTokens({
298
336
  chainIds: [transactionMeta.chainId],
299
- }).catch(() => {
300
- // Silently handle token detection errors
301
337
  });
302
338
  });
303
339
  }, _TokenDetectionController_stopPolling = function _TokenDetectionController_stopPolling() {
@@ -358,6 +394,41 @@ async function _TokenDetectionController_restartTokenDetection({ selectedAddress
358
394
  selectedAddress,
359
395
  });
360
396
  this.setIntervalLength(DEFAULT_INTERVAL);
397
+ }, _TokenDetectionController_getChainsToDetect = function _TokenDetectionController_getChainsToDetect(clientNetworks, supportedNetworks) {
398
+ const chainsToDetectUsingAccountAPI = [];
399
+ const chainsToDetectUsingRpc = [];
400
+ clientNetworks.forEach(({ chainId, networkClientId }) => {
401
+ if (supportedNetworks?.includes((0, utils_1.hexToNumber)(chainId))) {
402
+ chainsToDetectUsingAccountAPI.push(chainId);
403
+ }
404
+ else {
405
+ chainsToDetectUsingRpc.push({ chainId, networkClientId });
406
+ }
407
+ });
408
+ return { chainsToDetectUsingRpc, chainsToDetectUsingAccountAPI };
409
+ }, _TokenDetectionController_attemptAccountAPIDetection = async function _TokenDetectionController_attemptAccountAPIDetection(chainsToDetectUsingAccountAPI, addressToDetect, supportedNetworks, jwtToken) {
410
+ const result = await (0, controller_utils_1.safelyExecuteWithTimeout)(async () => {
411
+ return __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_addDetectedTokensViaAPI).call(this, {
412
+ chainIds: chainsToDetectUsingAccountAPI,
413
+ selectedAddress: addressToDetect,
414
+ supportedNetworks,
415
+ jwtToken,
416
+ });
417
+ }, false, ACCOUNTS_API_TIMEOUT_MS);
418
+ if (!result) {
419
+ return { result: 'failed' };
420
+ }
421
+ return result;
422
+ }, _TokenDetectionController_addChainsToRpcDetection = function _TokenDetectionController_addChainsToRpcDetection(chainsToDetectUsingRpc, chainsToDetectUsingAccountAPI, clientNetworks) {
423
+ chainsToDetectUsingAccountAPI.forEach((chainId) => {
424
+ const networkEntry = clientNetworks.find((network) => network.chainId === chainId);
425
+ if (networkEntry) {
426
+ chainsToDetectUsingRpc.push({
427
+ chainId: networkEntry.chainId,
428
+ networkClientId: networkEntry.networkClientId,
429
+ });
430
+ }
431
+ });
361
432
  }, _TokenDetectionController_shouldDetectTokens = function _TokenDetectionController_shouldDetectTokens(chainId) {
362
433
  if (!(0, assetsUtil_1.isTokenDetectionSupportedForNetwork)(chainId)) {
363
434
  return false;
@@ -432,6 +503,96 @@ async function _TokenDetectionController_restartTokenDetection({ selectedAddress
432
503
  timestamp: 0,
433
504
  },
434
505
  };
506
+ }, _TokenDetectionController_addDetectedTokensViaAPI =
507
+ /**
508
+ * This adds detected tokens from the Accounts API, avoiding the multi-call RPC calls for balances
509
+ *
510
+ * @param options - method arguments
511
+ * @param options.selectedAddress - address to check against
512
+ * @param options.chainIds - array of chainIds to check tokens for
513
+ * @param options.supportedNetworks - array of chainIds to check tokens for
514
+ * @param options.jwtToken - JWT token for authentication
515
+ * @returns a success or failed object
516
+ */
517
+ async function _TokenDetectionController_addDetectedTokensViaAPI({ selectedAddress, chainIds, supportedNetworks, jwtToken, }) {
518
+ return await (0, controller_utils_1.safelyExecute)(async () => {
519
+ // Fetch balances for multiple chain IDs at once
520
+ const apiResponse = await __classPrivateFieldGet(this, _TokenDetectionController_accountsAPI, "f")
521
+ .getMultiNetworksBalances(selectedAddress, chainIds, supportedNetworks, jwtToken)
522
+ .catch(() => null);
523
+ if (apiResponse === null) {
524
+ return { result: 'failed' };
525
+ }
526
+ const tokenBalancesByChain = apiResponse.balances;
527
+ // Process each chain ID individually
528
+ for (const chainId of chainIds) {
529
+ const isTokenDetectionInactiveInMainnet = !__classPrivateFieldGet(this, _TokenDetectionController_isDetectionEnabledFromPreferences, "f") &&
530
+ chainId === controller_utils_1.ChainId.mainnet;
531
+ const { tokensChainsCache } = this.messenger.call('TokenListController:getState');
532
+ __classPrivateFieldSet(this, _TokenDetectionController_tokensChainsCache, isTokenDetectionInactiveInMainnet
533
+ ? __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_getConvertedStaticMainnetTokenList).call(this)
534
+ : (tokensChainsCache ?? {}), "f");
535
+ // Generate token candidates based on chainId and selectedAddress
536
+ const tokenCandidateSlices = __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_getSlicesOfTokensToDetect).call(this, {
537
+ chainId,
538
+ selectedAddress,
539
+ });
540
+ // Filter balances for the current chainId
541
+ const tokenBalances = tokenBalancesByChain.filter((balance) => balance.chainId === (0, utils_1.hexToNumber)(chainId));
542
+ if (!tokenBalances || tokenBalances.length === 0) {
543
+ continue;
544
+ }
545
+ // Use helper function to filter tokens with balance for this chainId
546
+ const { tokensWithBalance, eventTokensDetails } = __classPrivateFieldGet(this, _TokenDetectionController_instances, "m", _TokenDetectionController_filterAndBuildTokensWithBalance).call(this, tokenCandidateSlices, tokenBalances, chainId);
547
+ if (tokensWithBalance.length) {
548
+ __classPrivateFieldGet(this, _TokenDetectionController_trackMetaMetricsEvent, "f").call(this, {
549
+ event: 'Token Detected',
550
+ category: 'Wallet',
551
+ properties: {
552
+ tokens: eventTokensDetails,
553
+ token_standard: controller_utils_1.ERC20,
554
+ asset_type: controller_utils_1.ASSET_TYPES.TOKEN,
555
+ },
556
+ });
557
+ const networkClientId = this.messenger.call('NetworkController:findNetworkClientIdByChainId', chainId);
558
+ await this.messenger.call('TokensController:addTokens', tokensWithBalance, networkClientId);
559
+ }
560
+ }
561
+ return {
562
+ result: 'success',
563
+ unprocessedNetworks: apiResponse.unprocessedNetworks,
564
+ };
565
+ });
566
+ }, _TokenDetectionController_filterAndBuildTokensWithBalance = function _TokenDetectionController_filterAndBuildTokensWithBalance(tokenCandidateSlices, tokenBalances, chainId) {
567
+ const tokensWithBalance = [];
568
+ const eventTokensDetails = [];
569
+ const tokenCandidateSet = new Set(tokenCandidateSlices.flat());
570
+ tokenBalances?.forEach((token) => {
571
+ const tokenAddress = token.address;
572
+ // Make sure the token to add is in our candidate list
573
+ if (!tokenCandidateSet.has(tokenAddress)) {
574
+ return;
575
+ }
576
+ // Retrieve token data from cache to safely add it
577
+ const tokenData = __classPrivateFieldGet(this, _TokenDetectionController_tokensChainsCache, "f")[chainId]?.data[tokenAddress];
578
+ // We need specific data from tokensChainsCache to correctly create a token
579
+ // So even if we have a token that was detected correctly by the API, if its missing data we cannot safely add it.
580
+ if (!tokenData) {
581
+ return;
582
+ }
583
+ const { decimals, symbol, aggregators, iconUrl, name } = tokenData;
584
+ eventTokensDetails.push(`${symbol} - ${tokenAddress}`);
585
+ tokensWithBalance.push({
586
+ address: tokenAddress,
587
+ decimals,
588
+ symbol,
589
+ aggregators,
590
+ image: iconUrl,
591
+ isERC721: false,
592
+ name,
593
+ });
594
+ });
595
+ return { tokensWithBalance, eventTokensDetails };
435
596
  }, _TokenDetectionController_addDetectedTokens = async function _TokenDetectionController_addDetectedTokens({ tokensSlice, selectedAddress, networkClientId, chainId, }) {
436
597
  await (0, controller_utils_1.safelyExecute)(async () => {
437
598
  const balances = await __classPrivateFieldGet(this, _TokenDetectionController_getBalancesInSingleCall, "f").call(this, selectedAddress, tokensSlice, networkClientId);
@@ -468,7 +629,7 @@ async function _TokenDetectionController_restartTokenDetection({ selectedAddress
468
629
  }, _TokenDetectionController_getSelectedAddress = function _TokenDetectionController_getSelectedAddress() {
469
630
  // If the address is not defined (or empty), we fallback to the currently selected account's address
470
631
  const account = this.messenger.call('AccountsController:getAccount', __classPrivateFieldGet(this, _TokenDetectionController_selectedAccountId, "f"));
471
- return account?.address ?? '';
632
+ return account?.address || '';
472
633
  };
473
634
  exports.default = TokenDetectionController;
474
635
  //# sourceMappingURL=TokenDetectionController.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"TokenDetectionController.cjs","sourceRoot":"","sources":["../src/TokenDetectionController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AccountsControllerGetSelectedAccountAction,\n AccountsControllerGetAccountAction,\n AccountsControllerSelectedEvmAccountChangeEvent,\n} from '@metamask/accounts-controller';\nimport type {\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n} from '@metamask/base-controller';\nimport contractMap from '@metamask/contract-metadata';\nimport {\n ASSET_TYPES,\n ChainId,\n ERC20,\n safelyExecute,\n isEqualCaseInsensitive,\n toChecksumHexAddress,\n} from '@metamask/controller-utils';\nimport type {\n KeyringControllerGetStateAction,\n KeyringControllerLockEvent,\n KeyringControllerUnlockEvent,\n} from '@metamask/keyring-controller';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport type { Messenger } from '@metamask/messenger';\nimport type {\n NetworkClientId,\n NetworkControllerFindNetworkClientIdByChainIdAction,\n NetworkControllerGetNetworkClientByIdAction,\n NetworkControllerGetNetworkConfigurationByNetworkClientId,\n NetworkControllerGetStateAction,\n NetworkControllerNetworkDidChangeEvent,\n} from '@metamask/network-controller';\nimport { StaticIntervalPollingController } from '@metamask/polling-controller';\nimport type {\n PreferencesControllerGetStateAction,\n PreferencesControllerStateChangeEvent,\n} from '@metamask/preferences-controller';\nimport type { AuthenticationController } from '@metamask/profile-sync-controller';\nimport type { TransactionControllerTransactionConfirmedEvent } from '@metamask/transaction-controller';\nimport type { Hex } from '@metamask/utils';\nimport { isEqual, mapValues, isObject, get } from 'lodash';\n\nimport type { AssetsContractController } from './AssetsContractController';\nimport { isTokenDetectionSupportedForNetwork } from './assetsUtil';\nimport { SUPPORTED_NETWORKS_ACCOUNTS_API_V4 } from './constants';\nimport type {\n GetTokenListState,\n TokenListMap,\n TokenListStateChange,\n TokensChainsCache,\n} from './TokenListController';\nimport type { Token } from './TokenRatesController';\nimport type {\n TokensControllerAddDetectedTokensAction,\n TokensControllerAddTokensAction,\n TokensControllerGetStateAction,\n} from './TokensController';\n\nconst DEFAULT_INTERVAL = 180000;\n\ntype LegacyToken = {\n name: string;\n logo: `${string}.svg`;\n symbol: string;\n decimals: number;\n erc20?: boolean;\n erc721?: boolean;\n};\n\ntype TokenDetectionMap = {\n [P in keyof TokenListMap]: Omit<TokenListMap[P], 'occurrences'>;\n};\n\ntype NetworkClient = {\n chainId: Hex;\n networkClientId: string;\n};\n\nexport const STATIC_MAINNET_TOKEN_LIST = Object.entries<LegacyToken>(\n contractMap,\n).reduce<TokenDetectionMap>((acc, [base, contract]) => {\n const { logo, erc20, erc721, ...tokenMetadata } = contract;\n return {\n ...acc,\n [base.toLowerCase()]: {\n ...tokenMetadata,\n address: base.toLowerCase(),\n iconUrl: `images/contract/${logo}`,\n aggregators: [],\n },\n };\n}, {});\n\n/**\n * Function that takes a TokensChainsCache object and maps chainId with TokenListMap.\n *\n * @param tokensChainsCache - TokensChainsCache input object\n * @returns returns the map of chainId with TokenListMap\n */\nexport function mapChainIdWithTokenListMap(\n tokensChainsCache: TokensChainsCache,\n): Record<string, unknown> {\n return mapValues(tokensChainsCache, (value) => {\n if (isObject(value) && 'data' in value) {\n return get(value, ['data']);\n }\n return value;\n });\n}\n\nexport const controllerName = 'TokenDetectionController';\n\nexport type TokenDetectionState = Record<never, never>;\n\nexport type TokenDetectionControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n TokenDetectionState\n>;\n\nexport type TokenDetectionControllerAddDetectedTokensViaWsAction = {\n type: `TokenDetectionController:addDetectedTokensViaWs`;\n handler: TokenDetectionController['addDetectedTokensViaWs'];\n};\n\nexport type TokenDetectionControllerDetectTokensAction = {\n type: `TokenDetectionController:detectTokens`;\n handler: TokenDetectionController['detectTokens'];\n};\n\nexport type TokenDetectionControllerActions =\n | TokenDetectionControllerGetStateAction\n | TokenDetectionControllerAddDetectedTokensViaWsAction\n | TokenDetectionControllerDetectTokensAction;\n\nexport type AllowedActions =\n | AccountsControllerGetSelectedAccountAction\n | AccountsControllerGetAccountAction\n | NetworkControllerGetNetworkClientByIdAction\n | NetworkControllerGetNetworkConfigurationByNetworkClientId\n | NetworkControllerGetStateAction\n | GetTokenListState\n | KeyringControllerGetStateAction\n | PreferencesControllerGetStateAction\n | TokensControllerGetStateAction\n | TokensControllerAddDetectedTokensAction\n | TokensControllerAddTokensAction\n | NetworkControllerFindNetworkClientIdByChainIdAction\n | AuthenticationController.AuthenticationControllerGetBearerToken;\n\nexport type TokenDetectionControllerStateChangeEvent =\n ControllerStateChangeEvent<typeof controllerName, TokenDetectionState>;\n\nexport type TokenDetectionControllerEvents =\n TokenDetectionControllerStateChangeEvent;\n\nexport type AllowedEvents =\n | AccountsControllerSelectedEvmAccountChangeEvent\n | NetworkControllerNetworkDidChangeEvent\n | TokenListStateChange\n | KeyringControllerLockEvent\n | KeyringControllerUnlockEvent\n | PreferencesControllerStateChangeEvent\n | TransactionControllerTransactionConfirmedEvent;\n\nexport type TokenDetectionControllerMessenger = Messenger<\n typeof controllerName,\n TokenDetectionControllerActions | AllowedActions,\n TokenDetectionControllerEvents | AllowedEvents\n>;\n\n/** The input to start polling for the {@link TokenDetectionController} */\ntype TokenDetectionPollingInput = {\n chainIds: Hex[];\n address: string;\n};\n\n/**\n * Controller that passively polls on a set interval for Tokens auto detection\n *\n * intervalId - Polling interval used to fetch new token rates\n *\n * selectedAddress - Vault selected address\n *\n * networkClientId - The network client ID of the current selected network\n *\n * disabled - Boolean to track if network requests are blocked\n *\n * isUnlocked - Boolean to track if the keyring state is unlocked\n *\n * isDetectionEnabledFromPreferences - Boolean to track if detection is enabled from PreferencesController\n *\n */\nexport class TokenDetectionController extends StaticIntervalPollingController<TokenDetectionPollingInput>()<\n typeof controllerName,\n TokenDetectionState,\n TokenDetectionControllerMessenger\n> {\n #intervalId?: ReturnType<typeof setTimeout>;\n\n #selectedAccountId: string;\n\n #tokensChainsCache: TokensChainsCache = {};\n\n #disabled: boolean;\n\n #isUnlocked: boolean;\n\n #isDetectionEnabledFromPreferences: boolean;\n\n readonly #useTokenDetection: () => boolean;\n\n readonly #useExternalServices: () => boolean;\n\n readonly #getBalancesInSingleCall: AssetsContractController['getBalancesInSingleCall'];\n\n readonly #trackMetaMetricsEvent: (options: {\n event: string;\n category: string;\n properties: {\n tokens: string[];\n // eslint-disable-next-line @typescript-eslint/naming-convention\n token_standard: string;\n // eslint-disable-next-line @typescript-eslint/naming-convention\n asset_type: string;\n };\n }) => void;\n\n /**\n * Creates a TokenDetectionController instance.\n *\n * @param options - The controller options.\n * @param options.messenger - The controller messenger.\n * @param options.disabled - If set to true, all network requests are blocked.\n * @param options.interval - Polling interval used to fetch new token rates\n * @param options.getBalancesInSingleCall - Gets the balances of a list of tokens for the given address.\n * @param options.trackMetaMetricsEvent - Sets options for MetaMetrics event tracking.\n * @param options.useTokenDetection - Feature Switch for using token detection (default: true)\n * @param options.useExternalServices - Feature Switch for using external services (default: false)\n */\n constructor({\n interval = DEFAULT_INTERVAL,\n disabled = true,\n getBalancesInSingleCall,\n trackMetaMetricsEvent,\n messenger,\n useTokenDetection = (): boolean => true,\n useExternalServices = (): boolean => true,\n }: {\n interval?: number;\n disabled?: boolean;\n getBalancesInSingleCall: AssetsContractController['getBalancesInSingleCall'];\n trackMetaMetricsEvent: (options: {\n event: string;\n category: string;\n properties: {\n tokens: string[];\n // eslint-disable-next-line @typescript-eslint/naming-convention\n token_standard: string;\n // eslint-disable-next-line @typescript-eslint/naming-convention\n asset_type: string;\n };\n }) => void;\n messenger: TokenDetectionControllerMessenger;\n useTokenDetection?: () => boolean;\n useExternalServices?: () => boolean;\n }) {\n super({\n name: controllerName,\n messenger,\n state: {},\n metadata: {},\n });\n\n this.messenger.registerActionHandler(\n `${controllerName}:addDetectedTokensViaWs` as const,\n this.addDetectedTokensViaWs.bind(this),\n );\n\n this.messenger.registerActionHandler(\n `${controllerName}:detectTokens` as const,\n this.detectTokens.bind(this),\n );\n\n this.#disabled = disabled;\n this.setIntervalLength(interval);\n\n this.#selectedAccountId = this.#getSelectedAccount().id;\n\n const { tokensChainsCache } = this.messenger.call(\n 'TokenListController:getState',\n );\n\n this.#tokensChainsCache = tokensChainsCache;\n\n const { useTokenDetection: defaultUseTokenDetection } = this.messenger.call(\n 'PreferencesController:getState',\n );\n this.#isDetectionEnabledFromPreferences = defaultUseTokenDetection;\n\n this.#getBalancesInSingleCall = getBalancesInSingleCall;\n\n this.#trackMetaMetricsEvent = trackMetaMetricsEvent;\n\n const { isUnlocked } = this.messenger.call('KeyringController:getState');\n this.#isUnlocked = isUnlocked;\n\n this.#useTokenDetection = useTokenDetection;\n this.#useExternalServices = useExternalServices;\n\n this.#registerEventListeners();\n }\n\n /**\n * Constructor helper for registering this controller's messenger subscriptions to controller events.\n */\n #registerEventListeners(): void {\n this.messenger.subscribe('KeyringController:unlock', () => {\n this.#isUnlocked = true;\n this.#restartTokenDetection().catch(() => {\n // Silently handle token detection errors\n });\n });\n\n this.messenger.subscribe('KeyringController:lock', () => {\n this.#isUnlocked = false;\n this.#stopPolling();\n });\n\n this.messenger.subscribe(\n 'TokenListController:stateChange',\n ({ tokensChainsCache }) => {\n const isEqualValues = this.#compareTokensChainsCache(\n tokensChainsCache,\n this.#tokensChainsCache,\n );\n if (!isEqualValues) {\n this.#restartTokenDetection().catch(() => {\n // Silently handle token detection errors\n });\n }\n },\n );\n\n this.messenger.subscribe(\n 'PreferencesController:stateChange',\n ({ useTokenDetection }) => {\n const selectedAccount = this.#getSelectedAccount();\n const isDetectionChangedFromPreferences =\n this.#isDetectionEnabledFromPreferences !== useTokenDetection;\n\n this.#isDetectionEnabledFromPreferences = useTokenDetection;\n\n if (isDetectionChangedFromPreferences) {\n this.#restartTokenDetection({\n selectedAddress: selectedAccount.address,\n }).catch(() => {\n // Silently handle token detection errors\n });\n }\n },\n );\n\n this.messenger.subscribe(\n 'AccountsController:selectedEvmAccountChange',\n (selectedAccount) => {\n const { networkConfigurationsByChainId } = this.messenger.call(\n 'NetworkController:getState',\n );\n\n const chainIds = Object.keys(networkConfigurationsByChainId) as Hex[];\n const isSelectedAccountIdChanged =\n this.#selectedAccountId !== selectedAccount.id;\n if (isSelectedAccountIdChanged) {\n this.#selectedAccountId = selectedAccount.id;\n this.#restartTokenDetection({\n selectedAddress: selectedAccount.address,\n chainIds,\n }).catch(() => {\n // Silently handle token detection errors\n });\n }\n },\n );\n\n this.messenger.subscribe(\n 'TransactionController:transactionConfirmed',\n (transactionMeta) => {\n this.detectTokens({\n chainIds: [transactionMeta.chainId],\n }).catch(() => {\n // Silently handle token detection errors\n });\n },\n );\n }\n\n /**\n * Allows controller to make active and passive polling requests\n */\n enable(): void {\n this.#disabled = false;\n }\n\n /**\n * Blocks controller from making network calls\n */\n disable(): void {\n this.#disabled = true;\n }\n\n /**\n * Internal isActive state\n *\n * @returns Whether the controller is active (not disabled and keyring is unlocked)\n */\n get isActive(): boolean {\n return !this.#disabled && this.#isUnlocked;\n }\n\n /**\n * Start polling for detected tokens.\n */\n async start(): Promise<void> {\n this.enable();\n await this.#startPolling();\n }\n\n /**\n * Stop polling for detected tokens.\n */\n stop(): void {\n this.disable();\n this.#stopPolling();\n }\n\n #stopPolling(): void {\n if (this.#intervalId) {\n clearInterval(this.#intervalId);\n }\n }\n\n /**\n * Starts a new polling interval.\n */\n async #startPolling(): Promise<void> {\n if (!this.isActive) {\n return;\n }\n this.#stopPolling();\n await this.detectTokens();\n // TODO: Either fix this lint violation or explain why it's necessary to ignore.\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n this.#intervalId = setInterval(async () => {\n await this.detectTokens();\n }, this.getIntervalLength());\n }\n\n /**\n * Compares current and previous tokensChainsCache object focusing only on the data object.\n *\n * @param tokensChainsCache - current tokensChainsCache input object\n * @param previousTokensChainsCache - previous tokensChainsCache input object\n * @returns boolean indicating if the two objects are equal\n */\n\n #compareTokensChainsCache(\n tokensChainsCache: TokensChainsCache,\n previousTokensChainsCache: TokensChainsCache,\n ): boolean {\n const cleanPreviousTokensChainsCache = mapChainIdWithTokenListMap(\n previousTokensChainsCache,\n );\n const cleanTokensChainsCache =\n mapChainIdWithTokenListMap(tokensChainsCache);\n const isEqualValues = isEqual(\n cleanTokensChainsCache,\n cleanPreviousTokensChainsCache,\n );\n return isEqualValues;\n }\n\n #getCorrectNetworkClientIdByChainId(\n chainIds: Hex[] | undefined,\n ): { chainId: Hex; networkClientId: NetworkClientId }[] {\n const { networkConfigurationsByChainId, selectedNetworkClientId } =\n this.messenger.call('NetworkController:getState');\n\n if (!chainIds) {\n const networkConfiguration = this.messenger.call(\n 'NetworkController:getNetworkConfigurationByNetworkClientId',\n selectedNetworkClientId,\n );\n\n return [\n {\n chainId: networkConfiguration?.chainId ?? ChainId.mainnet,\n networkClientId: selectedNetworkClientId,\n },\n ];\n }\n\n return chainIds.map((chainId) => {\n const configuration = networkConfigurationsByChainId[chainId];\n return {\n chainId,\n networkClientId:\n configuration.rpcEndpoints[configuration.defaultRpcEndpointIndex]\n .networkClientId,\n };\n });\n }\n\n async _executePoll({\n chainIds,\n address,\n }: TokenDetectionPollingInput): Promise<void> {\n if (!this.isActive) {\n return;\n }\n await this.detectTokens({\n chainIds,\n selectedAddress: address,\n });\n }\n\n /**\n * Restart token detection polling period and call detectNewTokens\n * in case of address change or user session initialization.\n *\n * @param options - Options for restart token detection.\n * @param options.selectedAddress - the selectedAddress against which to detect for token balances\n * @param options.chainIds - The chain IDs of the network client to use.\n */\n async #restartTokenDetection({\n selectedAddress,\n chainIds,\n }: {\n selectedAddress?: string;\n chainIds?: Hex[];\n } = {}): Promise<void> {\n await this.detectTokens({\n chainIds,\n selectedAddress,\n });\n this.setIntervalLength(DEFAULT_INTERVAL);\n }\n\n #shouldDetectTokens(chainId: Hex): boolean {\n if (!isTokenDetectionSupportedForNetwork(chainId)) {\n return false;\n }\n if (\n !this.#isDetectionEnabledFromPreferences &&\n chainId !== ChainId.mainnet\n ) {\n return false;\n }\n\n const isMainnetDetectionInactive =\n !this.#isDetectionEnabledFromPreferences && chainId === ChainId.mainnet;\n if (isMainnetDetectionInactive) {\n this.#tokensChainsCache = this.#getConvertedStaticMainnetTokenList();\n } else {\n const { tokensChainsCache } = this.messenger.call(\n 'TokenListController:getState',\n );\n this.#tokensChainsCache = tokensChainsCache ?? {};\n }\n\n return true;\n }\n\n async #detectTokensUsingRpc(\n chainsToDetectUsingRpc: NetworkClient[],\n addressToDetect: string,\n ): Promise<void> {\n for (const { chainId, networkClientId } of chainsToDetectUsingRpc) {\n if (!this.#shouldDetectTokens(chainId)) {\n continue;\n }\n\n const tokenCandidateSlices = this.#getSlicesOfTokensToDetect({\n chainId,\n selectedAddress: addressToDetect,\n });\n const tokenDetectionPromises = tokenCandidateSlices.map((tokensSlice) =>\n this.#addDetectedTokens({\n tokensSlice,\n selectedAddress: addressToDetect,\n networkClientId,\n chainId,\n }),\n );\n\n await Promise.all(tokenDetectionPromises);\n }\n }\n\n /**\n * 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.\n * 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.\n *\n * @param options - Options for token detection.\n * @param options.chainIds - The chain IDs of the network client to use.\n * @param options.selectedAddress - the selectedAddress against which to detect for token balances.\n * @param options.forceRpc - Force RPC-based token detection for all specified chains,\n * bypassing external services check and ensuring RPC is used even for chains\n * that might otherwise be handled by the Accounts API.\n */\n async detectTokens({\n chainIds,\n selectedAddress,\n forceRpc = false,\n }: {\n chainIds?: Hex[];\n selectedAddress?: string;\n forceRpc?: boolean;\n } = {}): Promise<void> {\n if (!this.isActive) {\n return;\n }\n\n // When forceRpc is true, bypass the useTokenDetection check to ensure RPC detection runs\n if (!forceRpc && !this.#useTokenDetection()) {\n return;\n }\n\n // If external services are disabled and not forcing RPC, skip all detection\n if (!forceRpc && !this.#useExternalServices()) {\n return;\n }\n\n const addressToDetect = selectedAddress ?? this.#getSelectedAddress();\n const clientNetworks = this.#getCorrectNetworkClientIdByChainId(chainIds);\n\n // If forceRpc is true, use RPC for all chains\n // Otherwise, skip chains supported by Accounts API (they are handled by TokenBalancesController)\n const chainsToDetectUsingRpc = forceRpc\n ? clientNetworks\n : clientNetworks.filter(\n ({ chainId }) =>\n !SUPPORTED_NETWORKS_ACCOUNTS_API_V4.includes(chainId),\n );\n\n if (chainsToDetectUsingRpc.length === 0) {\n return;\n }\n\n await this.#detectTokensUsingRpc(chainsToDetectUsingRpc, addressToDetect);\n }\n\n #getSlicesOfTokensToDetect({\n chainId,\n selectedAddress,\n }: {\n chainId: Hex;\n selectedAddress: string;\n }): string[][] {\n const { allTokens, allDetectedTokens, allIgnoredTokens } =\n this.messenger.call('TokensController:getState');\n const [tokensAddresses, detectedTokensAddresses, ignoredTokensAddresses] = [\n allTokens,\n allDetectedTokens,\n allIgnoredTokens,\n ].map((tokens) =>\n (tokens[chainId]?.[selectedAddress] ?? []).map((value) =>\n typeof value === 'string' ? value : value.address,\n ),\n );\n\n const tokensToDetect: string[] = [];\n for (const tokenAddress of Object.keys(\n this.#tokensChainsCache?.[chainId]?.data || {},\n )) {\n if (\n [\n tokensAddresses,\n detectedTokensAddresses,\n ignoredTokensAddresses,\n ].every(\n (addresses) =>\n !addresses.find((address) =>\n isEqualCaseInsensitive(address, tokenAddress),\n ),\n )\n ) {\n tokensToDetect.push(tokenAddress);\n }\n }\n\n const slicesOfTokensToDetect = [];\n for (let i = 0, size = 1000; i < tokensToDetect.length; i += size) {\n slicesOfTokensToDetect.push(tokensToDetect.slice(i, i + size));\n }\n\n return slicesOfTokensToDetect;\n }\n\n #getConvertedStaticMainnetTokenList(): TokensChainsCache {\n const data: TokenListMap = Object.entries(STATIC_MAINNET_TOKEN_LIST).reduce(\n (acc, [key, value]) => ({\n ...acc,\n [key]: {\n name: value.name,\n symbol: value.symbol,\n decimals: value.decimals,\n address: value.address,\n aggregators: [],\n iconUrl: value?.iconUrl,\n },\n }),\n {},\n );\n return {\n '0x1': {\n data,\n timestamp: 0,\n },\n };\n }\n\n async #addDetectedTokens({\n tokensSlice,\n selectedAddress,\n networkClientId,\n chainId,\n }: {\n tokensSlice: string[];\n selectedAddress: string;\n networkClientId: NetworkClientId;\n chainId: Hex;\n }): Promise<void> {\n await safelyExecute(async () => {\n const balances = await this.#getBalancesInSingleCall(\n selectedAddress,\n tokensSlice,\n networkClientId,\n );\n\n const tokensWithBalance: Token[] = [];\n const eventTokensDetails: string[] = [];\n for (const nonZeroTokenAddress of Object.keys(balances)) {\n const { decimals, symbol, aggregators, iconUrl, name } =\n this.#tokensChainsCache[chainId].data[nonZeroTokenAddress];\n eventTokensDetails.push(`${symbol} - ${nonZeroTokenAddress}`);\n tokensWithBalance.push({\n address: nonZeroTokenAddress,\n decimals,\n symbol,\n aggregators,\n image: iconUrl,\n isERC721: false,\n name,\n });\n }\n\n if (tokensWithBalance.length) {\n this.#trackMetaMetricsEvent({\n event: 'Token Detected',\n category: 'Wallet',\n properties: {\n tokens: eventTokensDetails,\n token_standard: ERC20,\n asset_type: ASSET_TYPES.TOKEN,\n },\n });\n\n await this.messenger.call(\n 'TokensController:addTokens',\n tokensWithBalance,\n networkClientId,\n );\n }\n });\n }\n\n /**\n * Add tokens detected from websocket balance updates\n * This method assumes:\n * - Tokens are already in the tokensChainsCache with full metadata\n * - Balance fetching is skipped since balances are provided by the websocket\n * - Ignored tokens have been filtered out by the caller\n *\n * @param options - The options object\n * @param options.tokensSlice - Array of token addresses detected from websocket (already filtered to exclude ignored tokens)\n * @param options.chainId - Hex chain ID\n * @returns Promise that resolves when tokens are added\n */\n async addDetectedTokensViaWs({\n tokensSlice,\n chainId,\n }: {\n tokensSlice: string[];\n chainId: Hex;\n }): Promise<void> {\n const tokensWithBalance: Token[] = [];\n const eventTokensDetails: string[] = [];\n\n for (const tokenAddress of tokensSlice) {\n // Normalize addresses explicitly (don't assume input format)\n const lowercaseTokenAddress = tokenAddress.toLowerCase();\n const checksummedTokenAddress = toChecksumHexAddress(tokenAddress);\n\n // Check map of validated tokens (cache keys are lowercase)\n const tokenData =\n this.#tokensChainsCache[chainId]?.data?.[lowercaseTokenAddress];\n\n if (!tokenData) {\n console.warn(\n `Token metadata not found in cache for ${tokenAddress} on chain ${chainId}`,\n );\n continue;\n }\n\n const { decimals, symbol, aggregators, iconUrl, name } = tokenData;\n\n // Push to lists with checksummed address (for allTokens storage)\n eventTokensDetails.push(`${symbol} - ${checksummedTokenAddress}`);\n tokensWithBalance.push({\n address: checksummedTokenAddress,\n decimals,\n symbol,\n aggregators,\n image: iconUrl,\n isERC721: false,\n name,\n });\n }\n\n // Perform addition\n if (tokensWithBalance.length) {\n this.#trackMetaMetricsEvent({\n event: 'Token Detected',\n category: 'Wallet',\n properties: {\n tokens: eventTokensDetails,\n token_standard: ERC20,\n asset_type: ASSET_TYPES.TOKEN,\n },\n });\n\n const networkClientId = this.messenger.call(\n 'NetworkController:findNetworkClientIdByChainId',\n chainId,\n );\n\n await this.messenger.call(\n 'TokensController:addTokens',\n tokensWithBalance,\n networkClientId,\n );\n }\n }\n\n #getSelectedAccount(): InternalAccount {\n return this.messenger.call('AccountsController:getSelectedAccount');\n }\n\n #getSelectedAddress(): string {\n // If the address is not defined (or empty), we fallback to the currently selected account's address\n const account = this.messenger.call(\n 'AccountsController:getAccount',\n this.#selectedAccountId,\n );\n return account?.address ?? '';\n }\n}\n\nexport default TokenDetectionController;\n"]}
1
+ {"version":3,"file":"TokenDetectionController.cjs","sourceRoot":"","sources":["../src/TokenDetectionController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AccountsControllerGetSelectedAccountAction,\n AccountsControllerGetAccountAction,\n AccountsControllerSelectedEvmAccountChangeEvent,\n} from '@metamask/accounts-controller';\nimport type {\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n} from '@metamask/base-controller';\nimport contractMap from '@metamask/contract-metadata';\nimport {\n ASSET_TYPES,\n ChainId,\n ERC20,\n safelyExecute,\n safelyExecuteWithTimeout,\n isEqualCaseInsensitive,\n toChecksumHexAddress,\n toHex,\n} from '@metamask/controller-utils';\nimport type {\n KeyringControllerGetStateAction,\n KeyringControllerLockEvent,\n KeyringControllerUnlockEvent,\n} from '@metamask/keyring-controller';\nimport type { Messenger } from '@metamask/messenger';\nimport type {\n NetworkClientId,\n NetworkControllerFindNetworkClientIdByChainIdAction,\n NetworkControllerGetNetworkClientByIdAction,\n NetworkControllerGetNetworkConfigurationByNetworkClientId,\n NetworkControllerGetStateAction,\n NetworkControllerNetworkDidChangeEvent,\n} from '@metamask/network-controller';\nimport { StaticIntervalPollingController } from '@metamask/polling-controller';\nimport type {\n PreferencesControllerGetStateAction,\n PreferencesControllerStateChangeEvent,\n} from '@metamask/preferences-controller';\nimport type { AuthenticationController } from '@metamask/profile-sync-controller';\nimport type { TransactionControllerTransactionConfirmedEvent } from '@metamask/transaction-controller';\nimport type { Hex } from '@metamask/utils';\nimport { hexToNumber } from '@metamask/utils';\nimport { isEqual, mapValues, isObject, get } from 'lodash';\n\nimport type { AssetsContractController } from './AssetsContractController';\nimport { isTokenDetectionSupportedForNetwork } from './assetsUtil';\nimport {\n fetchMultiChainBalances,\n fetchSupportedNetworks,\n} from './multi-chain-accounts-service';\nimport type {\n GetTokenListState,\n TokenListMap,\n TokenListStateChange,\n TokensChainsCache,\n} from './TokenListController';\nimport type { Token } from './TokenRatesController';\nimport type {\n TokensControllerAddDetectedTokensAction,\n TokensControllerAddTokensAction,\n TokensControllerGetStateAction,\n} from './TokensController';\n\nconst DEFAULT_INTERVAL = 180000;\nconst ACCOUNTS_API_TIMEOUT_MS = 10000;\n\ntype LegacyToken = {\n name: string;\n logo: `${string}.svg`;\n symbol: string;\n decimals: number;\n erc20?: boolean;\n erc721?: boolean;\n};\n\ntype TokenDetectionMap = {\n [P in keyof TokenListMap]: Omit<TokenListMap[P], 'occurrences'>;\n};\n\ntype NetworkClient = {\n chainId: Hex;\n networkClientId: string;\n};\n\nexport const STATIC_MAINNET_TOKEN_LIST = Object.entries<LegacyToken>(\n contractMap,\n).reduce<TokenDetectionMap>((acc, [base, contract]) => {\n const { logo, erc20, erc721, ...tokenMetadata } = contract;\n return {\n ...acc,\n [base.toLowerCase()]: {\n ...tokenMetadata,\n address: base.toLowerCase(),\n iconUrl: `images/contract/${logo}`,\n aggregators: [],\n },\n };\n}, {});\n\n/**\n * Function that takes a TokensChainsCache object and maps chainId with TokenListMap.\n *\n * @param tokensChainsCache - TokensChainsCache input object\n * @returns returns the map of chainId with TokenListMap\n */\nexport function mapChainIdWithTokenListMap(\n tokensChainsCache: TokensChainsCache,\n) {\n return mapValues(tokensChainsCache, (value) => {\n if (isObject(value) && 'data' in value) {\n return get(value, ['data']);\n }\n return value;\n });\n}\n\nexport const controllerName = 'TokenDetectionController';\n\nexport type TokenDetectionState = Record<never, never>;\n\nexport type TokenDetectionControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n TokenDetectionState\n>;\n\nexport type TokenDetectionControllerAddDetectedTokensViaWsAction = {\n type: `TokenDetectionController:addDetectedTokensViaWs`;\n handler: TokenDetectionController['addDetectedTokensViaWs'];\n};\n\nexport type TokenDetectionControllerActions =\n | TokenDetectionControllerGetStateAction\n | TokenDetectionControllerAddDetectedTokensViaWsAction;\n\nexport type AllowedActions =\n | AccountsControllerGetSelectedAccountAction\n | AccountsControllerGetAccountAction\n | NetworkControllerGetNetworkClientByIdAction\n | NetworkControllerGetNetworkConfigurationByNetworkClientId\n | NetworkControllerGetStateAction\n | GetTokenListState\n | KeyringControllerGetStateAction\n | PreferencesControllerGetStateAction\n | TokensControllerGetStateAction\n | TokensControllerAddDetectedTokensAction\n | TokensControllerAddTokensAction\n | NetworkControllerFindNetworkClientIdByChainIdAction\n | AuthenticationController.AuthenticationControllerGetBearerToken;\n\nexport type TokenDetectionControllerStateChangeEvent =\n ControllerStateChangeEvent<typeof controllerName, TokenDetectionState>;\n\nexport type TokenDetectionControllerEvents =\n TokenDetectionControllerStateChangeEvent;\n\nexport type AllowedEvents =\n | AccountsControllerSelectedEvmAccountChangeEvent\n | NetworkControllerNetworkDidChangeEvent\n | TokenListStateChange\n | KeyringControllerLockEvent\n | KeyringControllerUnlockEvent\n | PreferencesControllerStateChangeEvent\n | TransactionControllerTransactionConfirmedEvent;\n\nexport type TokenDetectionControllerMessenger = Messenger<\n typeof controllerName,\n TokenDetectionControllerActions | AllowedActions,\n TokenDetectionControllerEvents | AllowedEvents\n>;\n\n/** The input to start polling for the {@link TokenDetectionController} */\ntype TokenDetectionPollingInput = {\n chainIds: Hex[];\n address: string;\n};\n\n/**\n * Controller that passively polls on a set interval for Tokens auto detection\n *\n * intervalId - Polling interval used to fetch new token rates\n *\n * selectedAddress - Vault selected address\n *\n * networkClientId - The network client ID of the current selected network\n *\n * disabled - Boolean to track if network requests are blocked\n *\n * isUnlocked - Boolean to track if the keyring state is unlocked\n *\n * isDetectionEnabledFromPreferences - Boolean to track if detection is enabled from PreferencesController\n *\n */\nexport class TokenDetectionController extends StaticIntervalPollingController<TokenDetectionPollingInput>()<\n typeof controllerName,\n TokenDetectionState,\n TokenDetectionControllerMessenger\n> {\n #intervalId?: ReturnType<typeof setTimeout>;\n\n #selectedAccountId: string;\n\n #tokensChainsCache: TokensChainsCache = {};\n\n #disabled: boolean;\n\n #isUnlocked: boolean;\n\n #isDetectionEnabledFromPreferences: boolean;\n\n readonly #useTokenDetection: () => boolean;\n\n readonly #useExternalServices: () => boolean;\n\n readonly #getBalancesInSingleCall: AssetsContractController['getBalancesInSingleCall'];\n\n readonly #trackMetaMetricsEvent: (options: {\n event: string;\n category: string;\n properties: {\n tokens: string[];\n token_standard: string;\n asset_type: string;\n };\n }) => void;\n\n readonly #accountsAPI = {\n isAccountsAPIEnabled: true,\n supportedNetworksCache: null as number[] | null,\n platform: '' as 'extension' | 'mobile',\n\n async getSupportedNetworks() {\n /* istanbul ignore next */\n if (!this.isAccountsAPIEnabled) {\n throw new Error('Accounts API Feature Switch is disabled');\n }\n\n /* istanbul ignore next */\n if (this.supportedNetworksCache) {\n return this.supportedNetworksCache;\n }\n\n const result = await fetchSupportedNetworks().catch(() => null);\n this.supportedNetworksCache = result;\n return result;\n },\n\n async getMultiNetworksBalances(\n address: string,\n chainIds: Hex[],\n supportedNetworks: number[] | null,\n jwtToken?: string,\n ) {\n const chainIdNumbers = chainIds.map((chainId) => hexToNumber(chainId));\n\n if (\n !supportedNetworks ||\n !chainIdNumbers.every((id) => supportedNetworks.includes(id))\n ) {\n const supportedNetworksErrStr = (supportedNetworks ?? []).toString();\n throw new Error(\n `Unsupported Network: supported networks ${supportedNetworksErrStr}, requested networks: ${chainIdNumbers.toString()}`,\n );\n }\n\n const result = await fetchMultiChainBalances(\n address,\n {\n networks: chainIdNumbers,\n },\n this.platform,\n jwtToken,\n );\n\n // Return the full response including unprocessedNetworks\n return result;\n },\n };\n\n /**\n * Creates a TokenDetectionController instance.\n *\n * @param options - The controller options.\n * @param options.messenger - The controller messenger.\n * @param options.disabled - If set to true, all network requests are blocked.\n * @param options.interval - Polling interval used to fetch new token rates\n * @param options.getBalancesInSingleCall - Gets the balances of a list of tokens for the given address.\n * @param options.trackMetaMetricsEvent - Sets options for MetaMetrics event tracking.\n * @param options.useAccountsAPI - Feature Switch for using the accounts API when detecting tokens (default: true)\n * @param options.useTokenDetection - Feature Switch for using token detection (default: true)\n * @param options.useExternalServices - Feature Switch for using external services (default: false)\n * @param options.platform - Indicates whether the platform is extension or mobile\n */\n constructor({\n interval = DEFAULT_INTERVAL,\n disabled = true,\n getBalancesInSingleCall,\n trackMetaMetricsEvent,\n messenger,\n useAccountsAPI = true,\n useTokenDetection = () => true,\n useExternalServices = () => true,\n platform,\n }: {\n interval?: number;\n disabled?: boolean;\n getBalancesInSingleCall: AssetsContractController['getBalancesInSingleCall'];\n trackMetaMetricsEvent: (options: {\n event: string;\n category: string;\n properties: {\n tokens: string[];\n token_standard: string;\n asset_type: string;\n };\n }) => void;\n messenger: TokenDetectionControllerMessenger;\n useAccountsAPI?: boolean;\n useTokenDetection?: () => boolean;\n useExternalServices?: () => boolean;\n platform: 'extension' | 'mobile';\n }) {\n super({\n name: controllerName,\n messenger,\n state: {},\n metadata: {},\n });\n\n this.messenger.registerActionHandler(\n `${controllerName}:addDetectedTokensViaWs` as const,\n this.addDetectedTokensViaWs.bind(this),\n );\n\n this.#disabled = disabled;\n this.setIntervalLength(interval);\n\n this.#selectedAccountId = this.#getSelectedAccount().id;\n\n const { tokensChainsCache } = this.messenger.call(\n 'TokenListController:getState',\n );\n\n this.#tokensChainsCache = tokensChainsCache;\n\n const { useTokenDetection: defaultUseTokenDetection } = this.messenger.call(\n 'PreferencesController:getState',\n );\n this.#isDetectionEnabledFromPreferences = defaultUseTokenDetection;\n\n this.#getBalancesInSingleCall = getBalancesInSingleCall;\n\n this.#trackMetaMetricsEvent = trackMetaMetricsEvent;\n\n const { isUnlocked } = this.messenger.call('KeyringController:getState');\n this.#isUnlocked = isUnlocked;\n\n this.#accountsAPI.isAccountsAPIEnabled = useAccountsAPI;\n this.#useTokenDetection = useTokenDetection;\n this.#useExternalServices = useExternalServices;\n this.#accountsAPI.platform = platform;\n\n this.#registerEventListeners();\n }\n\n /**\n * Constructor helper for registering this controller's messenger subscriptions to controller events.\n */\n #registerEventListeners() {\n this.messenger.subscribe('KeyringController:unlock', async () => {\n this.#isUnlocked = true;\n await this.#restartTokenDetection();\n });\n\n this.messenger.subscribe('KeyringController:lock', () => {\n this.#isUnlocked = false;\n this.#stopPolling();\n });\n\n this.messenger.subscribe(\n 'TokenListController:stateChange',\n async ({ tokensChainsCache }) => {\n const isEqualValues = this.#compareTokensChainsCache(\n tokensChainsCache,\n this.#tokensChainsCache,\n );\n if (!isEqualValues) {\n await this.#restartTokenDetection();\n }\n },\n );\n\n this.messenger.subscribe(\n 'PreferencesController:stateChange',\n async ({ useTokenDetection }) => {\n const selectedAccount = this.#getSelectedAccount();\n const isDetectionChangedFromPreferences =\n this.#isDetectionEnabledFromPreferences !== useTokenDetection;\n\n this.#isDetectionEnabledFromPreferences = useTokenDetection;\n\n if (isDetectionChangedFromPreferences) {\n await this.#restartTokenDetection({\n selectedAddress: selectedAccount.address,\n });\n }\n },\n );\n\n this.messenger.subscribe(\n 'AccountsController:selectedEvmAccountChange',\n async (selectedAccount) => {\n const { networkConfigurationsByChainId } = this.messenger.call(\n 'NetworkController:getState',\n );\n\n const chainIds = Object.keys(networkConfigurationsByChainId) as Hex[];\n const isSelectedAccountIdChanged =\n this.#selectedAccountId !== selectedAccount.id;\n if (isSelectedAccountIdChanged) {\n this.#selectedAccountId = selectedAccount.id;\n await this.#restartTokenDetection({\n selectedAddress: selectedAccount.address,\n chainIds,\n });\n }\n },\n );\n\n this.messenger.subscribe(\n 'TransactionController:transactionConfirmed',\n async (transactionMeta) => {\n await this.detectTokens({\n chainIds: [transactionMeta.chainId],\n });\n },\n );\n }\n\n /**\n * Allows controller to make active and passive polling requests\n */\n enable(): void {\n this.#disabled = false;\n }\n\n /**\n * Blocks controller from making network calls\n */\n disable(): void {\n this.#disabled = true;\n }\n\n /**\n * Internal isActive state\n *\n * @returns Whether the controller is active (not disabled and keyring is unlocked)\n */\n get isActive(): boolean {\n return !this.#disabled && this.#isUnlocked;\n }\n\n /**\n * Start polling for detected tokens.\n */\n async start(): Promise<void> {\n this.enable();\n await this.#startPolling();\n }\n\n /**\n * Stop polling for detected tokens.\n */\n stop(): void {\n this.disable();\n this.#stopPolling();\n }\n\n #stopPolling(): void {\n if (this.#intervalId) {\n clearInterval(this.#intervalId);\n }\n }\n\n /**\n * Starts a new polling interval.\n */\n async #startPolling(): Promise<void> {\n if (!this.isActive) {\n return;\n }\n this.#stopPolling();\n await this.detectTokens();\n // TODO: Either fix this lint violation or explain why it's necessary to ignore.\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n this.#intervalId = setInterval(async () => {\n await this.detectTokens();\n }, this.getIntervalLength());\n }\n\n /**\n * Compares current and previous tokensChainsCache object focusing only on the data object.\n *\n * @param tokensChainsCache - current tokensChainsCache input object\n * @param previousTokensChainsCache - previous tokensChainsCache input object\n * @returns boolean indicating if the two objects are equal\n */\n\n #compareTokensChainsCache(\n tokensChainsCache: TokensChainsCache,\n previousTokensChainsCache: TokensChainsCache,\n ): boolean {\n const cleanPreviousTokensChainsCache = mapChainIdWithTokenListMap(\n previousTokensChainsCache,\n );\n const cleanTokensChainsCache =\n mapChainIdWithTokenListMap(tokensChainsCache);\n const isEqualValues = isEqual(\n cleanTokensChainsCache,\n cleanPreviousTokensChainsCache,\n );\n return isEqualValues;\n }\n\n #getCorrectNetworkClientIdByChainId(\n chainIds: Hex[] | undefined,\n ): { chainId: Hex; networkClientId: NetworkClientId }[] {\n const { networkConfigurationsByChainId, selectedNetworkClientId } =\n this.messenger.call('NetworkController:getState');\n\n if (!chainIds) {\n const networkConfiguration = this.messenger.call(\n 'NetworkController:getNetworkConfigurationByNetworkClientId',\n selectedNetworkClientId,\n );\n\n return [\n {\n chainId: networkConfiguration?.chainId ?? ChainId.mainnet,\n networkClientId: selectedNetworkClientId,\n },\n ];\n }\n\n return chainIds.map((chainId) => {\n const configuration = networkConfigurationsByChainId[chainId];\n return {\n chainId,\n networkClientId:\n configuration.rpcEndpoints[configuration.defaultRpcEndpointIndex]\n .networkClientId,\n };\n });\n }\n\n async _executePoll({\n chainIds,\n address,\n }: TokenDetectionPollingInput): Promise<void> {\n if (!this.isActive) {\n return;\n }\n await this.detectTokens({\n chainIds,\n selectedAddress: address,\n });\n }\n\n /**\n * Restart token detection polling period and call detectNewTokens\n * in case of address change or user session initialization.\n *\n * @param options - Options for restart token detection.\n * @param options.selectedAddress - the selectedAddress against which to detect for token balances\n * @param options.chainIds - The chain IDs of the network client to use.\n */\n async #restartTokenDetection({\n selectedAddress,\n chainIds,\n }: {\n selectedAddress?: string;\n chainIds?: Hex[];\n } = {}): Promise<void> {\n await this.detectTokens({\n chainIds,\n selectedAddress,\n });\n this.setIntervalLength(DEFAULT_INTERVAL);\n }\n\n #getChainsToDetect(\n clientNetworks: NetworkClient[],\n supportedNetworks: number[] | null | undefined,\n ) {\n const chainsToDetectUsingAccountAPI: Hex[] = [];\n const chainsToDetectUsingRpc: NetworkClient[] = [];\n\n clientNetworks.forEach(({ chainId, networkClientId }) => {\n if (supportedNetworks?.includes(hexToNumber(chainId))) {\n chainsToDetectUsingAccountAPI.push(chainId);\n } else {\n chainsToDetectUsingRpc.push({ chainId, networkClientId });\n }\n });\n\n return { chainsToDetectUsingRpc, chainsToDetectUsingAccountAPI };\n }\n\n async #attemptAccountAPIDetection(\n chainsToDetectUsingAccountAPI: Hex[],\n addressToDetect: string,\n supportedNetworks: number[] | null,\n jwtToken?: string,\n ) {\n const result = await safelyExecuteWithTimeout(\n async () => {\n return this.#addDetectedTokensViaAPI({\n chainIds: chainsToDetectUsingAccountAPI,\n selectedAddress: addressToDetect,\n supportedNetworks,\n jwtToken,\n });\n },\n false,\n ACCOUNTS_API_TIMEOUT_MS,\n );\n\n if (!result) {\n return { result: 'failed' } as const;\n }\n\n return result;\n }\n\n #addChainsToRpcDetection(\n chainsToDetectUsingRpc: NetworkClient[],\n chainsToDetectUsingAccountAPI: Hex[],\n clientNetworks: NetworkClient[],\n ): void {\n chainsToDetectUsingAccountAPI.forEach((chainId) => {\n const networkEntry = clientNetworks.find(\n (network) => network.chainId === chainId,\n );\n if (networkEntry) {\n chainsToDetectUsingRpc.push({\n chainId: networkEntry.chainId,\n networkClientId: networkEntry.networkClientId,\n });\n }\n });\n }\n\n #shouldDetectTokens(chainId: Hex): boolean {\n if (!isTokenDetectionSupportedForNetwork(chainId)) {\n return false;\n }\n if (\n !this.#isDetectionEnabledFromPreferences &&\n chainId !== ChainId.mainnet\n ) {\n return false;\n }\n\n const isMainnetDetectionInactive =\n !this.#isDetectionEnabledFromPreferences && chainId === ChainId.mainnet;\n if (isMainnetDetectionInactive) {\n this.#tokensChainsCache = this.#getConvertedStaticMainnetTokenList();\n } else {\n const { tokensChainsCache } = this.messenger.call(\n 'TokenListController:getState',\n );\n this.#tokensChainsCache = tokensChainsCache ?? {};\n }\n\n return true;\n }\n\n async #detectTokensUsingRpc(\n chainsToDetectUsingRpc: NetworkClient[],\n addressToDetect: string,\n ): Promise<void> {\n for (const { chainId, networkClientId } of chainsToDetectUsingRpc) {\n if (!this.#shouldDetectTokens(chainId)) {\n continue;\n }\n\n const tokenCandidateSlices = this.#getSlicesOfTokensToDetect({\n chainId,\n selectedAddress: addressToDetect,\n });\n const tokenDetectionPromises = tokenCandidateSlices.map((tokensSlice) =>\n this.#addDetectedTokens({\n tokensSlice,\n selectedAddress: addressToDetect,\n networkClientId,\n chainId,\n }),\n );\n\n await Promise.all(tokenDetectionPromises);\n }\n }\n\n /**\n * 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.\n * 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.\n *\n * @param options - Options for token detection.\n * @param options.chainIds - The chain IDs of the network client to use.\n * @param options.selectedAddress - the selectedAddress against which to detect for token balances.\n */\n async detectTokens({\n chainIds,\n selectedAddress,\n }: {\n chainIds?: Hex[];\n selectedAddress?: string;\n } = {}): Promise<void> {\n if (!this.isActive) {\n return;\n }\n\n if (!this.#useTokenDetection()) {\n return;\n }\n\n const addressToDetect = selectedAddress ?? this.#getSelectedAddress();\n const clientNetworks = this.#getCorrectNetworkClientIdByChainId(chainIds);\n\n const jwtToken = await safelyExecuteWithTimeout<string | undefined>(\n () => {\n return this.messenger.call('AuthenticationController:getBearerToken');\n },\n false,\n 5000,\n );\n\n let supportedNetworks;\n if (this.#accountsAPI.isAccountsAPIEnabled && this.#useExternalServices()) {\n supportedNetworks = await this.#accountsAPI.getSupportedNetworks();\n }\n const { chainsToDetectUsingRpc, chainsToDetectUsingAccountAPI } =\n this.#getChainsToDetect(clientNetworks, supportedNetworks);\n\n // Try detecting tokens via Account API first if conditions allow\n if (supportedNetworks && chainsToDetectUsingAccountAPI.length > 0) {\n const apiResult = await this.#attemptAccountAPIDetection(\n chainsToDetectUsingAccountAPI,\n addressToDetect,\n supportedNetworks,\n jwtToken,\n );\n\n // If the account API call failed or returned undefined, have those chains fall back to RPC detection\n if (!apiResult || apiResult.result === 'failed') {\n this.#addChainsToRpcDetection(\n chainsToDetectUsingRpc,\n chainsToDetectUsingAccountAPI,\n clientNetworks,\n );\n } else if (\n apiResult?.result === 'success' &&\n apiResult.unprocessedNetworks &&\n apiResult.unprocessedNetworks.length > 0\n ) {\n // Handle unprocessed networks by adding them to RPC detection\n const unprocessedChainIds = apiResult.unprocessedNetworks.map(\n (chainId: number) => toHex(chainId),\n );\n this.#addChainsToRpcDetection(\n chainsToDetectUsingRpc,\n unprocessedChainIds,\n clientNetworks,\n );\n }\n }\n\n // Proceed with RPC detection if there are chains remaining in chainsToDetectUsingRpc\n if (chainsToDetectUsingRpc.length > 0) {\n await this.#detectTokensUsingRpc(chainsToDetectUsingRpc, addressToDetect);\n }\n }\n\n #getSlicesOfTokensToDetect({\n chainId,\n selectedAddress,\n }: {\n chainId: Hex;\n selectedAddress: string;\n }): string[][] {\n const { allTokens, allDetectedTokens, allIgnoredTokens } =\n this.messenger.call('TokensController:getState');\n const [tokensAddresses, detectedTokensAddresses, ignoredTokensAddresses] = [\n allTokens,\n allDetectedTokens,\n allIgnoredTokens,\n ].map((tokens) =>\n (tokens[chainId]?.[selectedAddress] ?? []).map((value) =>\n typeof value === 'string' ? value : value.address,\n ),\n );\n\n const tokensToDetect: string[] = [];\n for (const tokenAddress of Object.keys(\n this.#tokensChainsCache?.[chainId]?.data || {},\n )) {\n if (\n [\n tokensAddresses,\n detectedTokensAddresses,\n ignoredTokensAddresses,\n ].every(\n (addresses) =>\n !addresses.find((address) =>\n isEqualCaseInsensitive(address, tokenAddress),\n ),\n )\n ) {\n tokensToDetect.push(tokenAddress);\n }\n }\n\n const slicesOfTokensToDetect = [];\n for (let i = 0, size = 1000; i < tokensToDetect.length; i += size) {\n slicesOfTokensToDetect.push(tokensToDetect.slice(i, i + size));\n }\n\n return slicesOfTokensToDetect;\n }\n\n #getConvertedStaticMainnetTokenList(): TokensChainsCache {\n const data: TokenListMap = Object.entries(STATIC_MAINNET_TOKEN_LIST).reduce(\n (acc, [key, value]) => ({\n ...acc,\n [key]: {\n name: value.name,\n symbol: value.symbol,\n decimals: value.decimals,\n address: value.address,\n aggregators: [],\n iconUrl: value?.iconUrl,\n },\n }),\n {},\n );\n return {\n '0x1': {\n data,\n timestamp: 0,\n },\n };\n }\n\n /**\n * This adds detected tokens from the Accounts API, avoiding the multi-call RPC calls for balances\n *\n * @param options - method arguments\n * @param options.selectedAddress - address to check against\n * @param options.chainIds - array of chainIds to check tokens for\n * @param options.supportedNetworks - array of chainIds to check tokens for\n * @param options.jwtToken - JWT token for authentication\n * @returns a success or failed object\n */\n async #addDetectedTokensViaAPI({\n selectedAddress,\n chainIds,\n supportedNetworks,\n jwtToken,\n }: {\n selectedAddress: string;\n chainIds: Hex[];\n supportedNetworks: number[] | null;\n jwtToken?: string;\n }) {\n return await safelyExecute(async () => {\n // Fetch balances for multiple chain IDs at once\n const apiResponse = await this.#accountsAPI\n .getMultiNetworksBalances(\n selectedAddress,\n chainIds,\n supportedNetworks,\n jwtToken,\n )\n .catch(() => null);\n\n if (apiResponse === null) {\n return { result: 'failed' } as const;\n }\n\n const tokenBalancesByChain = apiResponse.balances;\n\n // Process each chain ID individually\n for (const chainId of chainIds) {\n const isTokenDetectionInactiveInMainnet =\n !this.#isDetectionEnabledFromPreferences &&\n chainId === ChainId.mainnet;\n const { tokensChainsCache } = this.messenger.call(\n 'TokenListController:getState',\n );\n this.#tokensChainsCache = isTokenDetectionInactiveInMainnet\n ? this.#getConvertedStaticMainnetTokenList()\n : (tokensChainsCache ?? {});\n\n // Generate token candidates based on chainId and selectedAddress\n const tokenCandidateSlices = this.#getSlicesOfTokensToDetect({\n chainId,\n selectedAddress,\n });\n\n // Filter balances for the current chainId\n const tokenBalances = tokenBalancesByChain.filter(\n (balance) => balance.chainId === hexToNumber(chainId),\n );\n\n if (!tokenBalances || tokenBalances.length === 0) {\n continue;\n }\n\n // Use helper function to filter tokens with balance for this chainId\n const { tokensWithBalance, eventTokensDetails } =\n this.#filterAndBuildTokensWithBalance(\n tokenCandidateSlices,\n tokenBalances,\n chainId,\n );\n\n if (tokensWithBalance.length) {\n this.#trackMetaMetricsEvent({\n event: 'Token Detected',\n category: 'Wallet',\n properties: {\n tokens: eventTokensDetails,\n token_standard: ERC20,\n asset_type: ASSET_TYPES.TOKEN,\n },\n });\n\n const networkClientId = this.messenger.call(\n 'NetworkController:findNetworkClientIdByChainId',\n chainId,\n );\n\n await this.messenger.call(\n 'TokensController:addTokens',\n tokensWithBalance,\n networkClientId,\n );\n }\n }\n\n return {\n result: 'success',\n unprocessedNetworks: apiResponse.unprocessedNetworks,\n } as const;\n });\n }\n\n /**\n * Helper function to filter and build token data for detected tokens\n *\n * @param options.tokenCandidateSlices - these are tokens we know a user does not have (by checking the tokens controller).\n * We will use these these token candidates to determine if a token found from the API is valid to be added on the users wallet.\n * It will also prevent us to adding tokens a user already has\n * @param tokenBalances - Tokens balances fetched from API\n * @param chainId - The chain ID being processed\n * @returns an object containing tokensWithBalance and eventTokensDetails arrays\n */\n\n #filterAndBuildTokensWithBalance(\n tokenCandidateSlices: string[][],\n tokenBalances:\n | {\n object: string;\n type?: string;\n timestamp?: string;\n address: string;\n symbol: string;\n name: string;\n decimals: number;\n chainId: number;\n balance: string;\n }[]\n | null,\n chainId: Hex,\n ) {\n const tokensWithBalance: Token[] = [];\n const eventTokensDetails: string[] = [];\n\n const tokenCandidateSet = new Set<string>(tokenCandidateSlices.flat());\n\n tokenBalances?.forEach((token) => {\n const tokenAddress = token.address;\n\n // Make sure the token to add is in our candidate list\n if (!tokenCandidateSet.has(tokenAddress)) {\n return;\n }\n\n // Retrieve token data from cache to safely add it\n const tokenData = this.#tokensChainsCache[chainId]?.data[tokenAddress];\n\n // We need specific data from tokensChainsCache to correctly create a token\n // So even if we have a token that was detected correctly by the API, if its missing data we cannot safely add it.\n if (!tokenData) {\n return;\n }\n\n const { decimals, symbol, aggregators, iconUrl, name } = tokenData;\n eventTokensDetails.push(`${symbol} - ${tokenAddress}`);\n tokensWithBalance.push({\n address: tokenAddress,\n decimals,\n symbol,\n aggregators,\n image: iconUrl,\n isERC721: false,\n name,\n });\n });\n\n return { tokensWithBalance, eventTokensDetails };\n }\n\n async #addDetectedTokens({\n tokensSlice,\n selectedAddress,\n networkClientId,\n chainId,\n }: {\n tokensSlice: string[];\n selectedAddress: string;\n networkClientId: NetworkClientId;\n chainId: Hex;\n }): Promise<void> {\n await safelyExecute(async () => {\n const balances = await this.#getBalancesInSingleCall(\n selectedAddress,\n tokensSlice,\n networkClientId,\n );\n\n const tokensWithBalance: Token[] = [];\n const eventTokensDetails: string[] = [];\n for (const nonZeroTokenAddress of Object.keys(balances)) {\n const { decimals, symbol, aggregators, iconUrl, name } =\n this.#tokensChainsCache[chainId].data[nonZeroTokenAddress];\n eventTokensDetails.push(`${symbol} - ${nonZeroTokenAddress}`);\n tokensWithBalance.push({\n address: nonZeroTokenAddress,\n decimals,\n symbol,\n aggregators,\n image: iconUrl,\n isERC721: false,\n name,\n });\n }\n\n if (tokensWithBalance.length) {\n this.#trackMetaMetricsEvent({\n event: 'Token Detected',\n category: 'Wallet',\n properties: {\n tokens: eventTokensDetails,\n token_standard: ERC20,\n asset_type: ASSET_TYPES.TOKEN,\n },\n });\n\n await this.messenger.call(\n 'TokensController:addTokens',\n tokensWithBalance,\n networkClientId,\n );\n }\n });\n }\n\n /**\n * Add tokens detected from websocket balance updates\n * This method assumes:\n * - Tokens are already in the tokensChainsCache with full metadata\n * - Balance fetching is skipped since balances are provided by the websocket\n * - Ignored tokens have been filtered out by the caller\n *\n * @param options - The options object\n * @param options.tokensSlice - Array of token addresses detected from websocket (already filtered to exclude ignored tokens)\n * @param options.chainId - Hex chain ID\n * @returns Promise that resolves when tokens are added\n */\n async addDetectedTokensViaWs({\n tokensSlice,\n chainId,\n }: {\n tokensSlice: string[];\n chainId: Hex;\n }): Promise<void> {\n const tokensWithBalance: Token[] = [];\n const eventTokensDetails: string[] = [];\n\n for (const tokenAddress of tokensSlice) {\n // Normalize addresses explicitly (don't assume input format)\n const lowercaseTokenAddress = tokenAddress.toLowerCase();\n const checksummedTokenAddress = toChecksumHexAddress(tokenAddress);\n\n // Check map of validated tokens (cache keys are lowercase)\n const tokenData =\n this.#tokensChainsCache[chainId]?.data?.[lowercaseTokenAddress];\n\n if (!tokenData) {\n console.warn(\n `Token metadata not found in cache for ${tokenAddress} on chain ${chainId}`,\n );\n continue;\n }\n\n const { decimals, symbol, aggregators, iconUrl, name } = tokenData;\n\n // Push to lists with checksummed address (for allTokens storage)\n eventTokensDetails.push(`${symbol} - ${checksummedTokenAddress}`);\n tokensWithBalance.push({\n address: checksummedTokenAddress,\n decimals,\n symbol,\n aggregators,\n image: iconUrl,\n isERC721: false,\n name,\n });\n }\n\n // Perform addition\n if (tokensWithBalance.length) {\n this.#trackMetaMetricsEvent({\n event: 'Token Detected',\n category: 'Wallet',\n properties: {\n tokens: eventTokensDetails,\n token_standard: ERC20,\n asset_type: ASSET_TYPES.TOKEN,\n },\n });\n\n const networkClientId = this.messenger.call(\n 'NetworkController:findNetworkClientIdByChainId',\n chainId,\n );\n\n await this.messenger.call(\n 'TokensController:addTokens',\n tokensWithBalance,\n networkClientId,\n );\n }\n }\n\n #getSelectedAccount() {\n return this.messenger.call('AccountsController:getSelectedAccount');\n }\n\n #getSelectedAddress() {\n // If the address is not defined (or empty), we fallback to the currently selected account's address\n const account = this.messenger.call(\n 'AccountsController:getAccount',\n this.#selectedAccountId,\n );\n return account?.address || '';\n }\n}\n\nexport default TokenDetectionController;\n"]}