@metamask-previews/assets-controllers 11.0.1-preview.d32a7cc

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 (80) hide show
  1. package/CHANGELOG.md +235 -0
  2. package/LICENSE +20 -0
  3. package/README.md +30 -0
  4. package/dist/AccountTrackerController.d.ts +106 -0
  5. package/dist/AccountTrackerController.d.ts.map +1 -0
  6. package/dist/AccountTrackerController.js +163 -0
  7. package/dist/AccountTrackerController.js.map +1 -0
  8. package/dist/AssetsContractController.d.ts +188 -0
  9. package/dist/AssetsContractController.d.ts.map +1 -0
  10. package/dist/AssetsContractController.js +330 -0
  11. package/dist/AssetsContractController.js.map +1 -0
  12. package/dist/CurrencyRateController.d.ts +101 -0
  13. package/dist/CurrencyRateController.d.ts.map +1 -0
  14. package/dist/CurrencyRateController.js +219 -0
  15. package/dist/CurrencyRateController.js.map +1 -0
  16. package/dist/NftController.d.ts +464 -0
  17. package/dist/NftController.d.ts.map +1 -0
  18. package/dist/NftController.js +924 -0
  19. package/dist/NftController.js.map +1 -0
  20. package/dist/NftDetectionController.d.ts +182 -0
  21. package/dist/NftDetectionController.d.ts.map +1 -0
  22. package/dist/NftDetectionController.js +188 -0
  23. package/dist/NftDetectionController.js.map +1 -0
  24. package/dist/Standards/ERC20Standard.d.ts +50 -0
  25. package/dist/Standards/ERC20Standard.d.ts.map +1 -0
  26. package/dist/Standards/ERC20Standard.js +144 -0
  27. package/dist/Standards/ERC20Standard.js.map +1 -0
  28. package/dist/Standards/NftStandards/ERC1155/ERC1155Standard.d.ts +79 -0
  29. package/dist/Standards/NftStandards/ERC1155/ERC1155Standard.d.ts.map +1 -0
  30. package/dist/Standards/NftStandards/ERC1155/ERC1155Standard.js +148 -0
  31. package/dist/Standards/NftStandards/ERC1155/ERC1155Standard.js.map +1 -0
  32. package/dist/Standards/NftStandards/ERC721/ERC721Standard.d.ts +89 -0
  33. package/dist/Standards/NftStandards/ERC721/ERC721Standard.d.ts.map +1 -0
  34. package/dist/Standards/NftStandards/ERC721/ERC721Standard.js +182 -0
  35. package/dist/Standards/NftStandards/ERC721/ERC721Standard.js.map +1 -0
  36. package/dist/Standards/standards-types.d.ts +15 -0
  37. package/dist/Standards/standards-types.d.ts.map +1 -0
  38. package/dist/Standards/standards-types.js +3 -0
  39. package/dist/Standards/standards-types.js.map +1 -0
  40. package/dist/TokenBalancesController.d.ts +71 -0
  41. package/dist/TokenBalancesController.d.ts.map +1 -0
  42. package/dist/TokenBalancesController.js +94 -0
  43. package/dist/TokenBalancesController.js.map +1 -0
  44. package/dist/TokenDetectionController.d.ts +87 -0
  45. package/dist/TokenDetectionController.d.ts.map +1 -0
  46. package/dist/TokenDetectionController.js +189 -0
  47. package/dist/TokenDetectionController.js.map +1 -0
  48. package/dist/TokenListController.d.ts +117 -0
  49. package/dist/TokenListController.d.ts.map +1 -0
  50. package/dist/TokenListController.js +248 -0
  51. package/dist/TokenListController.js.map +1 -0
  52. package/dist/TokenRatesController.d.ts +172 -0
  53. package/dist/TokenRatesController.d.ts.map +1 -0
  54. package/dist/TokenRatesController.js +327 -0
  55. package/dist/TokenRatesController.js.map +1 -0
  56. package/dist/TokensController.d.ts +250 -0
  57. package/dist/TokensController.d.ts.map +1 -0
  58. package/dist/TokensController.js +550 -0
  59. package/dist/TokensController.js.map +1 -0
  60. package/dist/assetsUtil.d.ts +109 -0
  61. package/dist/assetsUtil.d.ts.map +1 -0
  62. package/dist/assetsUtil.js +227 -0
  63. package/dist/assetsUtil.js.map +1 -0
  64. package/dist/constants.d.ts +6 -0
  65. package/dist/constants.d.ts.map +1 -0
  66. package/dist/constants.js +10 -0
  67. package/dist/constants.js.map +1 -0
  68. package/dist/crypto-compare.d.ts +13 -0
  69. package/dist/crypto-compare.d.ts.map +1 -0
  70. package/dist/crypto-compare.js +67 -0
  71. package/dist/crypto-compare.js.map +1 -0
  72. package/dist/index.d.ts +12 -0
  73. package/dist/index.d.ts.map +1 -0
  74. package/dist/index.js +32 -0
  75. package/dist/index.js.map +1 -0
  76. package/dist/token-service.d.ts +31 -0
  77. package/dist/token-service.d.ts.map +1 -0
  78. package/dist/token-service.js +134 -0
  79. package/dist/token-service.js.map +1 -0
  80. package/package.json +82 -0
@@ -0,0 +1,189 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.TokenDetectionController = void 0;
13
+ const base_controller_1 = require("@metamask/base-controller");
14
+ const controller_utils_1 = require("@metamask/controller-utils");
15
+ const assetsUtil_1 = require("./assetsUtil");
16
+ const DEFAULT_INTERVAL = 180000;
17
+ /**
18
+ * Controller that passively polls on a set interval for Tokens auto detection
19
+ */
20
+ class TokenDetectionController extends base_controller_1.BaseController {
21
+ /**
22
+ * Creates a TokenDetectionController instance.
23
+ *
24
+ * @param options - The controller options.
25
+ * @param options.onPreferencesStateChange - Allows subscribing to preferences controller state changes.
26
+ * @param options.onNetworkStateChange - Allows subscribing to network controller state changes.
27
+ * @param options.onTokenListStateChange - Allows subscribing to token list controller state changes.
28
+ * @param options.getBalancesInSingleCall - Gets the balances of a list of tokens for the given address.
29
+ * @param options.addDetectedTokens - Add a list of detected tokens.
30
+ * @param options.getTokenListState - Gets the current state of the TokenList controller.
31
+ * @param options.getTokensState - Gets the current state of the Tokens controller.
32
+ * @param options.getNetworkState - Gets the state of the network controller.
33
+ * @param options.getPreferencesState - Gets the state of the preferences controller.
34
+ * @param config - Initial options used to configure this controller.
35
+ * @param state - Initial state to set on this controller.
36
+ */
37
+ constructor({ onPreferencesStateChange, onNetworkStateChange, onTokenListStateChange, getBalancesInSingleCall, addDetectedTokens, getTokenListState, getTokensState, getNetworkState, getPreferencesState, }, config, state) {
38
+ const { providerConfig: { chainId: defaultChainId }, } = getNetworkState();
39
+ const { useTokenDetection: defaultUseTokenDetection } = getPreferencesState();
40
+ super(config, state);
41
+ /**
42
+ * Name of this controller used during composition
43
+ */
44
+ this.name = 'TokenDetectionController';
45
+ this.defaultConfig = Object.assign({ interval: DEFAULT_INTERVAL, selectedAddress: '', disabled: true, chainId: defaultChainId, isDetectionEnabledFromPreferences: defaultUseTokenDetection, isDetectionEnabledForNetwork: (0, assetsUtil_1.isTokenDetectionSupportedForNetwork)(defaultChainId) }, config);
46
+ this.initialize();
47
+ this.getTokensState = getTokensState;
48
+ this.getTokenListState = getTokenListState;
49
+ this.addDetectedTokens = addDetectedTokens;
50
+ this.getBalancesInSingleCall = getBalancesInSingleCall;
51
+ onTokenListStateChange(({ tokenList }) => {
52
+ const hasTokens = Object.keys(tokenList).length;
53
+ if (hasTokens) {
54
+ this.detectTokens();
55
+ }
56
+ });
57
+ onPreferencesStateChange(({ selectedAddress, useTokenDetection }) => {
58
+ const { selectedAddress: currentSelectedAddress, isDetectionEnabledFromPreferences, } = this.config;
59
+ const isSelectedAddressChanged = selectedAddress !== currentSelectedAddress;
60
+ const isDetectionChangedFromPreferences = isDetectionEnabledFromPreferences !== useTokenDetection;
61
+ this.configure({
62
+ isDetectionEnabledFromPreferences: useTokenDetection,
63
+ selectedAddress,
64
+ });
65
+ if (useTokenDetection &&
66
+ (isSelectedAddressChanged || isDetectionChangedFromPreferences)) {
67
+ this.detectTokens();
68
+ }
69
+ });
70
+ onNetworkStateChange(({ providerConfig: { chainId } }) => {
71
+ const { chainId: currentChainId } = this.config;
72
+ const isDetectionEnabledForNetwork = (0, assetsUtil_1.isTokenDetectionSupportedForNetwork)(chainId);
73
+ const isChainIdChanged = currentChainId !== chainId;
74
+ this.configure({
75
+ chainId,
76
+ isDetectionEnabledForNetwork,
77
+ });
78
+ if (isDetectionEnabledForNetwork && isChainIdChanged) {
79
+ this.detectTokens();
80
+ }
81
+ });
82
+ }
83
+ /**
84
+ * Start polling for detected tokens.
85
+ */
86
+ start() {
87
+ return __awaiter(this, void 0, void 0, function* () {
88
+ this.configure({ disabled: false });
89
+ yield this.startPolling();
90
+ });
91
+ }
92
+ /**
93
+ * Stop polling for detected tokens.
94
+ */
95
+ stop() {
96
+ this.configure({ disabled: true });
97
+ this.stopPolling();
98
+ }
99
+ stopPolling() {
100
+ if (this.intervalId) {
101
+ clearInterval(this.intervalId);
102
+ }
103
+ }
104
+ /**
105
+ * Starts a new polling interval.
106
+ *
107
+ * @param interval - An interval on which to poll.
108
+ */
109
+ startPolling(interval) {
110
+ return __awaiter(this, void 0, void 0, function* () {
111
+ interval && this.configure({ interval }, false, false);
112
+ this.stopPolling();
113
+ yield this.detectTokens();
114
+ this.intervalId = setInterval(() => __awaiter(this, void 0, void 0, function* () {
115
+ yield this.detectTokens();
116
+ }), this.config.interval);
117
+ });
118
+ }
119
+ /**
120
+ * Triggers asset ERC20 token auto detection for each contract address in contract metadata on mainnet.
121
+ */
122
+ detectTokens() {
123
+ return __awaiter(this, void 0, void 0, function* () {
124
+ const { disabled, isDetectionEnabledForNetwork, isDetectionEnabledFromPreferences, } = this.config;
125
+ if (disabled ||
126
+ !isDetectionEnabledForNetwork ||
127
+ !isDetectionEnabledFromPreferences) {
128
+ return;
129
+ }
130
+ const { tokens } = this.getTokensState();
131
+ const { selectedAddress, chainId } = this.config;
132
+ const tokensAddresses = tokens.map(
133
+ /* istanbul ignore next*/ (token) => token.address.toLowerCase());
134
+ const { tokenList } = this.getTokenListState();
135
+ const tokensToDetect = [];
136
+ for (const address in tokenList) {
137
+ if (!tokensAddresses.includes(address)) {
138
+ tokensToDetect.push(address);
139
+ }
140
+ }
141
+ const sliceOfTokensToDetect = [];
142
+ sliceOfTokensToDetect[0] = tokensToDetect.slice(0, 1000);
143
+ sliceOfTokensToDetect[1] = tokensToDetect.slice(1000, tokensToDetect.length - 1);
144
+ /* istanbul ignore else */
145
+ if (!selectedAddress) {
146
+ return;
147
+ }
148
+ for (const tokensSlice of sliceOfTokensToDetect) {
149
+ if (tokensSlice.length === 0) {
150
+ break;
151
+ }
152
+ yield (0, controller_utils_1.safelyExecute)(() => __awaiter(this, void 0, void 0, function* () {
153
+ const balances = yield this.getBalancesInSingleCall(selectedAddress, tokensSlice);
154
+ const tokensToAdd = [];
155
+ for (const tokenAddress in balances) {
156
+ let ignored;
157
+ /* istanbul ignore else */
158
+ const { ignoredTokens } = this.getTokensState();
159
+ if (ignoredTokens.length) {
160
+ ignored = ignoredTokens.find((ignoredTokenAddress) => ignoredTokenAddress === (0, controller_utils_1.toChecksumHexAddress)(tokenAddress));
161
+ }
162
+ const caseInsensitiveTokenKey = Object.keys(tokenList).find((i) => i.toLowerCase() === tokenAddress.toLowerCase()) || '';
163
+ if (ignored === undefined) {
164
+ const { decimals, symbol, aggregators, iconUrl, name } = tokenList[caseInsensitiveTokenKey];
165
+ tokensToAdd.push({
166
+ address: tokenAddress,
167
+ decimals,
168
+ symbol,
169
+ aggregators,
170
+ image: iconUrl,
171
+ isERC721: false,
172
+ name,
173
+ });
174
+ }
175
+ }
176
+ if (tokensToAdd.length) {
177
+ yield this.addDetectedTokens(tokensToAdd, {
178
+ selectedAddress,
179
+ chainId,
180
+ });
181
+ }
182
+ }));
183
+ }
184
+ });
185
+ }
186
+ }
187
+ exports.TokenDetectionController = TokenDetectionController;
188
+ exports.default = TokenDetectionController;
189
+ //# sourceMappingURL=TokenDetectionController.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TokenDetectionController.js","sourceRoot":"","sources":["../src/TokenDetectionController.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BaseConfig, BaseState } from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport {\n safelyExecute,\n toChecksumHexAddress,\n} from '@metamask/controller-utils';\nimport type { NetworkState } from '@metamask/network-controller';\nimport type { PreferencesState } from '@metamask/preferences-controller';\nimport type { Hex } from '@metamask/utils';\n\nimport type { AssetsContractController } from './AssetsContractController';\nimport { isTokenDetectionSupportedForNetwork } from './assetsUtil';\nimport type { TokenListState } from './TokenListController';\nimport type { Token } from './TokenRatesController';\nimport type { TokensController, TokensState } from './TokensController';\n\nconst DEFAULT_INTERVAL = 180000;\n\n/**\n * @type TokenDetectionConfig\n *\n * TokenDetection configuration\n * @property interval - Polling interval used to fetch new token rates\n * @property selectedAddress - Vault selected address\n * @property chainId - The chain ID of the current network\n * @property isDetectionEnabledFromPreferences - Boolean to track if detection is enabled from PreferencesController\n * @property isDetectionEnabledForNetwork - Boolean to track if detected is enabled for current network\n */\nexport interface TokenDetectionConfig extends BaseConfig {\n interval: number;\n selectedAddress: string;\n chainId: Hex;\n isDetectionEnabledFromPreferences: boolean;\n isDetectionEnabledForNetwork: boolean;\n}\n\n/**\n * Controller that passively polls on a set interval for Tokens auto detection\n */\nexport class TokenDetectionController extends BaseController<\n TokenDetectionConfig,\n BaseState\n> {\n private intervalId?: ReturnType<typeof setTimeout>;\n\n /**\n * Name of this controller used during composition\n */\n override name = 'TokenDetectionController';\n\n private readonly getBalancesInSingleCall: AssetsContractController['getBalancesInSingleCall'];\n\n private readonly addDetectedTokens: TokensController['addDetectedTokens'];\n\n private readonly getTokensState: () => TokensState;\n\n private readonly getTokenListState: () => TokenListState;\n\n /**\n * Creates a TokenDetectionController instance.\n *\n * @param options - The controller options.\n * @param options.onPreferencesStateChange - Allows subscribing to preferences controller state changes.\n * @param options.onNetworkStateChange - Allows subscribing to network controller state changes.\n * @param options.onTokenListStateChange - Allows subscribing to token list controller state changes.\n * @param options.getBalancesInSingleCall - Gets the balances of a list of tokens for the given address.\n * @param options.addDetectedTokens - Add a list of detected tokens.\n * @param options.getTokenListState - Gets the current state of the TokenList controller.\n * @param options.getTokensState - Gets the current state of the Tokens controller.\n * @param options.getNetworkState - Gets the state of the network controller.\n * @param options.getPreferencesState - Gets the state of the preferences controller.\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(\n {\n onPreferencesStateChange,\n onNetworkStateChange,\n onTokenListStateChange,\n getBalancesInSingleCall,\n addDetectedTokens,\n getTokenListState,\n getTokensState,\n getNetworkState,\n getPreferencesState,\n }: {\n onPreferencesStateChange: (\n listener: (preferencesState: PreferencesState) => void,\n ) => void;\n onNetworkStateChange: (\n listener: (networkState: NetworkState) => void,\n ) => void;\n onTokenListStateChange: (\n listener: (tokenListState: TokenListState) => void,\n ) => void;\n getBalancesInSingleCall: AssetsContractController['getBalancesInSingleCall'];\n addDetectedTokens: TokensController['addDetectedTokens'];\n getTokenListState: () => TokenListState;\n getTokensState: () => TokensState;\n getNetworkState: () => NetworkState;\n getPreferencesState: () => PreferencesState;\n },\n config?: Partial<TokenDetectionConfig>,\n state?: Partial<BaseState>,\n ) {\n const {\n providerConfig: { chainId: defaultChainId },\n } = getNetworkState();\n const { useTokenDetection: defaultUseTokenDetection } =\n getPreferencesState();\n\n super(config, state);\n this.defaultConfig = {\n interval: DEFAULT_INTERVAL,\n selectedAddress: '',\n disabled: true,\n chainId: defaultChainId,\n isDetectionEnabledFromPreferences: defaultUseTokenDetection,\n isDetectionEnabledForNetwork:\n isTokenDetectionSupportedForNetwork(defaultChainId),\n ...config,\n };\n\n this.initialize();\n this.getTokensState = getTokensState;\n this.getTokenListState = getTokenListState;\n this.addDetectedTokens = addDetectedTokens;\n this.getBalancesInSingleCall = getBalancesInSingleCall;\n\n onTokenListStateChange(({ tokenList }) => {\n const hasTokens = Object.keys(tokenList).length;\n\n if (hasTokens) {\n this.detectTokens();\n }\n });\n\n onPreferencesStateChange(({ selectedAddress, useTokenDetection }) => {\n const {\n selectedAddress: currentSelectedAddress,\n isDetectionEnabledFromPreferences,\n } = this.config;\n const isSelectedAddressChanged =\n selectedAddress !== currentSelectedAddress;\n const isDetectionChangedFromPreferences =\n isDetectionEnabledFromPreferences !== useTokenDetection;\n\n this.configure({\n isDetectionEnabledFromPreferences: useTokenDetection,\n selectedAddress,\n });\n\n if (\n useTokenDetection &&\n (isSelectedAddressChanged || isDetectionChangedFromPreferences)\n ) {\n this.detectTokens();\n }\n });\n\n onNetworkStateChange(({ providerConfig: { chainId } }) => {\n const { chainId: currentChainId } = this.config;\n const isDetectionEnabledForNetwork =\n isTokenDetectionSupportedForNetwork(chainId);\n const isChainIdChanged = currentChainId !== chainId;\n\n this.configure({\n chainId,\n isDetectionEnabledForNetwork,\n });\n\n if (isDetectionEnabledForNetwork && isChainIdChanged) {\n this.detectTokens();\n }\n });\n }\n\n /**\n * Start polling for detected tokens.\n */\n async start() {\n this.configure({ disabled: false });\n await this.startPolling();\n }\n\n /**\n * Stop polling for detected tokens.\n */\n stop() {\n this.configure({ disabled: true });\n this.stopPolling();\n }\n\n private stopPolling() {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n }\n }\n\n /**\n * Starts a new polling interval.\n *\n * @param interval - An interval on which to poll.\n */\n private async startPolling(interval?: number): Promise<void> {\n interval && this.configure({ interval }, false, false);\n this.stopPolling();\n await this.detectTokens();\n this.intervalId = setInterval(async () => {\n await this.detectTokens();\n }, this.config.interval);\n }\n\n /**\n * Triggers asset ERC20 token auto detection for each contract address in contract metadata on mainnet.\n */\n async detectTokens() {\n const {\n disabled,\n isDetectionEnabledForNetwork,\n isDetectionEnabledFromPreferences,\n } = this.config;\n if (\n disabled ||\n !isDetectionEnabledForNetwork ||\n !isDetectionEnabledFromPreferences\n ) {\n return;\n }\n const { tokens } = this.getTokensState();\n const { selectedAddress, chainId } = this.config;\n\n const tokensAddresses = tokens.map(\n /* istanbul ignore next*/ (token) => token.address.toLowerCase(),\n );\n const { tokenList } = this.getTokenListState();\n const tokensToDetect: string[] = [];\n for (const address in tokenList) {\n if (!tokensAddresses.includes(address)) {\n tokensToDetect.push(address);\n }\n }\n const sliceOfTokensToDetect = [];\n sliceOfTokensToDetect[0] = tokensToDetect.slice(0, 1000);\n sliceOfTokensToDetect[1] = tokensToDetect.slice(\n 1000,\n tokensToDetect.length - 1,\n );\n\n /* istanbul ignore else */\n if (!selectedAddress) {\n return;\n }\n\n for (const tokensSlice of sliceOfTokensToDetect) {\n if (tokensSlice.length === 0) {\n break;\n }\n\n await safelyExecute(async () => {\n const balances = await this.getBalancesInSingleCall(\n selectedAddress,\n tokensSlice,\n );\n const tokensToAdd: Token[] = [];\n for (const tokenAddress in balances) {\n let ignored;\n /* istanbul ignore else */\n const { ignoredTokens } = this.getTokensState();\n if (ignoredTokens.length) {\n ignored = ignoredTokens.find(\n (ignoredTokenAddress) =>\n ignoredTokenAddress === toChecksumHexAddress(tokenAddress),\n );\n }\n const caseInsensitiveTokenKey =\n Object.keys(tokenList).find(\n (i) => i.toLowerCase() === tokenAddress.toLowerCase(),\n ) || '';\n\n if (ignored === undefined) {\n const { decimals, symbol, aggregators, iconUrl, name } =\n tokenList[caseInsensitiveTokenKey];\n tokensToAdd.push({\n address: tokenAddress,\n decimals,\n symbol,\n aggregators,\n image: iconUrl,\n isERC721: false,\n name,\n });\n }\n }\n\n if (tokensToAdd.length) {\n await this.addDetectedTokens(tokensToAdd, {\n selectedAddress,\n chainId,\n });\n }\n });\n }\n }\n}\n\nexport default TokenDetectionController;\n"]}
@@ -0,0 +1,117 @@
1
+ import type { RestrictedControllerMessenger } from '@metamask/base-controller';
2
+ import { BaseControllerV2 } from '@metamask/base-controller';
3
+ import type { NetworkControllerStateChangeEvent, NetworkState } from '@metamask/network-controller';
4
+ import type { Hex } from '@metamask/utils';
5
+ import type { Patch } from 'immer';
6
+ declare const name = "TokenListController";
7
+ export declare type TokenListToken = {
8
+ name: string;
9
+ symbol: string;
10
+ decimals: number;
11
+ address: string;
12
+ occurrences: number;
13
+ aggregators: string[];
14
+ iconUrl: string;
15
+ };
16
+ export declare type TokenListMap = Record<string, TokenListToken>;
17
+ declare type DataCache = {
18
+ timestamp: number;
19
+ data: TokenListMap;
20
+ };
21
+ declare type TokensChainsCache = {
22
+ [chainId: Hex]: DataCache;
23
+ };
24
+ export declare type TokenListState = {
25
+ tokenList: TokenListMap;
26
+ tokensChainsCache: TokensChainsCache;
27
+ preventPollingOnNetworkRestart: boolean;
28
+ };
29
+ export declare type TokenListStateChange = {
30
+ type: `${typeof name}:stateChange`;
31
+ payload: [TokenListState, Patch[]];
32
+ };
33
+ export declare type GetTokenListState = {
34
+ type: `${typeof name}:getState`;
35
+ handler: () => TokenListState;
36
+ };
37
+ declare type TokenListMessenger = RestrictedControllerMessenger<typeof name, GetTokenListState, TokenListStateChange | NetworkControllerStateChangeEvent, never, TokenListStateChange['type'] | NetworkControllerStateChangeEvent['type']>;
38
+ /**
39
+ * Controller that passively polls on a set interval for the list of tokens from metaswaps api
40
+ */
41
+ export declare class TokenListController extends BaseControllerV2<typeof name, TokenListState, TokenListMessenger> {
42
+ #private;
43
+ private readonly mutex;
44
+ private intervalId?;
45
+ private readonly intervalDelay;
46
+ private readonly cacheRefreshThreshold;
47
+ private chainId;
48
+ private abortController;
49
+ /**
50
+ * Creates a TokenListController instance.
51
+ *
52
+ * @param options - The controller options.
53
+ * @param options.chainId - The chain ID of the current network.
54
+ * @param options.onNetworkStateChange - A function for registering an event handler for network state changes.
55
+ * @param options.interval - The polling interval, in milliseconds.
56
+ * @param options.cacheRefreshThreshold - The token cache expiry time, in milliseconds.
57
+ * @param options.messenger - A restricted controller messenger.
58
+ * @param options.state - Initial state to set on this controller.
59
+ * @param options.preventPollingOnNetworkRestart - Determines whether to prevent poilling on network restart in extension.
60
+ */
61
+ constructor({ chainId, preventPollingOnNetworkRestart, onNetworkStateChange, interval, cacheRefreshThreshold, messenger, state, }: {
62
+ chainId: Hex;
63
+ preventPollingOnNetworkRestart?: boolean;
64
+ onNetworkStateChange?: (listener: (networkState: NetworkState) => void) => void;
65
+ interval?: number;
66
+ cacheRefreshThreshold?: number;
67
+ messenger: TokenListMessenger;
68
+ state?: Partial<TokenListState>;
69
+ });
70
+ /**
71
+ * Start polling for the token list.
72
+ */
73
+ start(): Promise<void>;
74
+ /**
75
+ * Restart polling for the token list.
76
+ */
77
+ restart(): Promise<void>;
78
+ /**
79
+ * Stop polling for the token list.
80
+ */
81
+ stop(): void;
82
+ /**
83
+ * Prepare to discard this controller.
84
+ *
85
+ * This stops any active polling.
86
+ */
87
+ destroy(): void;
88
+ private stopPolling;
89
+ /**
90
+ * Starts a new polling interval.
91
+ */
92
+ private startPolling;
93
+ /**
94
+ * Fetching token list from the Token Service API.
95
+ */
96
+ fetchTokenList(): Promise<void>;
97
+ /**
98
+ * Checks if the Cache timestamp is valid,
99
+ * if yes data in cache will be returned
100
+ * otherwise null will be returned.
101
+ *
102
+ * @returns The cached data, or `null` if the cache was expired.
103
+ */
104
+ fetchFromCache(): Promise<TokenListMap | null>;
105
+ /**
106
+ * Clearing tokenList and tokensChainsCache explicitly.
107
+ */
108
+ clearingTokenListData(): void;
109
+ /**
110
+ * Updates preventPollingOnNetworkRestart from extension.
111
+ *
112
+ * @param shouldPreventPolling - Determine whether to prevent polling on network change
113
+ */
114
+ updatePreventPollingOnNetworkRestart(shouldPreventPolling: boolean): void;
115
+ }
116
+ export default TokenListController;
117
+ //# sourceMappingURL=TokenListController.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TokenListController.d.ts","sourceRoot":"","sources":["../src/TokenListController.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,248 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
12
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
13
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
14
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
15
+ };
16
+ var _TokenListController_instances, _TokenListController_onNetworkControllerStateChange;
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.TokenListController = void 0;
19
+ const base_controller_1 = require("@metamask/base-controller");
20
+ const controller_utils_1 = require("@metamask/controller-utils");
21
+ const abort_controller_1 = require("abort-controller");
22
+ const async_mutex_1 = require("async-mutex");
23
+ const assetsUtil_1 = require("./assetsUtil");
24
+ const token_service_1 = require("./token-service");
25
+ const DEFAULT_INTERVAL = 24 * 60 * 60 * 1000;
26
+ const DEFAULT_THRESHOLD = 24 * 60 * 60 * 1000;
27
+ const name = 'TokenListController';
28
+ const metadata = {
29
+ tokenList: { persist: true, anonymous: true },
30
+ tokensChainsCache: { persist: true, anonymous: true },
31
+ preventPollingOnNetworkRestart: { persist: true, anonymous: true },
32
+ };
33
+ const defaultState = {
34
+ tokenList: {},
35
+ tokensChainsCache: {},
36
+ preventPollingOnNetworkRestart: false,
37
+ };
38
+ /**
39
+ * Controller that passively polls on a set interval for the list of tokens from metaswaps api
40
+ */
41
+ class TokenListController extends base_controller_1.BaseControllerV2 {
42
+ /**
43
+ * Creates a TokenListController instance.
44
+ *
45
+ * @param options - The controller options.
46
+ * @param options.chainId - The chain ID of the current network.
47
+ * @param options.onNetworkStateChange - A function for registering an event handler for network state changes.
48
+ * @param options.interval - The polling interval, in milliseconds.
49
+ * @param options.cacheRefreshThreshold - The token cache expiry time, in milliseconds.
50
+ * @param options.messenger - A restricted controller messenger.
51
+ * @param options.state - Initial state to set on this controller.
52
+ * @param options.preventPollingOnNetworkRestart - Determines whether to prevent poilling on network restart in extension.
53
+ */
54
+ constructor({ chainId, preventPollingOnNetworkRestart = false, onNetworkStateChange, interval = DEFAULT_INTERVAL, cacheRefreshThreshold = DEFAULT_THRESHOLD, messenger, state, }) {
55
+ super({
56
+ name,
57
+ metadata,
58
+ messenger,
59
+ state: Object.assign(Object.assign({}, defaultState), state),
60
+ });
61
+ _TokenListController_instances.add(this);
62
+ this.mutex = new async_mutex_1.Mutex();
63
+ this.intervalDelay = interval;
64
+ this.cacheRefreshThreshold = cacheRefreshThreshold;
65
+ this.chainId = chainId;
66
+ this.updatePreventPollingOnNetworkRestart(preventPollingOnNetworkRestart);
67
+ this.abortController = new abort_controller_1.AbortController();
68
+ if (onNetworkStateChange) {
69
+ onNetworkStateChange((networkControllerState) => __awaiter(this, void 0, void 0, function* () {
70
+ yield __classPrivateFieldGet(this, _TokenListController_instances, "m", _TokenListController_onNetworkControllerStateChange).call(this, networkControllerState);
71
+ }));
72
+ }
73
+ else {
74
+ this.messagingSystem.subscribe('NetworkController:stateChange', (networkControllerState) => __awaiter(this, void 0, void 0, function* () {
75
+ yield __classPrivateFieldGet(this, _TokenListController_instances, "m", _TokenListController_onNetworkControllerStateChange).call(this, networkControllerState);
76
+ }));
77
+ }
78
+ }
79
+ /**
80
+ * Start polling for the token list.
81
+ */
82
+ start() {
83
+ return __awaiter(this, void 0, void 0, function* () {
84
+ if (!(0, assetsUtil_1.isTokenListSupportedForNetwork)(this.chainId)) {
85
+ return;
86
+ }
87
+ yield this.startPolling();
88
+ });
89
+ }
90
+ /**
91
+ * Restart polling for the token list.
92
+ */
93
+ restart() {
94
+ return __awaiter(this, void 0, void 0, function* () {
95
+ this.stopPolling();
96
+ yield this.startPolling();
97
+ });
98
+ }
99
+ /**
100
+ * Stop polling for the token list.
101
+ */
102
+ stop() {
103
+ this.stopPolling();
104
+ }
105
+ /**
106
+ * Prepare to discard this controller.
107
+ *
108
+ * This stops any active polling.
109
+ */
110
+ destroy() {
111
+ super.destroy();
112
+ this.stopPolling();
113
+ }
114
+ stopPolling() {
115
+ if (this.intervalId) {
116
+ clearInterval(this.intervalId);
117
+ }
118
+ }
119
+ /**
120
+ * Starts a new polling interval.
121
+ */
122
+ startPolling() {
123
+ return __awaiter(this, void 0, void 0, function* () {
124
+ yield (0, controller_utils_1.safelyExecute)(() => this.fetchTokenList());
125
+ this.intervalId = setInterval(() => __awaiter(this, void 0, void 0, function* () {
126
+ yield (0, controller_utils_1.safelyExecute)(() => this.fetchTokenList());
127
+ }), this.intervalDelay);
128
+ });
129
+ }
130
+ /**
131
+ * Fetching token list from the Token Service API.
132
+ */
133
+ fetchTokenList() {
134
+ var _a;
135
+ return __awaiter(this, void 0, void 0, function* () {
136
+ const releaseLock = yield this.mutex.acquire();
137
+ try {
138
+ const { tokensChainsCache } = this.state;
139
+ let tokenList = {};
140
+ const cachedTokens = yield (0, controller_utils_1.safelyExecute)(() => this.fetchFromCache());
141
+ if (cachedTokens) {
142
+ // Use non-expired cached tokens
143
+ tokenList = Object.assign({}, cachedTokens);
144
+ }
145
+ else {
146
+ // Fetch fresh token list
147
+ const tokensFromAPI = yield (0, controller_utils_1.safelyExecute)(() => (0, token_service_1.fetchTokenList)(this.chainId, this.abortController.signal));
148
+ if (!tokensFromAPI) {
149
+ // Fallback to expired cached tokens
150
+ tokenList = Object.assign({}, (((_a = tokensChainsCache[this.chainId]) === null || _a === void 0 ? void 0 : _a.data) || {}));
151
+ this.update(() => {
152
+ return Object.assign(Object.assign({}, this.state), { tokenList,
153
+ tokensChainsCache });
154
+ });
155
+ return;
156
+ }
157
+ // Filtering out tokens with less than 3 occurrences and native tokens
158
+ const filteredTokenList = tokensFromAPI.filter((token) => token.occurrences &&
159
+ token.occurrences >= 3 &&
160
+ token.address !== '0x0000000000000000000000000000000000000000');
161
+ // Removing the tokens with symbol conflicts
162
+ const symbolsList = filteredTokenList.map((token) => token.symbol);
163
+ const duplicateSymbols = [
164
+ ...new Set(symbolsList.filter((symbol, index) => symbolsList.indexOf(symbol) !== index)),
165
+ ];
166
+ const uniqueTokenList = filteredTokenList.filter((token) => !duplicateSymbols.includes(token.symbol));
167
+ for (const token of uniqueTokenList) {
168
+ const formattedToken = Object.assign(Object.assign({}, token), { aggregators: (0, assetsUtil_1.formatAggregatorNames)(token.aggregators), iconUrl: (0, assetsUtil_1.formatIconUrlWithProxy)({
169
+ chainId: this.chainId,
170
+ tokenAddress: token.address,
171
+ }) });
172
+ tokenList[token.address] = formattedToken;
173
+ }
174
+ }
175
+ const updatedTokensChainsCache = Object.assign(Object.assign({}, tokensChainsCache), { [this.chainId]: {
176
+ timestamp: Date.now(),
177
+ data: tokenList,
178
+ } });
179
+ this.update(() => {
180
+ return Object.assign(Object.assign({}, this.state), { tokenList, tokensChainsCache: updatedTokensChainsCache });
181
+ });
182
+ }
183
+ finally {
184
+ releaseLock();
185
+ }
186
+ });
187
+ }
188
+ /**
189
+ * Checks if the Cache timestamp is valid,
190
+ * if yes data in cache will be returned
191
+ * otherwise null will be returned.
192
+ *
193
+ * @returns The cached data, or `null` if the cache was expired.
194
+ */
195
+ fetchFromCache() {
196
+ return __awaiter(this, void 0, void 0, function* () {
197
+ const { tokensChainsCache } = this.state;
198
+ const dataCache = tokensChainsCache[this.chainId];
199
+ const now = Date.now();
200
+ if ((dataCache === null || dataCache === void 0 ? void 0 : dataCache.data) &&
201
+ now - (dataCache === null || dataCache === void 0 ? void 0 : dataCache.timestamp) < this.cacheRefreshThreshold) {
202
+ return dataCache.data;
203
+ }
204
+ return null;
205
+ });
206
+ }
207
+ /**
208
+ * Clearing tokenList and tokensChainsCache explicitly.
209
+ */
210
+ clearingTokenListData() {
211
+ this.update(() => {
212
+ return Object.assign(Object.assign({}, this.state), { tokenList: {}, tokensChainsCache: {} });
213
+ });
214
+ }
215
+ /**
216
+ * Updates preventPollingOnNetworkRestart from extension.
217
+ *
218
+ * @param shouldPreventPolling - Determine whether to prevent polling on network change
219
+ */
220
+ updatePreventPollingOnNetworkRestart(shouldPreventPolling) {
221
+ this.update(() => {
222
+ return Object.assign(Object.assign({}, this.state), { preventPollingOnNetworkRestart: shouldPreventPolling });
223
+ });
224
+ }
225
+ }
226
+ exports.TokenListController = TokenListController;
227
+ _TokenListController_instances = new WeakSet(), _TokenListController_onNetworkControllerStateChange = function _TokenListController_onNetworkControllerStateChange(networkControllerState) {
228
+ return __awaiter(this, void 0, void 0, function* () {
229
+ if (this.chainId !== networkControllerState.providerConfig.chainId) {
230
+ this.abortController.abort();
231
+ this.abortController = new abort_controller_1.AbortController();
232
+ this.chainId = networkControllerState.providerConfig.chainId;
233
+ if (this.state.preventPollingOnNetworkRestart) {
234
+ this.clearingTokenListData();
235
+ }
236
+ else {
237
+ // Ensure tokenList is referencing data from correct network
238
+ this.update(() => {
239
+ var _a;
240
+ return Object.assign(Object.assign({}, this.state), { tokenList: ((_a = this.state.tokensChainsCache[this.chainId]) === null || _a === void 0 ? void 0 : _a.data) || {} });
241
+ });
242
+ yield this.restart();
243
+ }
244
+ }
245
+ });
246
+ };
247
+ exports.default = TokenListController;
248
+ //# sourceMappingURL=TokenListController.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TokenListController.js","sourceRoot":"","sources":["../src/TokenListController.ts"],"names":[],"mappings":"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","sourcesContent":["import type { RestrictedControllerMessenger } from '@metamask/base-controller';\nimport { BaseControllerV2 } from '@metamask/base-controller';\nimport { safelyExecute } from '@metamask/controller-utils';\nimport type {\n NetworkControllerStateChangeEvent,\n NetworkState,\n} from '@metamask/network-controller';\nimport type { Hex } from '@metamask/utils';\nimport { AbortController as WhatwgAbortController } from 'abort-controller';\nimport { Mutex } from 'async-mutex';\nimport type { Patch } from 'immer';\n\nimport {\n isTokenListSupportedForNetwork,\n formatAggregatorNames,\n formatIconUrlWithProxy,\n} from './assetsUtil';\nimport { fetchTokenList } from './token-service';\n\nconst DEFAULT_INTERVAL = 24 * 60 * 60 * 1000;\nconst DEFAULT_THRESHOLD = 24 * 60 * 60 * 1000;\n\nconst name = 'TokenListController';\n\nexport type TokenListToken = {\n name: string;\n symbol: string;\n decimals: number;\n address: string;\n occurrences: number;\n aggregators: string[];\n iconUrl: string;\n};\n\nexport type TokenListMap = Record<string, TokenListToken>;\n\ntype DataCache = {\n timestamp: number;\n data: TokenListMap;\n};\ntype TokensChainsCache = {\n [chainId: Hex]: DataCache;\n};\n\nexport type TokenListState = {\n tokenList: TokenListMap;\n tokensChainsCache: TokensChainsCache;\n preventPollingOnNetworkRestart: boolean;\n};\n\nexport type TokenListStateChange = {\n type: `${typeof name}:stateChange`;\n payload: [TokenListState, Patch[]];\n};\n\nexport type GetTokenListState = {\n type: `${typeof name}:getState`;\n handler: () => TokenListState;\n};\n\ntype TokenListMessenger = RestrictedControllerMessenger<\n typeof name,\n GetTokenListState,\n TokenListStateChange | NetworkControllerStateChangeEvent,\n never,\n TokenListStateChange['type'] | NetworkControllerStateChangeEvent['type']\n>;\n\nconst metadata = {\n tokenList: { persist: true, anonymous: true },\n tokensChainsCache: { persist: true, anonymous: true },\n preventPollingOnNetworkRestart: { persist: true, anonymous: true },\n};\n\nconst defaultState: TokenListState = {\n tokenList: {},\n tokensChainsCache: {},\n preventPollingOnNetworkRestart: false,\n};\n\n/**\n * Controller that passively polls on a set interval for the list of tokens from metaswaps api\n */\nexport class TokenListController extends BaseControllerV2<\n typeof name,\n TokenListState,\n TokenListMessenger\n> {\n private readonly mutex = new Mutex();\n\n private intervalId?: ReturnType<typeof setTimeout>;\n\n private readonly intervalDelay: number;\n\n private readonly cacheRefreshThreshold: number;\n\n private chainId: Hex;\n\n private abortController: WhatwgAbortController;\n\n /**\n * Creates a TokenListController instance.\n *\n * @param options - The controller options.\n * @param options.chainId - The chain ID of the current network.\n * @param options.onNetworkStateChange - A function for registering an event handler for network state changes.\n * @param options.interval - The polling interval, in milliseconds.\n * @param options.cacheRefreshThreshold - The token cache expiry time, in milliseconds.\n * @param options.messenger - A restricted controller messenger.\n * @param options.state - Initial state to set on this controller.\n * @param options.preventPollingOnNetworkRestart - Determines whether to prevent poilling on network restart in extension.\n */\n constructor({\n chainId,\n preventPollingOnNetworkRestart = false,\n onNetworkStateChange,\n interval = DEFAULT_INTERVAL,\n cacheRefreshThreshold = DEFAULT_THRESHOLD,\n messenger,\n state,\n }: {\n chainId: Hex;\n preventPollingOnNetworkRestart?: boolean;\n onNetworkStateChange?: (\n listener: (networkState: NetworkState) => void,\n ) => void;\n interval?: number;\n cacheRefreshThreshold?: number;\n messenger: TokenListMessenger;\n state?: Partial<TokenListState>;\n }) {\n super({\n name,\n metadata,\n messenger,\n state: { ...defaultState, ...state },\n });\n this.intervalDelay = interval;\n this.cacheRefreshThreshold = cacheRefreshThreshold;\n this.chainId = chainId;\n this.updatePreventPollingOnNetworkRestart(preventPollingOnNetworkRestart);\n this.abortController = new WhatwgAbortController();\n if (onNetworkStateChange) {\n onNetworkStateChange(async (networkControllerState) => {\n await this.#onNetworkControllerStateChange(networkControllerState);\n });\n } else {\n this.messagingSystem.subscribe(\n 'NetworkController:stateChange',\n async (networkControllerState) => {\n await this.#onNetworkControllerStateChange(networkControllerState);\n },\n );\n }\n }\n\n /**\n * Updates state and restarts polling on changes to the network controller\n * state.\n *\n * @param networkControllerState - The updated network controller state.\n */\n async #onNetworkControllerStateChange(networkControllerState: NetworkState) {\n if (this.chainId !== networkControllerState.providerConfig.chainId) {\n this.abortController.abort();\n this.abortController = new WhatwgAbortController();\n this.chainId = networkControllerState.providerConfig.chainId;\n if (this.state.preventPollingOnNetworkRestart) {\n this.clearingTokenListData();\n } else {\n // Ensure tokenList is referencing data from correct network\n this.update(() => {\n return {\n ...this.state,\n tokenList: this.state.tokensChainsCache[this.chainId]?.data || {},\n };\n });\n await this.restart();\n }\n }\n }\n\n /**\n * Start polling for the token list.\n */\n async start() {\n if (!isTokenListSupportedForNetwork(this.chainId)) {\n return;\n }\n await this.startPolling();\n }\n\n /**\n * Restart polling for the token list.\n */\n async restart() {\n this.stopPolling();\n await this.startPolling();\n }\n\n /**\n * Stop polling for the token list.\n */\n stop() {\n this.stopPolling();\n }\n\n /**\n * Prepare to discard this controller.\n *\n * This stops any active polling.\n */\n override destroy() {\n super.destroy();\n this.stopPolling();\n }\n\n private stopPolling() {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n }\n }\n\n /**\n * Starts a new polling interval.\n */\n private async startPolling(): Promise<void> {\n await safelyExecute(() => this.fetchTokenList());\n this.intervalId = setInterval(async () => {\n await safelyExecute(() => this.fetchTokenList());\n }, this.intervalDelay);\n }\n\n /**\n * Fetching token list from the Token Service API.\n */\n async fetchTokenList(): Promise<void> {\n const releaseLock = await this.mutex.acquire();\n try {\n const { tokensChainsCache } = this.state;\n let tokenList: TokenListMap = {};\n const cachedTokens: TokenListMap = await safelyExecute(() =>\n this.fetchFromCache(),\n );\n if (cachedTokens) {\n // Use non-expired cached tokens\n tokenList = { ...cachedTokens };\n } else {\n // Fetch fresh token list\n const tokensFromAPI: TokenListToken[] = await safelyExecute(() =>\n fetchTokenList(this.chainId, this.abortController.signal),\n );\n\n if (!tokensFromAPI) {\n // Fallback to expired cached tokens\n tokenList = { ...(tokensChainsCache[this.chainId]?.data || {}) };\n\n this.update(() => {\n return {\n ...this.state,\n tokenList,\n tokensChainsCache,\n };\n });\n return;\n }\n // Filtering out tokens with less than 3 occurrences and native tokens\n const filteredTokenList = tokensFromAPI.filter(\n (token) =>\n token.occurrences &&\n token.occurrences >= 3 &&\n token.address !== '0x0000000000000000000000000000000000000000',\n );\n // Removing the tokens with symbol conflicts\n const symbolsList = filteredTokenList.map((token) => token.symbol);\n const duplicateSymbols = [\n ...new Set(\n symbolsList.filter(\n (symbol, index) => symbolsList.indexOf(symbol) !== index,\n ),\n ),\n ];\n const uniqueTokenList = filteredTokenList.filter(\n (token) => !duplicateSymbols.includes(token.symbol),\n );\n for (const token of uniqueTokenList) {\n const formattedToken: TokenListToken = {\n ...token,\n aggregators: formatAggregatorNames(token.aggregators),\n iconUrl: formatIconUrlWithProxy({\n chainId: this.chainId,\n tokenAddress: token.address,\n }),\n };\n tokenList[token.address] = formattedToken;\n }\n }\n const updatedTokensChainsCache: TokensChainsCache = {\n ...tokensChainsCache,\n [this.chainId]: {\n timestamp: Date.now(),\n data: tokenList,\n },\n };\n this.update(() => {\n return {\n ...this.state,\n tokenList,\n tokensChainsCache: updatedTokensChainsCache,\n };\n });\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Checks if the Cache timestamp is valid,\n * if yes data in cache will be returned\n * otherwise null will be returned.\n *\n * @returns The cached data, or `null` if the cache was expired.\n */\n async fetchFromCache(): Promise<TokenListMap | null> {\n const { tokensChainsCache }: TokenListState = this.state;\n const dataCache = tokensChainsCache[this.chainId];\n const now = Date.now();\n if (\n dataCache?.data &&\n now - dataCache?.timestamp < this.cacheRefreshThreshold\n ) {\n return dataCache.data;\n }\n return null;\n }\n\n /**\n * Clearing tokenList and tokensChainsCache explicitly.\n */\n clearingTokenListData(): void {\n this.update(() => {\n return {\n ...this.state,\n tokenList: {},\n tokensChainsCache: {},\n };\n });\n }\n\n /**\n * Updates preventPollingOnNetworkRestart from extension.\n *\n * @param shouldPreventPolling - Determine whether to prevent polling on network change\n */\n updatePreventPollingOnNetworkRestart(shouldPreventPolling: boolean): void {\n this.update(() => {\n return {\n ...this.state,\n preventPollingOnNetworkRestart: shouldPreventPolling,\n };\n });\n }\n}\n\nexport default TokenListController;\n"]}