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