@metamask/assets-controllers 108.3.0 → 108.5.0
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 +25 -1
- package/dist/TokenListController.cjs +84 -4
- package/dist/TokenListController.cjs.map +1 -1
- package/dist/TokenListController.d.cts +6 -1
- package/dist/TokenListController.d.cts.map +1 -1
- package/dist/TokenListController.d.mts +6 -1
- package/dist/TokenListController.d.mts.map +1 -1
- package/dist/TokenListController.mjs +84 -4
- package/dist/TokenListController.mjs.map +1 -1
- package/dist/multicall.cjs +2 -0
- package/dist/multicall.cjs.map +1 -1
- package/dist/multicall.d.cts.map +1 -1
- package/dist/multicall.d.mts.map +1 -1
- package/dist/multicall.mjs +2 -0
- package/dist/multicall.mjs.map +1 -1
- package/dist/token-prices-service/codefi-v2.cjs +1 -0
- package/dist/token-prices-service/codefi-v2.cjs.map +1 -1
- package/dist/token-prices-service/codefi-v2.d.cts +2 -1
- package/dist/token-prices-service/codefi-v2.d.cts.map +1 -1
- package/dist/token-prices-service/codefi-v2.d.mts +2 -1
- package/dist/token-prices-service/codefi-v2.d.mts.map +1 -1
- package/dist/token-prices-service/codefi-v2.mjs +1 -0
- package/dist/token-prices-service/codefi-v2.mjs.map +1 -1
- package/package.json +6 -6
|
@@ -9,7 +9,7 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
|
|
|
9
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
10
|
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
11
11
|
};
|
|
12
|
-
var _TokenListController_instances, _a, _TokenListController_persistDebounceTimer, _TokenListController_initializePromise, _TokenListController_persistInFlightPromise, _TokenListController_changedChainsToPersist, _TokenListController_previousTokensChainsCache, _TokenListController_persistDebounceMs, _TokenListController_storageKeyPrefix, _TokenListController_getChainStorageKey, _TokenListController_intervalId, _TokenListController_intervalDelay, _TokenListController_cacheRefreshThreshold, _TokenListController_chainId, _TokenListController_abortController, _TokenListController_waitForInitialization, _TokenListController_onCacheChanged, _TokenListController_debouncePersist, _TokenListController_persistChangedChains, _TokenListController_synchronizeCacheWithStorage, _TokenListController_saveChainCacheToStorage, _TokenListController_onNetworkControllerStateChange, _TokenListController_stopPolling, _TokenListController_startDeprecatedPolling;
|
|
12
|
+
var _TokenListController_instances, _a, _TokenListController_persistDebounceTimer, _TokenListController_initializePromise, _TokenListController_persistInFlightPromise, _TokenListController_changedChainsToPersist, _TokenListController_previousTokensChainsCache, _TokenListController_persistDebounceMs, _TokenListController_storageKeyPrefix, _TokenListController_getChainStorageKey, _TokenListController_intervalId, _TokenListController_intervalDelay, _TokenListController_cacheRefreshThreshold, _TokenListController_chainId, _TokenListController_abortController, _TokenListController_isDeprecated, _TokenListController_waitForInitialization, _TokenListController_onCacheChanged, _TokenListController_debouncePersist, _TokenListController_persistChangedChains, _TokenListController_synchronizeCacheWithStorage, _TokenListController_saveChainCacheToStorage, _TokenListController_onNetworkControllerStateChange, _TokenListController_stopPolling, _TokenListController_startDeprecatedPolling, _TokenListController_enforceDisabledState, _TokenListController_resetCacheState, _TokenListController_clearPersistedCache;
|
|
13
13
|
import { safelyExecute } from "@metamask/controller-utils";
|
|
14
14
|
import { StaticIntervalPollingController } from "@metamask/polling-controller";
|
|
15
15
|
import { isTokenListSupportedForNetwork, formatAggregatorNames, formatIconUrlWithProxy } from "./assetsUtil.mjs";
|
|
@@ -43,10 +43,14 @@ export class TokenListController extends StaticIntervalPollingController() {
|
|
|
43
43
|
* @param options.onNetworkStateChange - A function for registering an event handler for network state changes.
|
|
44
44
|
* @param options.interval - The polling interval, in milliseconds.
|
|
45
45
|
* @param options.cacheRefreshThreshold - The token cache expiry time, in milliseconds.
|
|
46
|
+
* @param options.isDeprecated - Optional function returning whether the controller should be disabled.
|
|
47
|
+
* When it returns `true`, `tokensChainsCache` is reset to `{}` at construction and at every
|
|
48
|
+
* polling entry point, no fetches are issued, and persisted storage is cleared on initialize.
|
|
49
|
+
* The function is evaluated dynamically on each entry point so it can be toggled at runtime.
|
|
46
50
|
* @param options.messenger - A restricted messenger.
|
|
47
51
|
* @param options.state - Initial state to set on this controller.
|
|
48
52
|
*/
|
|
49
|
-
constructor({ chainId, onNetworkStateChange, interval = DEFAULT_INTERVAL, cacheRefreshThreshold = DEFAULT_THRESHOLD, messenger, state, }) {
|
|
53
|
+
constructor({ chainId, onNetworkStateChange, interval = DEFAULT_INTERVAL, cacheRefreshThreshold = DEFAULT_THRESHOLD, isDeprecated = () => false, messenger, state, }) {
|
|
50
54
|
super({
|
|
51
55
|
name,
|
|
52
56
|
metadata,
|
|
@@ -81,11 +85,19 @@ export class TokenListController extends StaticIntervalPollingController() {
|
|
|
81
85
|
_TokenListController_cacheRefreshThreshold.set(this, void 0);
|
|
82
86
|
_TokenListController_chainId.set(this, void 0);
|
|
83
87
|
_TokenListController_abortController.set(this, void 0);
|
|
88
|
+
_TokenListController_isDeprecated.set(this, void 0);
|
|
84
89
|
__classPrivateFieldSet(this, _TokenListController_intervalDelay, interval, "f");
|
|
85
90
|
this.setIntervalLength(interval);
|
|
86
91
|
__classPrivateFieldSet(this, _TokenListController_cacheRefreshThreshold, cacheRefreshThreshold, "f");
|
|
87
92
|
__classPrivateFieldSet(this, _TokenListController_chainId, chainId, "f");
|
|
88
93
|
__classPrivateFieldSet(this, _TokenListController_abortController, new AbortController(), "f");
|
|
94
|
+
__classPrivateFieldSet(this, _TokenListController_isDeprecated, isDeprecated, "f");
|
|
95
|
+
if (__classPrivateFieldGet(this, _TokenListController_isDeprecated, "f").call(this)) {
|
|
96
|
+
// Fire-and-forget: storage clearing is async but the constructor is sync.
|
|
97
|
+
// Errors are caught inside `#enforceDisabledState`.
|
|
98
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
99
|
+
__classPrivateFieldGet(this, _TokenListController_instances, "m", _TokenListController_enforceDisabledState).call(this);
|
|
100
|
+
}
|
|
89
101
|
if (onNetworkStateChange) {
|
|
90
102
|
// TODO: Either fix this lint violation or explain why it's necessary to ignore.
|
|
91
103
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
@@ -115,6 +127,10 @@ export class TokenListController extends StaticIntervalPollingController() {
|
|
|
115
127
|
}
|
|
116
128
|
const executeInit = async () => {
|
|
117
129
|
try {
|
|
130
|
+
if (__classPrivateFieldGet(this, _TokenListController_isDeprecated, "f").call(this)) {
|
|
131
|
+
await __classPrivateFieldGet(this, _TokenListController_instances, "m", _TokenListController_enforceDisabledState).call(this);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
118
134
|
await __classPrivateFieldGet(this, _TokenListController_instances, "m", _TokenListController_synchronizeCacheWithStorage).call(this);
|
|
119
135
|
// Subscribe to state changes to automatically persist tokensChainsCache
|
|
120
136
|
this.messenger.subscribe('TokenListController:stateChange', (newCache) => __classPrivateFieldGet(this, _TokenListController_instances, "m", _TokenListController_onCacheChanged).call(this, newCache), (controllerState) => controllerState.tokensChainsCache);
|
|
@@ -138,6 +154,10 @@ export class TokenListController extends StaticIntervalPollingController() {
|
|
|
138
154
|
* Consider using the new polling approach instead
|
|
139
155
|
*/
|
|
140
156
|
async start() {
|
|
157
|
+
if (__classPrivateFieldGet(this, _TokenListController_isDeprecated, "f").call(this)) {
|
|
158
|
+
await __classPrivateFieldGet(this, _TokenListController_instances, "m", _TokenListController_enforceDisabledState).call(this);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
141
161
|
if (!isTokenListSupportedForNetwork(__classPrivateFieldGet(this, _TokenListController_chainId, "f"))) {
|
|
142
162
|
return;
|
|
143
163
|
}
|
|
@@ -151,6 +171,10 @@ export class TokenListController extends StaticIntervalPollingController() {
|
|
|
151
171
|
*/
|
|
152
172
|
async restart() {
|
|
153
173
|
__classPrivateFieldGet(this, _TokenListController_instances, "m", _TokenListController_stopPolling).call(this);
|
|
174
|
+
if (__classPrivateFieldGet(this, _TokenListController_isDeprecated, "f").call(this)) {
|
|
175
|
+
await __classPrivateFieldGet(this, _TokenListController_instances, "m", _TokenListController_enforceDisabledState).call(this);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
154
178
|
await __classPrivateFieldGet(this, _TokenListController_instances, "m", _TokenListController_startDeprecatedPolling).call(this);
|
|
155
179
|
}
|
|
156
180
|
/**
|
|
@@ -186,8 +210,12 @@ export class TokenListController extends StaticIntervalPollingController() {
|
|
|
186
210
|
* @returns A promise that resolves when this operation completes.
|
|
187
211
|
*/
|
|
188
212
|
async _executePoll({ chainId }) {
|
|
213
|
+
if (__classPrivateFieldGet(this, _TokenListController_isDeprecated, "f").call(this)) {
|
|
214
|
+
await __classPrivateFieldGet(this, _TokenListController_instances, "m", _TokenListController_enforceDisabledState).call(this);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
189
217
|
await __classPrivateFieldGet(this, _TokenListController_instances, "m", _TokenListController_waitForInitialization).call(this);
|
|
190
|
-
|
|
218
|
+
await this.fetchTokenList(chainId);
|
|
191
219
|
}
|
|
192
220
|
/**
|
|
193
221
|
* Fetching token list from the Token Service API. This will fetch tokens across chains.
|
|
@@ -196,6 +224,10 @@ export class TokenListController extends StaticIntervalPollingController() {
|
|
|
196
224
|
* @param chainId - The chainId of the current chain triggering the fetch.
|
|
197
225
|
*/
|
|
198
226
|
async fetchTokenList(chainId) {
|
|
227
|
+
if (__classPrivateFieldGet(this, _TokenListController_isDeprecated, "f").call(this)) {
|
|
228
|
+
await __classPrivateFieldGet(this, _TokenListController_instances, "m", _TokenListController_enforceDisabledState).call(this);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
199
231
|
if (this.isCacheValid(chainId)) {
|
|
200
232
|
return;
|
|
201
233
|
}
|
|
@@ -248,7 +280,7 @@ export class TokenListController extends StaticIntervalPollingController() {
|
|
|
248
280
|
return (timestamp !== undefined && now - timestamp < __classPrivateFieldGet(this, _TokenListController_cacheRefreshThreshold, "f"));
|
|
249
281
|
}
|
|
250
282
|
}
|
|
251
|
-
_a = TokenListController, _TokenListController_persistDebounceTimer = new WeakMap(), _TokenListController_initializePromise = new WeakMap(), _TokenListController_persistInFlightPromise = new WeakMap(), _TokenListController_changedChainsToPersist = 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) {
|
|
283
|
+
_a = TokenListController, _TokenListController_persistDebounceTimer = new WeakMap(), _TokenListController_initializePromise = new WeakMap(), _TokenListController_persistInFlightPromise = new WeakMap(), _TokenListController_changedChainsToPersist = 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_isDeprecated = new WeakMap(), _TokenListController_instances = new WeakSet(), _TokenListController_getChainStorageKey = function _TokenListController_getChainStorageKey(chainId) {
|
|
252
284
|
return `${__classPrivateFieldGet(_a, _a, "f", _TokenListController_storageKeyPrefix)}:${chainId}`;
|
|
253
285
|
}, _TokenListController_waitForInitialization =
|
|
254
286
|
/**
|
|
@@ -441,6 +473,54 @@ async function _TokenListController_startDeprecatedPolling() {
|
|
|
441
473
|
__classPrivateFieldSet(this, _TokenListController_intervalId, setInterval(async () => {
|
|
442
474
|
await safelyExecute(() => this.fetchTokenList(__classPrivateFieldGet(this, _TokenListController_chainId, "f")));
|
|
443
475
|
}, __classPrivateFieldGet(this, _TokenListController_intervalDelay, "f")), "f");
|
|
476
|
+
}, _TokenListController_enforceDisabledState =
|
|
477
|
+
/**
|
|
478
|
+
* Fully enforce the disabled state in a single step:
|
|
479
|
+
* 1. Drop in-memory `tokensChainsCache` to `{}`.
|
|
480
|
+
* 2. Cancel any pending debounced persist and clear the changed-chains set so
|
|
481
|
+
* a stale entry can't write old data after the in-memory reset.
|
|
482
|
+
* 3. Overwrite every persisted `tokensChainsCache:*` entry in StorageService
|
|
483
|
+
* with `{ data: {}, timestamp: 0 }`.
|
|
484
|
+
*
|
|
485
|
+
* Called from every fetching entry point when `isDeprecated()` is true so that
|
|
486
|
+
* a runtime toggle propagates to both memory and storage immediately, even
|
|
487
|
+
* if `initialize()` was originally invoked while the controller was enabled.
|
|
488
|
+
*
|
|
489
|
+
* @returns A promise that resolves when persisted entries have been cleared.
|
|
490
|
+
*/
|
|
491
|
+
async function _TokenListController_enforceDisabledState() {
|
|
492
|
+
__classPrivateFieldGet(this, _TokenListController_instances, "m", _TokenListController_resetCacheState).call(this);
|
|
493
|
+
if (__classPrivateFieldGet(this, _TokenListController_persistDebounceTimer, "f")) {
|
|
494
|
+
clearTimeout(__classPrivateFieldGet(this, _TokenListController_persistDebounceTimer, "f"));
|
|
495
|
+
__classPrivateFieldSet(this, _TokenListController_persistDebounceTimer, undefined, "f");
|
|
496
|
+
}
|
|
497
|
+
__classPrivateFieldGet(this, _TokenListController_changedChainsToPersist, "f").clear();
|
|
498
|
+
await __classPrivateFieldGet(this, _TokenListController_instances, "m", _TokenListController_clearPersistedCache).call(this);
|
|
499
|
+
}, _TokenListController_resetCacheState = function _TokenListController_resetCacheState() {
|
|
500
|
+
if (Object.keys(this.state.tokensChainsCache).length === 0) {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
this.update((state) => {
|
|
504
|
+
state.tokensChainsCache = {};
|
|
505
|
+
});
|
|
506
|
+
}, _TokenListController_clearPersistedCache =
|
|
507
|
+
/**
|
|
508
|
+
* Overwrite every persisted `tokensChainsCache:*` entry in StorageService
|
|
509
|
+
* with `{ data: {}, timestamp: 0 }` so the on-disk view reflects the
|
|
510
|
+
* disabled state.
|
|
511
|
+
*
|
|
512
|
+
* @returns A promise that resolves when persisted entries have been cleared.
|
|
513
|
+
*/
|
|
514
|
+
async function _TokenListController_clearPersistedCache() {
|
|
515
|
+
try {
|
|
516
|
+
const allKeys = await this.messenger.call('StorageService:getAllKeys', name);
|
|
517
|
+
const cacheKeys = allKeys.filter((key) => key.startsWith(`${__classPrivateFieldGet(_a, _a, "f", _TokenListController_storageKeyPrefix)}:`));
|
|
518
|
+
const emptyDataCache = { data: {}, timestamp: 0 };
|
|
519
|
+
await Promise.all(cacheKeys.map((key) => this.messenger.call('StorageService:setItem', name, key, emptyDataCache)));
|
|
520
|
+
}
|
|
521
|
+
catch (error) {
|
|
522
|
+
console.error('TokenListController: Failed to clear persisted cache:', error);
|
|
523
|
+
}
|
|
444
524
|
};
|
|
445
525
|
/**
|
|
446
526
|
* Debounce delay for persisting state changes (in milliseconds).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TokenListController.mjs","sourceRoot":"","sources":["../src/TokenListController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport { safelyExecute } from '@metamask/controller-utils';\nimport type { Messenger } from '@metamask/messenger';\nimport type {\n NetworkControllerStateChangeEvent,\n NetworkState,\n NetworkControllerGetNetworkClientByIdAction,\n} from '@metamask/network-controller';\nimport { StaticIntervalPollingController } from '@metamask/polling-controller';\nimport type {\n StorageServiceSetItemAction,\n StorageServiceGetItemAction,\n StorageServiceGetAllKeysAction,\n} from '@metamask/storage-service';\nimport type { Hex } from '@metamask/utils';\n\nimport {\n isTokenListSupportedForNetwork,\n formatAggregatorNames,\n formatIconUrlWithProxy,\n} from './assetsUtil';\nimport { TokenRwaData, fetchTokenListByChainId } from './token-service';\n\n// 4 Hour Interval Cache Refresh Threshold\nconst DEFAULT_INTERVAL = 4 * 60 * 60 * 1000;\nconst DEFAULT_THRESHOLD = 4 * 60 * 60 * 1000;\n\nconst name = 'TokenListController';\n\nexport type TokenListToken = {\n name: string;\n symbol: string;\n decimals: number;\n address: string;\n occurrences: number;\n aggregators: string[];\n iconUrl: string;\n rwaData?: TokenRwaData;\n};\n\nexport type TokenListMap = Record<string, TokenListToken>;\n\nexport type DataCache = {\n timestamp: number;\n data: TokenListMap;\n};\nexport type TokensChainsCache = {\n [chainId: Hex]: DataCache;\n};\n\nexport type TokenListState = {\n tokensChainsCache: TokensChainsCache;\n};\n\nexport type TokenListStateChange = ControllerStateChangeEvent<\n typeof name,\n TokenListState\n>;\n\nexport type TokenListControllerEvents = TokenListStateChange;\n\nexport type GetTokenListState = ControllerGetStateAction<\n typeof name,\n TokenListState\n>;\n\nexport type TokenListControllerActions = GetTokenListState;\n\ntype AllowedActions =\n | NetworkControllerGetNetworkClientByIdAction\n | StorageServiceSetItemAction\n | StorageServiceGetItemAction\n | StorageServiceGetAllKeysAction;\n\ntype AllowedEvents = NetworkControllerStateChangeEvent;\n\nexport type TokenListControllerMessenger = Messenger<\n typeof name,\n TokenListControllerActions | AllowedActions,\n TokenListControllerEvents | AllowedEvents\n>;\n\nconst metadata: StateMetadata<TokenListState> = {\n tokensChainsCache: {\n includeInStateLogs: false,\n persist: false, // Persisted separately via StorageService\n includeInDebugSnapshot: true,\n usedInUi: true,\n },\n};\n\nexport const getDefaultTokenListState = (): TokenListState => {\n return {\n tokensChainsCache: {},\n };\n};\n\n/** The input to start polling for the {@link TokenListController} */\ntype TokenListPollingInput = {\n chainId: Hex;\n};\n\n/**\n * Controller that passively polls on a set interval for the list of tokens from metaswaps api\n */\nexport class TokenListController extends StaticIntervalPollingController<TokenListPollingInput>()<\n typeof name,\n TokenListState,\n TokenListControllerMessenger\n> {\n /**\n * Debounce timer for persisting state changes to storage.\n */\n #persistDebounceTimer?: ReturnType<typeof setTimeout>;\n\n /**\n * Promise for the in-flight initialization sequence.\n */\n #initializePromise?: Promise<void>;\n\n /**\n * Promise that resolves when the current persist operation completes.\n * Used to prevent race conditions between persist operations.\n */\n #persistInFlightPromise?: Promise<void>;\n\n /**\n * Tracks which chains have pending changes to persist.\n * Only changed chains are persisted to reduce write amplification.\n */\n readonly #changedChainsToPersist: Set<Hex> = new Set();\n\n /**\n * Previous tokensChainsCache for detecting which chains changed.\n */\n #previousTokensChainsCache: TokensChainsCache = {};\n\n /**\n * Debounce delay for persisting state changes (in milliseconds).\n */\n static readonly #persistDebounceMs = 500;\n\n // Storage key prefix for per-chain files\n static readonly #storageKeyPrefix = 'tokensChainsCache';\n\n /**\n * Get storage key for a specific chain.\n *\n * @param chainId - The chain ID.\n * @returns Storage key for the chain.\n */\n static #getChainStorageKey(chainId: Hex): string {\n return `${TokenListController.#storageKeyPrefix}:${chainId}`;\n }\n\n #intervalId?: ReturnType<typeof setTimeout>;\n\n readonly #intervalDelay: number;\n\n readonly #cacheRefreshThreshold: number;\n\n #chainId: Hex;\n\n #abortController: AbortController;\n\n /**\n * Creates a TokenListController instance.\n *\n * @param options - The controller options.\n * @param options.chainId - The chain ID of the current network.\n * @param options.onNetworkStateChange - A function for registering an event handler for network state changes.\n * @param options.interval - The polling interval, in milliseconds.\n * @param options.cacheRefreshThreshold - The token cache expiry time, in milliseconds.\n * @param options.messenger - A restricted messenger.\n * @param options.state - Initial state to set on this controller.\n */\n constructor({\n chainId,\n onNetworkStateChange,\n interval = DEFAULT_INTERVAL,\n cacheRefreshThreshold = DEFAULT_THRESHOLD,\n messenger,\n state,\n }: {\n chainId: Hex;\n onNetworkStateChange?: (\n listener: (networkState: NetworkState) => void,\n ) => void;\n interval?: number;\n cacheRefreshThreshold?: number;\n messenger: TokenListControllerMessenger;\n state?: Partial<TokenListState>;\n }) {\n super({\n name,\n metadata,\n messenger,\n state: { ...getDefaultTokenListState(), ...state },\n });\n\n this.#intervalDelay = interval;\n this.setIntervalLength(interval);\n this.#cacheRefreshThreshold = cacheRefreshThreshold;\n this.#chainId = chainId;\n this.#abortController = new AbortController();\n\n if (onNetworkStateChange) {\n // TODO: Either fix this lint violation or explain why it's necessary to ignore.\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n onNetworkStateChange(async (networkControllerState) => {\n await this.#onNetworkControllerStateChange(networkControllerState);\n });\n } else {\n this.messenger.subscribe(\n 'NetworkController:stateChange',\n // TODO: Either fix this lint violation or explain why it's necessary to ignore.\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n async (networkControllerState) => {\n await this.#onNetworkControllerStateChange(networkControllerState);\n },\n );\n }\n }\n\n /**\n * Initialize the controller by loading cache from storage and running migration.\n * This method should be called by clients after construction.\n *\n * @returns A promise that resolves when initialization is complete.\n */\n async initialize(): Promise<void> {\n if (this.#initializePromise) {\n await this.#initializePromise;\n return;\n }\n\n const executeInit = async (): Promise<void> => {\n try {\n await this.#synchronizeCacheWithStorage();\n\n // Subscribe to state changes to automatically persist tokensChainsCache\n this.messenger.subscribe(\n 'TokenListController:stateChange',\n (newCache: TokensChainsCache) => this.#onCacheChanged(newCache),\n (controllerState) => controllerState.tokensChainsCache,\n );\n } catch {\n // do nothing\n } finally {\n this.#initializePromise = undefined;\n }\n };\n\n this.#initializePromise = executeInit();\n\n await this.#initializePromise;\n }\n\n /**\n * Waits for any in-flight initialization to complete.\n * Polling should not run against partially initialized state.\n */\n async #waitForInitialization(): Promise<void> {\n try {\n await this.#initializePromise;\n } catch {\n // do nothing\n }\n }\n\n /**\n * Handle tokensChainsCache changes by detecting which chains changed\n * and scheduling debounced persistence.\n *\n * @param newCache - The new tokensChainsCache state.\n */\n #onCacheChanged(newCache: TokensChainsCache): void {\n // Detect which chains changed by comparing with previous cache\n for (const chainId of Object.keys(newCache) as Hex[]) {\n const newData = newCache[chainId];\n const prevData = this.#previousTokensChainsCache[chainId];\n\n // Chain is new or timestamp changed (indicating data update)\n if (prevData?.timestamp !== newData.timestamp) {\n this.#changedChainsToPersist.add(chainId);\n }\n }\n\n // Update previous cache reference\n this.#previousTokensChainsCache = { ...newCache };\n\n // Schedule persistence if there are changes\n if (this.#changedChainsToPersist.size > 0) {\n this.#debouncePersist();\n }\n }\n\n /**\n * Debounce persistence of changed chains to storage.\n */\n #debouncePersist(): void {\n if (this.#persistDebounceTimer) {\n clearTimeout(this.#persistDebounceTimer);\n }\n\n this.#persistDebounceTimer = setTimeout(() => {\n // Note: #persistChangedChains handles errors internally via #saveChainCacheToStorage,\n // so this promise will not reject.\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.#persistChangedChains();\n }, TokenListController.#persistDebounceMs);\n }\n\n /**\n * Persist only the chains that have changed to storage.\n * Reduces write amplification by skipping unchanged chains.\n *\n * If a persist operation is already in-flight, this method returns early\n * and reschedules the debounce to ensure accumulated changes are retried\n * after the current operation completes.\n *\n * @returns A promise that resolves when changed chains are persisted.\n */\n async #persistChangedChains(): Promise<void> {\n if (this.#persistInFlightPromise) {\n // Reschedule debounce to retry accumulated changes after in-flight persist completes\n if (this.#changedChainsToPersist.size > 0) {\n this.#debouncePersist();\n }\n return;\n }\n\n const chainsToPersist = [...this.#changedChainsToPersist];\n this.#changedChainsToPersist.clear();\n\n if (chainsToPersist.length === 0) {\n return;\n }\n\n this.#persistInFlightPromise = Promise.all(\n chainsToPersist.map((chainId) => this.#saveChainCacheToStorage(chainId)),\n ).then(() => undefined);\n\n try {\n await this.#persistInFlightPromise;\n } finally {\n this.#persistInFlightPromise = undefined;\n }\n }\n\n /**\n * Synchronize tokensChainsCache between state and storage bidirectionally.\n *\n * This method:\n * 1. Loads cached chains from storage (per-chain files) in parallel\n * 2. Merges loaded data into state (preferring existing state to avoid overwriting fresh data)\n * 3. Persists any chains that exist in state but not in storage\n *\n * Called during initialization to ensure state and storage are consistent.\n *\n * @returns A promise that resolves when synchronization is complete.\n */\n async #synchronizeCacheWithStorage(): Promise<void> {\n try {\n const allKeys = await this.messenger.call(\n 'StorageService:getAllKeys',\n name,\n );\n\n // Filter keys that belong to tokensChainsCache (per-chain files)\n const cacheKeys = allKeys.filter((key) =>\n key.startsWith(`${TokenListController.#storageKeyPrefix}:`),\n );\n\n // Load all chains in parallel\n const chainCaches = await Promise.all(\n cacheKeys.map(async (key) => {\n // Extract chainId from key: 'tokensChainsCache:0x1' → '0x1'\n const chainId = key.split(':')[1] as Hex;\n\n const { result, error } = await this.messenger.call(\n 'StorageService:getItem',\n name,\n key,\n );\n\n if (error) {\n console.error(\n `TokenListController: Error loading cache for ${chainId}:`,\n error,\n );\n return null;\n }\n\n return result ? { chainId, data: result as DataCache } : null;\n }),\n );\n\n // Build complete cache from loaded chains\n const loadedCache: TokensChainsCache = {};\n chainCaches.forEach((chainCache) => {\n if (chainCache) {\n loadedCache[chainCache.chainId] = chainCache.data;\n }\n });\n\n // Chains in state _before loading persisted state_, from a recent update\n const chainsInState = new Set(\n Object.keys(this.state.tokensChainsCache) as Hex[],\n );\n\n // Merge loaded cache with existing state, preferring existing data\n // (which may be fresher if fetched during initialization)\n if (Object.keys(loadedCache).length > 0) {\n this.update((state) => {\n // Only load chains that don't already exist in state\n // This prevents overwriting fresh API data with stale cached data\n for (const [chainId, cacheData] of Object.entries(loadedCache)) {\n if (!state.tokensChainsCache[chainId as Hex]) {\n state.tokensChainsCache[chainId as Hex] = cacheData;\n }\n }\n });\n }\n\n // Persist chains that exist in state but were not loaded from storage.\n // This handles the case where initial state contains chains that don't exist\n // in storage yet (e.g., fresh data from API). Without this, those chains\n // would be lost on the next app restart.\n for (const chainId of chainsInState) {\n this.#changedChainsToPersist.add(chainId);\n }\n\n // Persist any chains that need to be saved\n if (this.#changedChainsToPersist.size > 0) {\n this.#debouncePersist();\n }\n\n this.#previousTokensChainsCache = { ...this.state.tokensChainsCache };\n } catch (error) {\n console.error(\n 'TokenListController: Failed to load cache from storage:',\n error,\n );\n }\n }\n\n /**\n * Save a specific chain's cache to StorageService.\n * This persists only the updated chain's data, reducing write amplification.\n *\n * @param chainId - The chain ID to save.\n * @returns A promise that resolves when saving is complete.\n */\n async #saveChainCacheToStorage(chainId: Hex): Promise<void> {\n try {\n const chainData = this.state.tokensChainsCache[chainId];\n\n if (!chainData) {\n console.warn(`TokenListController: No cache data for chain ${chainId}`);\n return;\n }\n\n const storageKey = TokenListController.#getChainStorageKey(chainId);\n\n await this.messenger.call(\n 'StorageService:setItem',\n name,\n storageKey,\n chainData,\n );\n } catch (error) {\n console.error(\n `TokenListController: Failed to save cache for ${chainId}:`,\n error,\n );\n }\n }\n\n /**\n * Updates state and restarts polling on changes to the network controller\n * state.\n *\n * @param networkControllerState - The updated network controller state.\n */\n async #onNetworkControllerStateChange(\n networkControllerState: NetworkState,\n ): Promise<void> {\n const selectedNetworkClient = this.messenger.call(\n 'NetworkController:getNetworkClientById',\n networkControllerState.selectedNetworkClientId,\n );\n const { chainId } = selectedNetworkClient.configuration;\n\n if (this.#chainId !== chainId) {\n this.#abortController.abort();\n this.#abortController = new AbortController();\n this.#chainId = chainId;\n }\n }\n\n // Eventually we want to remove start/restart/stop controls in favor of new _executePoll API\n // Maintaining these functions for now until we can safely deprecate them for backwards compatibility\n /**\n * Start polling for the token list.\n *\n * @deprecated This method is deprecated and will be removed in the future.\n * Consider using the new polling approach instead\n */\n async start(): Promise<void> {\n if (!isTokenListSupportedForNetwork(this.#chainId)) {\n return;\n }\n await this.#startDeprecatedPolling();\n }\n\n /**\n * Restart polling for the token list.\n *\n * @deprecated This method is deprecated and will be removed in the future.\n * Consider using the new polling approach instead\n */\n async restart(): Promise<void> {\n this.#stopPolling();\n await this.#startDeprecatedPolling();\n }\n\n /**\n * Stop polling for the token list.\n *\n * @deprecated This method is deprecated and will be removed in the future.\n * Consider using the new polling approach instead\n */\n stop(): void {\n this.#stopPolling();\n }\n\n /**\n * This stops any active polling.\n *\n * @deprecated This method is deprecated and will be removed in the future.\n * Consider using the new polling approach instead\n */\n override destroy(): void {\n super.destroy();\n this.#stopPolling();\n\n // Cancel any pending debounced persistence operations\n if (this.#persistDebounceTimer) {\n clearTimeout(this.#persistDebounceTimer);\n this.#persistDebounceTimer = undefined;\n }\n this.#changedChainsToPersist.clear();\n }\n\n /**\n * This stops any active polling intervals.\n *\n * @deprecated This method is deprecated and will be removed in the future.\n * Consider using the new polling approach instead\n */\n #stopPolling(): void {\n if (this.#intervalId) {\n clearInterval(this.#intervalId);\n }\n }\n\n /**\n * Starts a new polling interval for a given chainId (this should be deprecated in favor of _executePoll)\n *\n * @deprecated This method is deprecated and will be removed in the future.\n * Consider using the new polling approach instead\n */\n async #startDeprecatedPolling(): Promise<void> {\n // renaming this to avoid collision with base class\n await safelyExecute(() => this.fetchTokenList(this.#chainId));\n // TODO: Either fix this lint violation or explain why it's necessary to ignore.\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n this.#intervalId = setInterval(async () => {\n await safelyExecute(() => this.fetchTokenList(this.#chainId));\n }, this.#intervalDelay);\n }\n\n /**\n * This starts a new polling loop for any given chain. Under the hood it is deduping polls\n *\n * @param input - The input for the poll.\n * @param input.chainId - The chainId of the chain to trigger the fetch.\n * @returns A promise that resolves when this operation completes.\n */\n async _executePoll({ chainId }: TokenListPollingInput): Promise<void> {\n await this.#waitForInitialization();\n return this.fetchTokenList(chainId);\n }\n\n /**\n * Fetching token list from the Token Service API. This will fetch tokens across chains.\n * State changes are automatically persisted via the stateChange subscription.\n *\n * @param chainId - The chainId of the current chain triggering the fetch.\n */\n async fetchTokenList(chainId: Hex): Promise<void> {\n if (this.isCacheValid(chainId)) {\n return;\n }\n\n // Fetch fresh token list from the API\n const tokensFromAPI = await safelyExecute(\n () =>\n fetchTokenListByChainId(\n chainId,\n this.#abortController.signal,\n ) as Promise<TokenListToken[]>,\n );\n\n // Have response - process and update list\n if (tokensFromAPI) {\n // Format tokens from API (HTTP) and update tokenList\n const tokenList: TokenListMap = {};\n for (const token of tokensFromAPI) {\n tokenList[token.address] = {\n ...token,\n aggregators: formatAggregatorNames(token.aggregators),\n iconUrl: formatIconUrlWithProxy({\n chainId,\n tokenAddress: token.address,\n }),\n };\n }\n\n // Update state - persistence happens automatically via subscription\n const newDataCache: DataCache = {\n data: tokenList,\n timestamp: Date.now(),\n };\n this.update((state) => {\n state.tokensChainsCache[chainId] = newDataCache;\n });\n return;\n }\n\n // No response - fallback to previous state, or initialise empty.\n // Only initialize with a new timestamp if there's no existing cache.\n // If there's existing cache, keep it as-is without updating the timestamp\n // to avoid making stale data appear \"fresh\" and preventing retry attempts.\n if (!tokensFromAPI) {\n const existingCache = this.state.tokensChainsCache[chainId];\n if (!existingCache) {\n // No existing cache - initialize empty (persistence happens automatically)\n const newDataCache: DataCache = { data: {}, timestamp: Date.now() };\n this.update((state) => {\n state.tokensChainsCache[chainId] = newDataCache;\n });\n }\n // If there's existing cache, keep it as-is (don't update timestamp or persist)\n }\n }\n\n isCacheValid(chainId: Hex): boolean {\n const { tokensChainsCache }: TokenListState = this.state;\n const timestamp: number | undefined = tokensChainsCache[chainId]?.timestamp;\n const now = Date.now();\n return (\n timestamp !== undefined && now - timestamp < this.#cacheRefreshThreshold\n );\n }\n}\n\nexport default TokenListController;\n"]}
|
|
1
|
+
{"version":3,"file":"TokenListController.mjs","sourceRoot":"","sources":["../src/TokenListController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport { safelyExecute } from '@metamask/controller-utils';\nimport type { Messenger } from '@metamask/messenger';\nimport type {\n NetworkControllerStateChangeEvent,\n NetworkState,\n NetworkControllerGetNetworkClientByIdAction,\n} from '@metamask/network-controller';\nimport { StaticIntervalPollingController } from '@metamask/polling-controller';\nimport type {\n StorageServiceSetItemAction,\n StorageServiceGetItemAction,\n StorageServiceGetAllKeysAction,\n} from '@metamask/storage-service';\nimport type { Hex } from '@metamask/utils';\n\nimport {\n isTokenListSupportedForNetwork,\n formatAggregatorNames,\n formatIconUrlWithProxy,\n} from './assetsUtil';\nimport { TokenRwaData, fetchTokenListByChainId } from './token-service';\n\n// 4 Hour Interval Cache Refresh Threshold\nconst DEFAULT_INTERVAL = 4 * 60 * 60 * 1000;\nconst DEFAULT_THRESHOLD = 4 * 60 * 60 * 1000;\n\nconst name = 'TokenListController';\n\nexport type TokenListToken = {\n name: string;\n symbol: string;\n decimals: number;\n address: string;\n occurrences: number;\n aggregators: string[];\n iconUrl: string;\n rwaData?: TokenRwaData;\n};\n\nexport type TokenListMap = Record<string, TokenListToken>;\n\nexport type DataCache = {\n timestamp: number;\n data: TokenListMap;\n};\nexport type TokensChainsCache = {\n [chainId: Hex]: DataCache;\n};\n\nexport type TokenListState = {\n tokensChainsCache: TokensChainsCache;\n};\n\nexport type TokenListStateChange = ControllerStateChangeEvent<\n typeof name,\n TokenListState\n>;\n\nexport type TokenListControllerEvents = TokenListStateChange;\n\nexport type GetTokenListState = ControllerGetStateAction<\n typeof name,\n TokenListState\n>;\n\nexport type TokenListControllerActions = GetTokenListState;\n\ntype AllowedActions =\n | NetworkControllerGetNetworkClientByIdAction\n | StorageServiceSetItemAction\n | StorageServiceGetItemAction\n | StorageServiceGetAllKeysAction;\n\ntype AllowedEvents = NetworkControllerStateChangeEvent;\n\nexport type TokenListControllerMessenger = Messenger<\n typeof name,\n TokenListControllerActions | AllowedActions,\n TokenListControllerEvents | AllowedEvents\n>;\n\nconst metadata: StateMetadata<TokenListState> = {\n tokensChainsCache: {\n includeInStateLogs: false,\n persist: false, // Persisted separately via StorageService\n includeInDebugSnapshot: true,\n usedInUi: true,\n },\n};\n\nexport const getDefaultTokenListState = (): TokenListState => {\n return {\n tokensChainsCache: {},\n };\n};\n\n/** The input to start polling for the {@link TokenListController} */\ntype TokenListPollingInput = {\n chainId: Hex;\n};\n\n/**\n * Controller that passively polls on a set interval for the list of tokens from metaswaps api\n */\nexport class TokenListController extends StaticIntervalPollingController<TokenListPollingInput>()<\n typeof name,\n TokenListState,\n TokenListControllerMessenger\n> {\n /**\n * Debounce timer for persisting state changes to storage.\n */\n #persistDebounceTimer?: ReturnType<typeof setTimeout>;\n\n /**\n * Promise for the in-flight initialization sequence.\n */\n #initializePromise?: Promise<void>;\n\n /**\n * Promise that resolves when the current persist operation completes.\n * Used to prevent race conditions between persist operations.\n */\n #persistInFlightPromise?: Promise<void>;\n\n /**\n * Tracks which chains have pending changes to persist.\n * Only changed chains are persisted to reduce write amplification.\n */\n readonly #changedChainsToPersist: Set<Hex> = new Set();\n\n /**\n * Previous tokensChainsCache for detecting which chains changed.\n */\n #previousTokensChainsCache: TokensChainsCache = {};\n\n /**\n * Debounce delay for persisting state changes (in milliseconds).\n */\n static readonly #persistDebounceMs = 500;\n\n // Storage key prefix for per-chain files\n static readonly #storageKeyPrefix = 'tokensChainsCache';\n\n /**\n * Get storage key for a specific chain.\n *\n * @param chainId - The chain ID.\n * @returns Storage key for the chain.\n */\n static #getChainStorageKey(chainId: Hex): string {\n return `${TokenListController.#storageKeyPrefix}:${chainId}`;\n }\n\n #intervalId?: ReturnType<typeof setTimeout>;\n\n readonly #intervalDelay: number;\n\n readonly #cacheRefreshThreshold: number;\n\n #chainId: Hex;\n\n #abortController: AbortController;\n\n readonly #isDeprecated: () => boolean;\n\n /**\n * Creates a TokenListController instance.\n *\n * @param options - The controller options.\n * @param options.chainId - The chain ID of the current network.\n * @param options.onNetworkStateChange - A function for registering an event handler for network state changes.\n * @param options.interval - The polling interval, in milliseconds.\n * @param options.cacheRefreshThreshold - The token cache expiry time, in milliseconds.\n * @param options.isDeprecated - Optional function returning whether the controller should be disabled.\n * When it returns `true`, `tokensChainsCache` is reset to `{}` at construction and at every\n * polling entry point, no fetches are issued, and persisted storage is cleared on initialize.\n * The function is evaluated dynamically on each entry point so it can be toggled at runtime.\n * @param options.messenger - A restricted messenger.\n * @param options.state - Initial state to set on this controller.\n */\n constructor({\n chainId,\n onNetworkStateChange,\n interval = DEFAULT_INTERVAL,\n cacheRefreshThreshold = DEFAULT_THRESHOLD,\n isDeprecated = (): boolean => false,\n messenger,\n state,\n }: {\n chainId: Hex;\n onNetworkStateChange?: (\n listener: (networkState: NetworkState) => void,\n ) => void;\n interval?: number;\n cacheRefreshThreshold?: number;\n isDeprecated?: () => boolean;\n messenger: TokenListControllerMessenger;\n state?: Partial<TokenListState>;\n }) {\n super({\n name,\n metadata,\n messenger,\n state: { ...getDefaultTokenListState(), ...state },\n });\n\n this.#intervalDelay = interval;\n this.setIntervalLength(interval);\n this.#cacheRefreshThreshold = cacheRefreshThreshold;\n this.#chainId = chainId;\n this.#abortController = new AbortController();\n this.#isDeprecated = isDeprecated;\n\n if (this.#isDeprecated()) {\n // Fire-and-forget: storage clearing is async but the constructor is sync.\n // Errors are caught inside `#enforceDisabledState`.\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.#enforceDisabledState();\n }\n\n if (onNetworkStateChange) {\n // TODO: Either fix this lint violation or explain why it's necessary to ignore.\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n onNetworkStateChange(async (networkControllerState) => {\n await this.#onNetworkControllerStateChange(networkControllerState);\n });\n } else {\n this.messenger.subscribe(\n 'NetworkController:stateChange',\n // TODO: Either fix this lint violation or explain why it's necessary to ignore.\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n async (networkControllerState) => {\n await this.#onNetworkControllerStateChange(networkControllerState);\n },\n );\n }\n }\n\n /**\n * Initialize the controller by loading cache from storage and running migration.\n * This method should be called by clients after construction.\n *\n * @returns A promise that resolves when initialization is complete.\n */\n async initialize(): Promise<void> {\n if (this.#initializePromise) {\n await this.#initializePromise;\n return;\n }\n\n const executeInit = async (): Promise<void> => {\n try {\n if (this.#isDeprecated()) {\n await this.#enforceDisabledState();\n return;\n }\n\n await this.#synchronizeCacheWithStorage();\n\n // Subscribe to state changes to automatically persist tokensChainsCache\n this.messenger.subscribe(\n 'TokenListController:stateChange',\n (newCache: TokensChainsCache) => this.#onCacheChanged(newCache),\n (controllerState) => controllerState.tokensChainsCache,\n );\n } catch {\n // do nothing\n } finally {\n this.#initializePromise = undefined;\n }\n };\n\n this.#initializePromise = executeInit();\n\n await this.#initializePromise;\n }\n\n /**\n * Waits for any in-flight initialization to complete.\n * Polling should not run against partially initialized state.\n */\n async #waitForInitialization(): Promise<void> {\n try {\n await this.#initializePromise;\n } catch {\n // do nothing\n }\n }\n\n /**\n * Handle tokensChainsCache changes by detecting which chains changed\n * and scheduling debounced persistence.\n *\n * @param newCache - The new tokensChainsCache state.\n */\n #onCacheChanged(newCache: TokensChainsCache): void {\n // Detect which chains changed by comparing with previous cache\n for (const chainId of Object.keys(newCache) as Hex[]) {\n const newData = newCache[chainId];\n const prevData = this.#previousTokensChainsCache[chainId];\n\n // Chain is new or timestamp changed (indicating data update)\n if (prevData?.timestamp !== newData.timestamp) {\n this.#changedChainsToPersist.add(chainId);\n }\n }\n\n // Update previous cache reference\n this.#previousTokensChainsCache = { ...newCache };\n\n // Schedule persistence if there are changes\n if (this.#changedChainsToPersist.size > 0) {\n this.#debouncePersist();\n }\n }\n\n /**\n * Debounce persistence of changed chains to storage.\n */\n #debouncePersist(): void {\n if (this.#persistDebounceTimer) {\n clearTimeout(this.#persistDebounceTimer);\n }\n\n this.#persistDebounceTimer = setTimeout(() => {\n // Note: #persistChangedChains handles errors internally via #saveChainCacheToStorage,\n // so this promise will not reject.\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.#persistChangedChains();\n }, TokenListController.#persistDebounceMs);\n }\n\n /**\n * Persist only the chains that have changed to storage.\n * Reduces write amplification by skipping unchanged chains.\n *\n * If a persist operation is already in-flight, this method returns early\n * and reschedules the debounce to ensure accumulated changes are retried\n * after the current operation completes.\n *\n * @returns A promise that resolves when changed chains are persisted.\n */\n async #persistChangedChains(): Promise<void> {\n if (this.#persistInFlightPromise) {\n // Reschedule debounce to retry accumulated changes after in-flight persist completes\n if (this.#changedChainsToPersist.size > 0) {\n this.#debouncePersist();\n }\n return;\n }\n\n const chainsToPersist = [...this.#changedChainsToPersist];\n this.#changedChainsToPersist.clear();\n\n if (chainsToPersist.length === 0) {\n return;\n }\n\n this.#persistInFlightPromise = Promise.all(\n chainsToPersist.map((chainId) => this.#saveChainCacheToStorage(chainId)),\n ).then(() => undefined);\n\n try {\n await this.#persistInFlightPromise;\n } finally {\n this.#persistInFlightPromise = undefined;\n }\n }\n\n /**\n * Synchronize tokensChainsCache between state and storage bidirectionally.\n *\n * This method:\n * 1. Loads cached chains from storage (per-chain files) in parallel\n * 2. Merges loaded data into state (preferring existing state to avoid overwriting fresh data)\n * 3. Persists any chains that exist in state but not in storage\n *\n * Called during initialization to ensure state and storage are consistent.\n *\n * @returns A promise that resolves when synchronization is complete.\n */\n async #synchronizeCacheWithStorage(): Promise<void> {\n try {\n const allKeys = await this.messenger.call(\n 'StorageService:getAllKeys',\n name,\n );\n\n // Filter keys that belong to tokensChainsCache (per-chain files)\n const cacheKeys = allKeys.filter((key) =>\n key.startsWith(`${TokenListController.#storageKeyPrefix}:`),\n );\n\n // Load all chains in parallel\n const chainCaches = await Promise.all(\n cacheKeys.map(async (key) => {\n // Extract chainId from key: 'tokensChainsCache:0x1' → '0x1'\n const chainId = key.split(':')[1] as Hex;\n\n const { result, error } = await this.messenger.call(\n 'StorageService:getItem',\n name,\n key,\n );\n\n if (error) {\n console.error(\n `TokenListController: Error loading cache for ${chainId}:`,\n error,\n );\n return null;\n }\n\n return result ? { chainId, data: result as DataCache } : null;\n }),\n );\n\n // Build complete cache from loaded chains\n const loadedCache: TokensChainsCache = {};\n chainCaches.forEach((chainCache) => {\n if (chainCache) {\n loadedCache[chainCache.chainId] = chainCache.data;\n }\n });\n\n // Chains in state _before loading persisted state_, from a recent update\n const chainsInState = new Set(\n Object.keys(this.state.tokensChainsCache) as Hex[],\n );\n\n // Merge loaded cache with existing state, preferring existing data\n // (which may be fresher if fetched during initialization)\n if (Object.keys(loadedCache).length > 0) {\n this.update((state) => {\n // Only load chains that don't already exist in state\n // This prevents overwriting fresh API data with stale cached data\n for (const [chainId, cacheData] of Object.entries(loadedCache)) {\n if (!state.tokensChainsCache[chainId as Hex]) {\n state.tokensChainsCache[chainId as Hex] = cacheData;\n }\n }\n });\n }\n\n // Persist chains that exist in state but were not loaded from storage.\n // This handles the case where initial state contains chains that don't exist\n // in storage yet (e.g., fresh data from API). Without this, those chains\n // would be lost on the next app restart.\n for (const chainId of chainsInState) {\n this.#changedChainsToPersist.add(chainId);\n }\n\n // Persist any chains that need to be saved\n if (this.#changedChainsToPersist.size > 0) {\n this.#debouncePersist();\n }\n\n this.#previousTokensChainsCache = { ...this.state.tokensChainsCache };\n } catch (error) {\n console.error(\n 'TokenListController: Failed to load cache from storage:',\n error,\n );\n }\n }\n\n /**\n * Save a specific chain's cache to StorageService.\n * This persists only the updated chain's data, reducing write amplification.\n *\n * @param chainId - The chain ID to save.\n * @returns A promise that resolves when saving is complete.\n */\n async #saveChainCacheToStorage(chainId: Hex): Promise<void> {\n try {\n const chainData = this.state.tokensChainsCache[chainId];\n\n if (!chainData) {\n console.warn(`TokenListController: No cache data for chain ${chainId}`);\n return;\n }\n\n const storageKey = TokenListController.#getChainStorageKey(chainId);\n\n await this.messenger.call(\n 'StorageService:setItem',\n name,\n storageKey,\n chainData,\n );\n } catch (error) {\n console.error(\n `TokenListController: Failed to save cache for ${chainId}:`,\n error,\n );\n }\n }\n\n /**\n * Updates state and restarts polling on changes to the network controller\n * state.\n *\n * @param networkControllerState - The updated network controller state.\n */\n async #onNetworkControllerStateChange(\n networkControllerState: NetworkState,\n ): Promise<void> {\n const selectedNetworkClient = this.messenger.call(\n 'NetworkController:getNetworkClientById',\n networkControllerState.selectedNetworkClientId,\n );\n const { chainId } = selectedNetworkClient.configuration;\n\n if (this.#chainId !== chainId) {\n this.#abortController.abort();\n this.#abortController = new AbortController();\n this.#chainId = chainId;\n }\n }\n\n // Eventually we want to remove start/restart/stop controls in favor of new _executePoll API\n // Maintaining these functions for now until we can safely deprecate them for backwards compatibility\n /**\n * Start polling for the token list.\n *\n * @deprecated This method is deprecated and will be removed in the future.\n * Consider using the new polling approach instead\n */\n async start(): Promise<void> {\n if (this.#isDeprecated()) {\n await this.#enforceDisabledState();\n return;\n }\n if (!isTokenListSupportedForNetwork(this.#chainId)) {\n return;\n }\n await this.#startDeprecatedPolling();\n }\n\n /**\n * Restart polling for the token list.\n *\n * @deprecated This method is deprecated and will be removed in the future.\n * Consider using the new polling approach instead\n */\n async restart(): Promise<void> {\n this.#stopPolling();\n if (this.#isDeprecated()) {\n await this.#enforceDisabledState();\n return;\n }\n await this.#startDeprecatedPolling();\n }\n\n /**\n * Stop polling for the token list.\n *\n * @deprecated This method is deprecated and will be removed in the future.\n * Consider using the new polling approach instead\n */\n stop(): void {\n this.#stopPolling();\n }\n\n /**\n * This stops any active polling.\n *\n * @deprecated This method is deprecated and will be removed in the future.\n * Consider using the new polling approach instead\n */\n override destroy(): void {\n super.destroy();\n this.#stopPolling();\n\n // Cancel any pending debounced persistence operations\n if (this.#persistDebounceTimer) {\n clearTimeout(this.#persistDebounceTimer);\n this.#persistDebounceTimer = undefined;\n }\n this.#changedChainsToPersist.clear();\n }\n\n /**\n * This stops any active polling intervals.\n *\n * @deprecated This method is deprecated and will be removed in the future.\n * Consider using the new polling approach instead\n */\n #stopPolling(): void {\n if (this.#intervalId) {\n clearInterval(this.#intervalId);\n }\n }\n\n /**\n * Starts a new polling interval for a given chainId (this should be deprecated in favor of _executePoll)\n *\n * @deprecated This method is deprecated and will be removed in the future.\n * Consider using the new polling approach instead\n */\n async #startDeprecatedPolling(): Promise<void> {\n // renaming this to avoid collision with base class\n await safelyExecute(() => this.fetchTokenList(this.#chainId));\n // TODO: Either fix this lint violation or explain why it's necessary to ignore.\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n this.#intervalId = setInterval(async () => {\n await safelyExecute(() => this.fetchTokenList(this.#chainId));\n }, this.#intervalDelay);\n }\n\n /**\n * This starts a new polling loop for any given chain. Under the hood it is deduping polls\n *\n * @param input - The input for the poll.\n * @param input.chainId - The chainId of the chain to trigger the fetch.\n * @returns A promise that resolves when this operation completes.\n */\n async _executePoll({ chainId }: TokenListPollingInput): Promise<void> {\n if (this.#isDeprecated()) {\n await this.#enforceDisabledState();\n return;\n }\n await this.#waitForInitialization();\n await this.fetchTokenList(chainId);\n }\n\n /**\n * Fully enforce the disabled state in a single step:\n * 1. Drop in-memory `tokensChainsCache` to `{}`.\n * 2. Cancel any pending debounced persist and clear the changed-chains set so\n * a stale entry can't write old data after the in-memory reset.\n * 3. Overwrite every persisted `tokensChainsCache:*` entry in StorageService\n * with `{ data: {}, timestamp: 0 }`.\n *\n * Called from every fetching entry point when `isDeprecated()` is true so that\n * a runtime toggle propagates to both memory and storage immediately, even\n * if `initialize()` was originally invoked while the controller was enabled.\n *\n * @returns A promise that resolves when persisted entries have been cleared.\n */\n async #enforceDisabledState(): Promise<void> {\n this.#resetCacheState();\n if (this.#persistDebounceTimer) {\n clearTimeout(this.#persistDebounceTimer);\n this.#persistDebounceTimer = undefined;\n }\n this.#changedChainsToPersist.clear();\n await this.#clearPersistedCache();\n }\n\n /**\n * Reset in-memory `tokensChainsCache` to an empty object.\n * Used when the controller is disabled to drop any data already in state.\n */\n #resetCacheState(): void {\n if (Object.keys(this.state.tokensChainsCache).length === 0) {\n return;\n }\n this.update((state) => {\n state.tokensChainsCache = {};\n });\n }\n\n /**\n * Overwrite every persisted `tokensChainsCache:*` entry in StorageService\n * with `{ data: {}, timestamp: 0 }` so the on-disk view reflects the\n * disabled state.\n *\n * @returns A promise that resolves when persisted entries have been cleared.\n */\n async #clearPersistedCache(): Promise<void> {\n try {\n const allKeys = await this.messenger.call(\n 'StorageService:getAllKeys',\n name,\n );\n const cacheKeys = allKeys.filter((key) =>\n key.startsWith(`${TokenListController.#storageKeyPrefix}:`),\n );\n const emptyDataCache: DataCache = { data: {}, timestamp: 0 };\n await Promise.all(\n cacheKeys.map((key) =>\n this.messenger.call(\n 'StorageService:setItem',\n name,\n key,\n emptyDataCache,\n ),\n ),\n );\n } catch (error) {\n console.error(\n 'TokenListController: Failed to clear persisted cache:',\n error,\n );\n }\n }\n\n /**\n * Fetching token list from the Token Service API. This will fetch tokens across chains.\n * State changes are automatically persisted via the stateChange subscription.\n *\n * @param chainId - The chainId of the current chain triggering the fetch.\n */\n async fetchTokenList(chainId: Hex): Promise<void> {\n if (this.#isDeprecated()) {\n await this.#enforceDisabledState();\n return;\n }\n if (this.isCacheValid(chainId)) {\n return;\n }\n\n // Fetch fresh token list from the API\n const tokensFromAPI = await safelyExecute(\n () =>\n fetchTokenListByChainId(\n chainId,\n this.#abortController.signal,\n ) as Promise<TokenListToken[]>,\n );\n\n // Have response - process and update list\n if (tokensFromAPI) {\n // Format tokens from API (HTTP) and update tokenList\n const tokenList: TokenListMap = {};\n for (const token of tokensFromAPI) {\n tokenList[token.address] = {\n ...token,\n aggregators: formatAggregatorNames(token.aggregators),\n iconUrl: formatIconUrlWithProxy({\n chainId,\n tokenAddress: token.address,\n }),\n };\n }\n\n // Update state - persistence happens automatically via subscription\n const newDataCache: DataCache = {\n data: tokenList,\n timestamp: Date.now(),\n };\n this.update((state) => {\n state.tokensChainsCache[chainId] = newDataCache;\n });\n return;\n }\n\n // No response - fallback to previous state, or initialise empty.\n // Only initialize with a new timestamp if there's no existing cache.\n // If there's existing cache, keep it as-is without updating the timestamp\n // to avoid making stale data appear \"fresh\" and preventing retry attempts.\n if (!tokensFromAPI) {\n const existingCache = this.state.tokensChainsCache[chainId];\n if (!existingCache) {\n // No existing cache - initialize empty (persistence happens automatically)\n const newDataCache: DataCache = { data: {}, timestamp: Date.now() };\n this.update((state) => {\n state.tokensChainsCache[chainId] = newDataCache;\n });\n }\n // If there's existing cache, keep it as-is (don't update timestamp or persist)\n }\n }\n\n isCacheValid(chainId: Hex): boolean {\n const { tokensChainsCache }: TokenListState = this.state;\n const timestamp: number | undefined = tokensChainsCache[chainId]?.timestamp;\n const now = Date.now();\n return (\n timestamp !== undefined && now - timestamp < this.#cacheRefreshThreshold\n );\n }\n}\n\nexport default TokenListController;\n"]}
|
package/dist/multicall.cjs
CHANGED
|
@@ -317,6 +317,8 @@ const MULTICALL_CONTRACT_BY_CHAINID = {
|
|
|
317
317
|
'0xa5bf': '0xcA11bde05977b3631167028862bE2a173976CA11',
|
|
318
318
|
// Tempo Mainnet (4217)
|
|
319
319
|
'0x1079': '0xcA11bde05977b3631167028862bE2a173976CA11',
|
|
320
|
+
// Arc (5042)
|
|
321
|
+
'0x13b2': '0xcA11bde05977b3631167028862bE2a173976CA11',
|
|
320
322
|
};
|
|
321
323
|
const multicallAbi = [
|
|
322
324
|
{
|