@metamask-previews/assets-controllers 95.1.0-preview-009e026d → 95.1.0-preview-f8cdcfc2

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