@metamask-previews/assets-controllers 96.0.0-preview-a94732c3 → 96.0.0-preview-766f7065

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 (48) hide show
  1. package/CHANGELOG.md +0 -28
  2. package/dist/TokenListController.cjs +78 -353
  3. package/dist/TokenListController.cjs.map +1 -1
  4. package/dist/TokenListController.d.cts +17 -22
  5. package/dist/TokenListController.d.cts.map +1 -1
  6. package/dist/TokenListController.d.mts +17 -22
  7. package/dist/TokenListController.d.mts.map +1 -1
  8. package/dist/TokenListController.mjs +78 -353
  9. package/dist/TokenListController.mjs.map +1 -1
  10. package/dist/TokenRatesController.cjs +1 -8
  11. package/dist/TokenRatesController.cjs.map +1 -1
  12. package/dist/TokenRatesController.d.cts +1 -2
  13. package/dist/TokenRatesController.d.cts.map +1 -1
  14. package/dist/TokenRatesController.d.mts +1 -2
  15. package/dist/TokenRatesController.d.mts.map +1 -1
  16. package/dist/TokenRatesController.mjs +1 -8
  17. package/dist/TokenRatesController.mjs.map +1 -1
  18. package/dist/TokenSearchDiscoveryDataController/TokenSearchDiscoveryDataController.cjs +53 -3
  19. package/dist/TokenSearchDiscoveryDataController/TokenSearchDiscoveryDataController.cjs.map +1 -1
  20. package/dist/TokenSearchDiscoveryDataController/TokenSearchDiscoveryDataController.d.cts +12 -1
  21. package/dist/TokenSearchDiscoveryDataController/TokenSearchDiscoveryDataController.d.cts.map +1 -1
  22. package/dist/TokenSearchDiscoveryDataController/TokenSearchDiscoveryDataController.d.mts +12 -1
  23. package/dist/TokenSearchDiscoveryDataController/TokenSearchDiscoveryDataController.d.mts.map +1 -1
  24. package/dist/TokenSearchDiscoveryDataController/TokenSearchDiscoveryDataController.mjs +53 -3
  25. package/dist/TokenSearchDiscoveryDataController/TokenSearchDiscoveryDataController.mjs.map +1 -1
  26. package/dist/token-prices-service/abstract-token-prices-service.cjs.map +1 -1
  27. package/dist/token-prices-service/abstract-token-prices-service.d.cts +1 -12
  28. package/dist/token-prices-service/abstract-token-prices-service.d.cts.map +1 -1
  29. package/dist/token-prices-service/abstract-token-prices-service.d.mts +1 -12
  30. package/dist/token-prices-service/abstract-token-prices-service.d.mts.map +1 -1
  31. package/dist/token-prices-service/abstract-token-prices-service.mjs.map +1 -1
  32. package/dist/token-prices-service/codefi-v2.cjs +16 -156
  33. package/dist/token-prices-service/codefi-v2.cjs.map +1 -1
  34. package/dist/token-prices-service/codefi-v2.d.cts +1 -44
  35. package/dist/token-prices-service/codefi-v2.d.cts.map +1 -1
  36. package/dist/token-prices-service/codefi-v2.d.mts +1 -44
  37. package/dist/token-prices-service/codefi-v2.d.mts.map +1 -1
  38. package/dist/token-prices-service/codefi-v2.mjs +16 -153
  39. package/dist/token-prices-service/codefi-v2.mjs.map +1 -1
  40. package/dist/token-prices-service/index.cjs +1 -4
  41. package/dist/token-prices-service/index.cjs.map +1 -1
  42. package/dist/token-prices-service/index.d.cts +2 -2
  43. package/dist/token-prices-service/index.d.cts.map +1 -1
  44. package/dist/token-prices-service/index.d.mts +2 -2
  45. package/dist/token-prices-service/index.d.mts.map +1 -1
  46. package/dist/token-prices-service/index.mjs +1 -1
  47. package/dist/token-prices-service/index.mjs.map +1 -1
  48. package/package.json +2 -4
package/CHANGELOG.md CHANGED
@@ -7,33 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
- ### Added
11
-
12
- - Add dynamic fetching of supported networks from `/v2/supportedNetworks` API endpoint with fallback to hardcoded list ([#7716](https://github.com/MetaMask/core/pull/7716))
13
- - Add `fetchSupportedNetworks()`, `getSupportedNetworks()`, and `resetSupportedNetworksCache()` exports from token-prices-service
14
- - Add `setNativeAssetIdentifiers()` method to `CodefiTokenPricesServiceV2` for CAIP-19 native token lookups
15
- - Add `updateSupportedNetworks()` method to `CodefiTokenPricesServiceV2`
16
- - Add `NativeAssetIdentifiersMap` type export from token-prices-service
17
- - Integrate `TokenRatesController` with `NetworkEnablementController` to use native asset identifiers for token price lookups ([#7716](https://github.com/MetaMask/core/pull/7716))
18
-
19
- ### Changed
20
-
21
- - **BREAKING:** `TokenListController` now persists `tokensChainsCache` via `StorageService` and requires clients to call `initialize()` after construction ([#7413](https://github.com/MetaMask/core/pull/7413))
22
- - Each chain's token cache is stored in a separate file, reducing write amplification
23
- - All chains are loaded in parallel at startup to maintain compatibility with TokenDetectionController
24
- - `tokensChainsCache` state metadata now has `persist: false` to prevent duplicate persistence
25
- - Clients must call `await controller.initialize()` before using the controller
26
- - State changes are automatically persisted via debounced subscription
27
- - Bump `@metamask/keyring-controller` from `^25.0.0` to `^25.1.0` ([#7713](https://github.com/MetaMask/core/pull/7713))
28
- - Add `@metamask/network-enablement-controller` as a dependency ([#7716](https://github.com/MetaMask/core/pull/7716))
29
-
30
- ### Removed
31
-
32
- - **BREAKING:** Remove swaps token fetching functionality from TokenSearchDiscoveryDataController ([#7712](https://github.com/MetaMask/core/pull/7712))
33
- - Remove `swapsTokenAddressesByChainId` from controller state
34
- - Remove `swapsSupportedChainIds`, `fetchTokens`, and `fetchSwapsTokensThresholdMs` constructor parameters
35
- - Remove `fetchSwapsTokens` method
36
-
37
10
  ## [96.0.0]
38
11
 
39
12
  ### Added
@@ -80,7 +53,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
80
53
 
81
54
  ### Changed
82
55
 
83
- - Bump `@metamask/transaction-controller` from `^62.8.0` to `^62.9.0` ([#7602](https://github.com/MetaMask/core/pull/7602))
84
56
  - Bump `@metamask/transaction-controller` from `^62.8.0` to `^62.9.1` ([#7602](https://github.com/MetaMask/core/pull/7602), [#7604](https://github.com/MetaMask/core/pull/7604))
85
57
  - Bump `@metamask/network-controller` from `^27.2.0` to `^28.0.0` ([#7604](https://github.com/MetaMask/core/pull/7604))
86
58
  - Bump `@metamask/accounts-controller` from `^35.0.0` to `^35.0.1` ([#7604](https://github.com/MetaMask/core/pull/7604))
@@ -4,17 +4,12 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
4
4
  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");
5
5
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
6
6
  };
7
- var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
8
- if (kind === "m") throw new TypeError("Private method is not writable");
9
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
10
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
11
- return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
12
- };
13
- var _TokenListController_instances, _a, _TokenListController_initializationPromise, _TokenListController_persistDebounceTimer, _TokenListController_persistInFlightPromise, _TokenListController_changedChainsToPersist, _TokenListController_chainsLoadedFromStorage, _TokenListController_previousTokensChainsCache, _TokenListController_persistDebounceMs, _TokenListController_storageKeyPrefix, _TokenListController_getChainStorageKey, _TokenListController_intervalId, _TokenListController_intervalDelay, _TokenListController_cacheRefreshThreshold, _TokenListController_chainId, _TokenListController_abortController, _TokenListController_onCacheChanged, _TokenListController_debouncePersist, _TokenListController_persistChangedChains, _TokenListController_loadCacheFromStorage, _TokenListController_saveChainCacheToStorage, _TokenListController_onNetworkControllerStateChange, _TokenListController_stopPolling, _TokenListController_startDeprecatedPolling;
7
+ var _TokenListController_instances, _TokenListController_onNetworkControllerStateChange, _TokenListController_startDeprecatedPolling;
14
8
  Object.defineProperty(exports, "__esModule", { value: true });
15
9
  exports.TokenListController = exports.getDefaultTokenListState = void 0;
16
10
  const controller_utils_1 = require("@metamask/controller-utils");
17
11
  const polling_controller_1 = require("@metamask/polling-controller");
12
+ const async_mutex_1 = require("async-mutex");
18
13
  const assetsUtil_1 = require("./assetsUtil.cjs");
19
14
  const token_service_1 = require("./token-service.cjs");
20
15
  // 4 Hour Interval Cache Refresh Threshold
@@ -24,7 +19,7 @@ const name = 'TokenListController';
24
19
  const metadata = {
25
20
  tokensChainsCache: {
26
21
  includeInStateLogs: false,
27
- persist: false, // Persisted separately via StorageService
22
+ persist: true,
28
23
  includeInDebugSnapshot: true,
29
24
  usedInUi: true,
30
25
  },
@@ -66,48 +61,13 @@ class TokenListController extends (0, polling_controller_1.StaticIntervalPolling
66
61
  state: { ...(0, exports.getDefaultTokenListState)(), ...state },
67
62
  });
68
63
  _TokenListController_instances.add(this);
69
- /**
70
- * Promise that resolves when initialization (loading cache from storage) is complete.
71
- */
72
- _TokenListController_initializationPromise.set(this, Promise.resolve());
73
- /**
74
- * Debounce timer for persisting state changes to storage.
75
- */
76
- _TokenListController_persistDebounceTimer.set(this, void 0);
77
- /**
78
- * Promise that resolves when the current persist operation completes.
79
- * Used to prevent race conditions between persist and clear operations.
80
- */
81
- _TokenListController_persistInFlightPromise.set(this, void 0);
82
- /**
83
- * Tracks which chains have pending changes to persist.
84
- * Only changed chains are persisted to reduce write amplification.
85
- */
86
- _TokenListController_changedChainsToPersist.set(this, new Set());
87
- /**
88
- * Tracks chains that were just loaded from storage and should skip
89
- * the next persistence cycle. This prevents redundant writes where
90
- * data loaded from storage would be immediately written back.
91
- * Chains are removed from this set after being skipped once.
92
- */
93
- _TokenListController_chainsLoadedFromStorage.set(this, new Set());
94
- /**
95
- * Previous tokensChainsCache for detecting which chains changed.
96
- */
97
- _TokenListController_previousTokensChainsCache.set(this, {});
98
- _TokenListController_intervalId.set(this, void 0);
99
- _TokenListController_intervalDelay.set(this, void 0);
100
- _TokenListController_cacheRefreshThreshold.set(this, void 0);
101
- _TokenListController_chainId.set(this, void 0);
102
- _TokenListController_abortController.set(this, void 0);
103
- __classPrivateFieldSet(this, _TokenListController_intervalDelay, interval, "f");
64
+ this.mutex = new async_mutex_1.Mutex();
65
+ this.intervalDelay = interval;
104
66
  this.setIntervalLength(interval);
105
- __classPrivateFieldSet(this, _TokenListController_cacheRefreshThreshold, cacheRefreshThreshold, "f");
106
- __classPrivateFieldSet(this, _TokenListController_chainId, chainId, "f");
67
+ this.cacheRefreshThreshold = cacheRefreshThreshold;
68
+ this.chainId = chainId;
107
69
  this.updatePreventPollingOnNetworkRestart(preventPollingOnNetworkRestart);
108
- __classPrivateFieldSet(this, _TokenListController_abortController, new AbortController(), "f");
109
- // Subscribe to state changes to automatically persist tokensChainsCache
110
- this.messenger.subscribe('TokenListController:stateChange', (newCache) => __classPrivateFieldGet(this, _TokenListController_instances, "m", _TokenListController_onCacheChanged).call(this, newCache), (controllerState) => controllerState.tokensChainsCache);
70
+ this.abortController = new AbortController();
111
71
  if (onNetworkStateChange) {
112
72
  // TODO: Either fix this lint violation or explain why it's necessary to ignore.
113
73
  // eslint-disable-next-line @typescript-eslint/no-misused-promises
@@ -124,16 +84,6 @@ class TokenListController extends (0, polling_controller_1.StaticIntervalPolling
124
84
  });
125
85
  }
126
86
  }
127
- /**
128
- * Initialize the controller by loading cache from storage and running migration.
129
- * This method should be called by clients after construction.
130
- *
131
- * @returns A promise that resolves when initialization is complete.
132
- */
133
- async initialize() {
134
- __classPrivateFieldSet(this, _TokenListController_initializationPromise, __classPrivateFieldGet(this, _TokenListController_instances, "m", _TokenListController_loadCacheFromStorage).call(this), "f");
135
- await __classPrivateFieldGet(this, _TokenListController_initializationPromise, "f");
136
- }
137
87
  // Eventually we want to remove start/restart/stop controls in favor of new _executePoll API
138
88
  // Maintaining these functions for now until we can safely deprecate them for backwards compatibility
139
89
  /**
@@ -143,7 +93,7 @@ class TokenListController extends (0, polling_controller_1.StaticIntervalPolling
143
93
  * Consider using the new polling approach instead
144
94
  */
145
95
  async start() {
146
- if (!(0, assetsUtil_1.isTokenListSupportedForNetwork)(__classPrivateFieldGet(this, _TokenListController_chainId, "f"))) {
96
+ if (!(0, assetsUtil_1.isTokenListSupportedForNetwork)(this.chainId)) {
147
97
  return;
148
98
  }
149
99
  await __classPrivateFieldGet(this, _TokenListController_instances, "m", _TokenListController_startDeprecatedPolling).call(this);
@@ -155,7 +105,7 @@ class TokenListController extends (0, polling_controller_1.StaticIntervalPolling
155
105
  * Consider using the new polling approach instead
156
106
  */
157
107
  async restart() {
158
- __classPrivateFieldGet(this, _TokenListController_instances, "m", _TokenListController_stopPolling).call(this);
108
+ this.stopPolling();
159
109
  await __classPrivateFieldGet(this, _TokenListController_instances, "m", _TokenListController_startDeprecatedPolling).call(this);
160
110
  }
161
111
  /**
@@ -165,7 +115,7 @@ class TokenListController extends (0, polling_controller_1.StaticIntervalPolling
165
115
  * Consider using the new polling approach instead
166
116
  */
167
117
  stop() {
168
- __classPrivateFieldGet(this, _TokenListController_instances, "m", _TokenListController_stopPolling).call(this);
118
+ this.stopPolling();
169
119
  }
170
120
  /**
171
121
  * This stops any active polling.
@@ -175,14 +125,18 @@ class TokenListController extends (0, polling_controller_1.StaticIntervalPolling
175
125
  */
176
126
  destroy() {
177
127
  super.destroy();
178
- __classPrivateFieldGet(this, _TokenListController_instances, "m", _TokenListController_stopPolling).call(this);
179
- // Cancel any pending debounced persistence operations
180
- if (__classPrivateFieldGet(this, _TokenListController_persistDebounceTimer, "f")) {
181
- clearTimeout(__classPrivateFieldGet(this, _TokenListController_persistDebounceTimer, "f"));
182
- __classPrivateFieldSet(this, _TokenListController_persistDebounceTimer, undefined, "f");
128
+ this.stopPolling();
129
+ }
130
+ /**
131
+ * This stops any active polling intervals.
132
+ *
133
+ * @deprecated This method is deprecated and will be removed in the future.
134
+ * Consider using the new polling approach instead
135
+ */
136
+ stopPolling() {
137
+ if (this.intervalId) {
138
+ clearInterval(this.intervalId);
183
139
  }
184
- __classPrivateFieldGet(this, _TokenListController_changedChainsToPersist, "f").clear();
185
- __classPrivateFieldGet(this, _TokenListController_chainsLoadedFromStorage, "f").clear();
186
140
  }
187
141
  /**
188
142
  * This starts a new polling loop for any given chain. Under the hood it is deduping polls
@@ -195,142 +149,71 @@ class TokenListController extends (0, polling_controller_1.StaticIntervalPolling
195
149
  return this.fetchTokenList(chainId);
196
150
  }
197
151
  /**
198
- * Fetching token list from the Token Service API. This will fetch tokens across chains.
199
- * State changes are automatically persisted via the stateChange subscription.
152
+ * Fetching token list from the Token Service API. This will fetch tokens across chains. It will update tokensChainsCache (scoped across chains), and also the tokenList (scoped for the selected chain)
200
153
  *
201
154
  * @param chainId - The chainId of the current chain triggering the fetch.
202
155
  */
203
156
  async fetchTokenList(chainId) {
204
- if (this.isCacheValid(chainId)) {
205
- return;
206
- }
207
- // Fetch fresh token list from the API
208
- const tokensFromAPI = await (0, controller_utils_1.safelyExecute)(() => (0, token_service_1.fetchTokenListByChainId)(chainId, __classPrivateFieldGet(this, _TokenListController_abortController, "f").signal));
209
- // Have response - process and update list
210
- if (tokensFromAPI) {
211
- // Format tokens from API (HTTP) and update tokenList
212
- const tokenList = {};
213
- for (const token of tokensFromAPI) {
214
- tokenList[token.address] = {
215
- ...token,
216
- aggregators: (0, assetsUtil_1.formatAggregatorNames)(token.aggregators),
217
- iconUrl: (0, assetsUtil_1.formatIconUrlWithProxy)({
218
- chainId,
219
- tokenAddress: token.address,
220
- }),
221
- };
157
+ const releaseLock = await this.mutex.acquire();
158
+ try {
159
+ if (this.isCacheValid(chainId)) {
160
+ return;
222
161
  }
223
- // Update state - persistence happens automatically via subscription
224
- const newDataCache = {
225
- data: tokenList,
226
- timestamp: Date.now(),
227
- };
228
- this.update((state) => {
229
- state.tokensChainsCache[chainId] = newDataCache;
230
- });
231
- return;
232
- }
233
- // No response - fallback to previous state, or initialise empty.
234
- // Only initialize with a new timestamp if there's no existing cache.
235
- // If there's existing cache, keep it as-is without updating the timestamp
236
- // to avoid making stale data appear "fresh" and preventing retry attempts.
237
- if (!tokensFromAPI) {
238
- const existingCache = this.state.tokensChainsCache[chainId];
239
- if (!existingCache) {
240
- // No existing cache - initialize empty (persistence happens automatically)
241
- const newDataCache = { data: {}, timestamp: Date.now() };
162
+ // Fetch fresh token list from the API
163
+ const tokensFromAPI = await (0, controller_utils_1.safelyExecute)(() => (0, token_service_1.fetchTokenListByChainId)(chainId, this.abortController.signal));
164
+ // Have response - process and update list
165
+ if (tokensFromAPI) {
166
+ // Format tokens from API (HTTP) and update tokenList
167
+ const tokenList = {};
168
+ for (const token of tokensFromAPI) {
169
+ tokenList[token.address] = {
170
+ ...token,
171
+ aggregators: (0, assetsUtil_1.formatAggregatorNames)(token.aggregators),
172
+ iconUrl: (0, assetsUtil_1.formatIconUrlWithProxy)({
173
+ chainId,
174
+ tokenAddress: token.address,
175
+ }),
176
+ };
177
+ }
242
178
  this.update((state) => {
243
- state.tokensChainsCache[chainId] = newDataCache;
179
+ var _a;
180
+ const newDataCache = { data: {}, timestamp: Date.now() };
181
+ (_a = state.tokensChainsCache)[chainId] ?? (_a[chainId] = newDataCache);
182
+ state.tokensChainsCache[chainId].data = tokenList;
183
+ state.tokensChainsCache[chainId].timestamp = Date.now();
244
184
  });
185
+ return;
245
186
  }
246
- // If there's existing cache, keep it as-is (don't update timestamp or persist)
187
+ // No response - fallback to previous state, or initialise empty
188
+ if (!tokensFromAPI) {
189
+ this.update((state) => {
190
+ var _a;
191
+ const newDataCache = { data: {}, timestamp: Date.now() };
192
+ (_a = state.tokensChainsCache)[chainId] ?? (_a[chainId] = newDataCache);
193
+ state.tokensChainsCache[chainId].timestamp = Date.now();
194
+ });
195
+ }
196
+ }
197
+ finally {
198
+ releaseLock();
247
199
  }
248
200
  }
249
201
  isCacheValid(chainId) {
250
202
  const { tokensChainsCache } = this.state;
251
203
  const timestamp = tokensChainsCache[chainId]?.timestamp;
252
204
  const now = Date.now();
253
- return (timestamp !== undefined && now - timestamp < __classPrivateFieldGet(this, _TokenListController_cacheRefreshThreshold, "f"));
205
+ return (timestamp !== undefined && now - timestamp < this.cacheRefreshThreshold);
254
206
  }
255
207
  /**
256
208
  * Clearing tokenList and tokensChainsCache explicitly.
257
- * This clears both state and all per-chain files in StorageService.
258
- *
259
- * Uses Promise.allSettled to handle partial failures gracefully.
260
- * After all removal attempts complete, state is updated to match storage:
261
- * - Successfully removed chains are cleared from state
262
- * - Failed removals are kept in state to maintain consistency with storage
263
- *
264
- * Note: This method explicitly deletes from storage rather than relying on the
265
- * stateChange subscription, since the subscription handles saves, not deletes.
266
209
  */
267
- async clearingTokenListData() {
268
- if (__classPrivateFieldGet(this, _TokenListController_persistDebounceTimer, "f")) {
269
- clearTimeout(__classPrivateFieldGet(this, _TokenListController_persistDebounceTimer, "f"));
270
- __classPrivateFieldSet(this, _TokenListController_persistDebounceTimer, undefined, "f");
271
- }
272
- __classPrivateFieldGet(this, _TokenListController_changedChainsToPersist, "f").clear();
273
- __classPrivateFieldGet(this, _TokenListController_chainsLoadedFromStorage, "f").clear();
274
- __classPrivateFieldSet(this, _TokenListController_previousTokensChainsCache, {}, "f");
275
- // Wait for any in-flight persist operation to complete before clearing storage.
276
- // This prevents race conditions where persist setItem calls interleave with
277
- // our removeItem calls, potentially re-saving data after we remove it.
278
- if (__classPrivateFieldGet(this, _TokenListController_persistInFlightPromise, "f")) {
279
- try {
280
- await __classPrivateFieldGet(this, _TokenListController_persistInFlightPromise, "f");
281
- }
282
- catch {
283
- // Ignore
284
- }
285
- }
286
- try {
287
- const allKeys = await this.messenger.call('StorageService:getAllKeys', name);
288
- // Filter and remove all tokensChainsCache keys
289
- const cacheKeys = allKeys.filter((key) => key.startsWith(`${__classPrivateFieldGet(_a, _a, "f", _TokenListController_storageKeyPrefix)}:`));
290
- if (cacheKeys.length === 0) {
291
- // No storage keys to remove, just clear state
292
- this.update((state) => {
293
- state.tokensChainsCache = {};
294
- });
295
- return;
296
- }
297
- // Use Promise.allSettled to handle partial failures gracefully.
298
- // This ensures all removals are attempted and we can track which succeeded.
299
- const results = await Promise.allSettled(cacheKeys.map((key) => this.messenger.call('StorageService:removeItem', name, key)));
300
- // Identify which chains failed to be removed from storage
301
- const failedChainIds = new Set();
302
- results.forEach((result, index) => {
303
- if (result.status === 'rejected') {
304
- const key = cacheKeys[index];
305
- const chainId = key.split(':')[1];
306
- failedChainIds.add(chainId);
307
- console.error(`TokenListController: Failed to remove cache for chain ${chainId}:`, result.reason);
308
- }
309
- });
310
- // Update state to match storage: keep only chains that failed to be removed
311
- this.update((state) => {
312
- if (failedChainIds.size === 0) {
313
- state.tokensChainsCache = {};
314
- }
315
- else {
316
- // Keep only chains that failed to be removed from storage
317
- const preservedCache = {};
318
- for (const chainId of failedChainIds) {
319
- if (state.tokensChainsCache[chainId]) {
320
- preservedCache[chainId] = state.tokensChainsCache[chainId];
321
- }
322
- }
323
- state.tokensChainsCache = preservedCache;
324
- }
325
- });
326
- }
327
- catch (error) {
328
- console.error('TokenListController: Failed to clear cache from storage:', error);
329
- // Still clear state even if storage access fails
330
- this.update((state) => {
331
- state.tokensChainsCache = {};
332
- });
333
- }
210
+ clearingTokenListData() {
211
+ this.update(() => {
212
+ return {
213
+ ...this.state,
214
+ tokensChainsCache: {},
215
+ };
216
+ });
334
217
  }
335
218
  /**
336
219
  * Updates preventPollingOnNetworkRestart from extension.
@@ -347,154 +230,7 @@ class TokenListController extends (0, polling_controller_1.StaticIntervalPolling
347
230
  }
348
231
  }
349
232
  exports.TokenListController = TokenListController;
350
- _a = TokenListController, _TokenListController_initializationPromise = new WeakMap(), _TokenListController_persistDebounceTimer = new WeakMap(), _TokenListController_persistInFlightPromise = new WeakMap(), _TokenListController_changedChainsToPersist = new WeakMap(), _TokenListController_chainsLoadedFromStorage = new WeakMap(), _TokenListController_previousTokensChainsCache = new WeakMap(), _TokenListController_intervalId = new WeakMap(), _TokenListController_intervalDelay = new WeakMap(), _TokenListController_cacheRefreshThreshold = new WeakMap(), _TokenListController_chainId = new WeakMap(), _TokenListController_abortController = new WeakMap(), _TokenListController_instances = new WeakSet(), _TokenListController_getChainStorageKey = function _TokenListController_getChainStorageKey(chainId) {
351
- return `${__classPrivateFieldGet(_a, _a, "f", _TokenListController_storageKeyPrefix)}:${chainId}`;
352
- }, _TokenListController_onCacheChanged = function _TokenListController_onCacheChanged(newCache) {
353
- // Detect which chains changed by comparing with previous cache
354
- for (const chainId of Object.keys(newCache)) {
355
- const newData = newCache[chainId];
356
- const prevData = __classPrivateFieldGet(this, _TokenListController_previousTokensChainsCache, "f")[chainId];
357
- // Chain is new or timestamp changed (indicating data update)
358
- if (!prevData || prevData.timestamp !== newData.timestamp) {
359
- // Skip persistence for chains that were just loaded from storage
360
- // (they don't need to be written back immediately)
361
- if (__classPrivateFieldGet(this, _TokenListController_chainsLoadedFromStorage, "f").has(chainId)) {
362
- __classPrivateFieldGet(this, _TokenListController_chainsLoadedFromStorage, "f").delete(chainId); // Clean up - future updates should persist
363
- }
364
- else {
365
- __classPrivateFieldGet(this, _TokenListController_changedChainsToPersist, "f").add(chainId);
366
- }
367
- }
368
- }
369
- // Update previous cache reference
370
- __classPrivateFieldSet(this, _TokenListController_previousTokensChainsCache, { ...newCache }, "f");
371
- // Schedule persistence if there are changes
372
- if (__classPrivateFieldGet(this, _TokenListController_changedChainsToPersist, "f").size > 0) {
373
- __classPrivateFieldGet(this, _TokenListController_instances, "m", _TokenListController_debouncePersist).call(this);
374
- }
375
- }, _TokenListController_debouncePersist = function _TokenListController_debouncePersist() {
376
- if (__classPrivateFieldGet(this, _TokenListController_persistDebounceTimer, "f")) {
377
- clearTimeout(__classPrivateFieldGet(this, _TokenListController_persistDebounceTimer, "f"));
378
- }
379
- __classPrivateFieldSet(this, _TokenListController_persistDebounceTimer, setTimeout(() => {
380
- // Note: #persistChangedChains handles errors internally via #saveChainCacheToStorage,
381
- // so this promise will not reject.
382
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
383
- __classPrivateFieldGet(this, _TokenListController_instances, "m", _TokenListController_persistChangedChains).call(this);
384
- }, __classPrivateFieldGet(_a, _a, "f", _TokenListController_persistDebounceMs)), "f");
385
- }, _TokenListController_persistChangedChains =
386
- /**
387
- * Persist only the chains that have changed to storage.
388
- * Reduces write amplification by skipping unchanged chains.
389
- *
390
- * Tracks the in-flight operation via #persistInFlightPromise so that
391
- * clearingTokenListData() can wait for it to complete before removing
392
- * items from storage, preventing race conditions.
393
- *
394
- * @returns A promise that resolves when changed chains are persisted.
395
- */
396
- async function _TokenListController_persistChangedChains() {
397
- const chainsToPersist = [...__classPrivateFieldGet(this, _TokenListController_changedChainsToPersist, "f")];
398
- __classPrivateFieldGet(this, _TokenListController_changedChainsToPersist, "f").clear();
399
- if (chainsToPersist.length === 0) {
400
- return;
401
- }
402
- __classPrivateFieldSet(this, _TokenListController_persistInFlightPromise, Promise.all(chainsToPersist.map((chainId) => __classPrivateFieldGet(this, _TokenListController_instances, "m", _TokenListController_saveChainCacheToStorage).call(this, chainId))).then(() => undefined), "f"); // Convert Promise<void[]> to Promise<void>
403
- try {
404
- await __classPrivateFieldGet(this, _TokenListController_persistInFlightPromise, "f");
405
- }
406
- finally {
407
- __classPrivateFieldSet(this, _TokenListController_persistInFlightPromise, undefined, "f");
408
- }
409
- }, _TokenListController_loadCacheFromStorage =
410
- /**
411
- * Load tokensChainsCache from StorageService into state.
412
- * Loads all cached chains from separate per-chain files in parallel.
413
- * Called during initialization to restore cached data.
414
- *
415
- * Note: This method merges loaded data with existing state to avoid
416
- * overwriting any fresh data that may have been fetched concurrently.
417
- * Caller must hold the mutex.
418
- *
419
- * @returns A promise that resolves when loading is complete.
420
- */
421
- async function _TokenListController_loadCacheFromStorage() {
422
- try {
423
- const allKeys = await this.messenger.call('StorageService:getAllKeys', name);
424
- // Filter keys that belong to tokensChainsCache (per-chain files)
425
- const cacheKeys = allKeys.filter((key) => key.startsWith(`${__classPrivateFieldGet(_a, _a, "f", _TokenListController_storageKeyPrefix)}:`));
426
- if (cacheKeys.length === 0) {
427
- return;
428
- }
429
- // Load all chains in parallel
430
- const chainCaches = await Promise.all(cacheKeys.map(async (key) => {
431
- // Extract chainId from key: 'tokensChainsCache:0x1' → '0x1'
432
- const chainId = key.split(':')[1];
433
- const { result, error } = await this.messenger.call('StorageService:getItem', name, key);
434
- if (error) {
435
- console.error(`TokenListController: Error loading cache for ${chainId}:`, error);
436
- return null;
437
- }
438
- return result ? { chainId, data: result } : null;
439
- }));
440
- // Build complete cache from loaded chains
441
- const loadedCache = {};
442
- chainCaches.forEach((chainCache) => {
443
- if (chainCache) {
444
- loadedCache[chainCache.chainId] = chainCache.data;
445
- }
446
- });
447
- // Merge loaded cache with existing state, preferring existing data
448
- // (which may be fresher if fetched during initialization)
449
- if (Object.keys(loadedCache).length > 0) {
450
- // Track which chains we're actually loading from storage
451
- // These will be skipped in the next #onCacheChanged to avoid redundant writes
452
- for (const chainId of Object.keys(loadedCache)) {
453
- if (!this.state.tokensChainsCache[chainId]) {
454
- __classPrivateFieldGet(this, _TokenListController_chainsLoadedFromStorage, "f").add(chainId);
455
- }
456
- }
457
- this.update((state) => {
458
- // Only load chains that don't already exist in state
459
- // This prevents overwriting fresh API data with stale cached data
460
- for (const [chainId, cacheData] of Object.entries(loadedCache)) {
461
- if (!state.tokensChainsCache[chainId]) {
462
- state.tokensChainsCache[chainId] = cacheData;
463
- }
464
- }
465
- });
466
- // Note: The update() call above triggers #onCacheChanged. Chains that were
467
- // just loaded from storage are tracked in #chainsLoadedFromStorage and will
468
- // be skipped from persistence (since they're already in storage).
469
- // Chains from initial state that were NOT overwritten will still be persisted
470
- // correctly, as they're not in #chainsLoadedFromStorage.
471
- }
472
- }
473
- catch (error) {
474
- console.error('TokenListController: Failed to load cache from storage:', error);
475
- }
476
- }, _TokenListController_saveChainCacheToStorage =
477
- /**
478
- * Save a specific chain's cache to StorageService.
479
- * This persists only the updated chain's data, reducing write amplification.
480
- *
481
- * @param chainId - The chain ID to save.
482
- * @returns A promise that resolves when saving is complete.
483
- */
484
- async function _TokenListController_saveChainCacheToStorage(chainId) {
485
- try {
486
- const chainData = this.state.tokensChainsCache[chainId];
487
- if (!chainData) {
488
- console.warn(`TokenListController: No cache data for chain ${chainId}`);
489
- return;
490
- }
491
- const storageKey = __classPrivateFieldGet(_a, _a, "m", _TokenListController_getChainStorageKey).call(_a, chainId);
492
- await this.messenger.call('StorageService:setItem', name, storageKey, chainData);
493
- }
494
- catch (error) {
495
- console.error(`TokenListController: Failed to save cache for ${chainId}:`, error);
496
- }
497
- }, _TokenListController_onNetworkControllerStateChange =
233
+ _TokenListController_instances = new WeakSet(), _TokenListController_onNetworkControllerStateChange =
498
234
  /**
499
235
  * Updates state and restarts polling on changes to the network controller
500
236
  * state.
@@ -504,19 +240,14 @@ async function _TokenListController_saveChainCacheToStorage(chainId) {
504
240
  async function _TokenListController_onNetworkControllerStateChange(networkControllerState) {
505
241
  const selectedNetworkClient = this.messenger.call('NetworkController:getNetworkClientById', networkControllerState.selectedNetworkClientId);
506
242
  const { chainId } = selectedNetworkClient.configuration;
507
- if (__classPrivateFieldGet(this, _TokenListController_chainId, "f") !== chainId) {
508
- __classPrivateFieldGet(this, _TokenListController_abortController, "f").abort();
509
- __classPrivateFieldSet(this, _TokenListController_abortController, new AbortController(), "f");
510
- __classPrivateFieldSet(this, _TokenListController_chainId, chainId, "f");
243
+ if (this.chainId !== chainId) {
244
+ this.abortController.abort();
245
+ this.abortController = new AbortController();
246
+ this.chainId = chainId;
511
247
  if (this.state.preventPollingOnNetworkRestart) {
512
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
513
248
  this.clearingTokenListData();
514
249
  }
515
250
  }
516
- }, _TokenListController_stopPolling = function _TokenListController_stopPolling() {
517
- if (__classPrivateFieldGet(this, _TokenListController_intervalId, "f")) {
518
- clearInterval(__classPrivateFieldGet(this, _TokenListController_intervalId, "f"));
519
- }
520
251
  }, _TokenListController_startDeprecatedPolling =
521
252
  /**
522
253
  * Starts a new polling interval for a given chainId (this should be deprecated in favor of _executePoll)
@@ -526,18 +257,12 @@ async function _TokenListController_onNetworkControllerStateChange(networkContro
526
257
  */
527
258
  async function _TokenListController_startDeprecatedPolling() {
528
259
  // renaming this to avoid collision with base class
529
- await (0, controller_utils_1.safelyExecute)(() => this.fetchTokenList(__classPrivateFieldGet(this, _TokenListController_chainId, "f")));
260
+ await (0, controller_utils_1.safelyExecute)(() => this.fetchTokenList(this.chainId));
530
261
  // TODO: Either fix this lint violation or explain why it's necessary to ignore.
531
262
  // eslint-disable-next-line @typescript-eslint/no-misused-promises
532
- __classPrivateFieldSet(this, _TokenListController_intervalId, setInterval(async () => {
533
- await (0, controller_utils_1.safelyExecute)(() => this.fetchTokenList(__classPrivateFieldGet(this, _TokenListController_chainId, "f")));
534
- }, __classPrivateFieldGet(this, _TokenListController_intervalDelay, "f")), "f");
263
+ this.intervalId = setInterval(async () => {
264
+ await (0, controller_utils_1.safelyExecute)(() => this.fetchTokenList(this.chainId));
265
+ }, this.intervalDelay);
535
266
  };
536
- /**
537
- * Debounce delay for persisting state changes (in milliseconds).
538
- */
539
- _TokenListController_persistDebounceMs = { value: 500 };
540
- // Storage key prefix for per-chain files
541
- _TokenListController_storageKeyPrefix = { value: 'tokensChainsCache' };
542
267
  exports.default = TokenListController;
543
268
  //# sourceMappingURL=TokenListController.cjs.map