@metamask-previews/assets-controllers 93.1.0-preview-d717276a → 94.0.0-preview-cd26c284

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/CHANGELOG.md +14 -1
  2. package/dist/TokenBalancesController.cjs +342 -378
  3. package/dist/TokenBalancesController.cjs.map +1 -1
  4. package/dist/TokenBalancesController.d.cts +17 -38
  5. package/dist/TokenBalancesController.d.cts.map +1 -1
  6. package/dist/TokenBalancesController.d.mts +17 -38
  7. package/dist/TokenBalancesController.d.mts.map +1 -1
  8. package/dist/TokenBalancesController.mjs +342 -378
  9. package/dist/TokenBalancesController.mjs.map +1 -1
  10. package/dist/TokenDetectionController.cjs +131 -207
  11. package/dist/TokenDetectionController.cjs.map +1 -1
  12. package/dist/TokenDetectionController.d.cts +38 -12
  13. package/dist/TokenDetectionController.d.cts.map +1 -1
  14. package/dist/TokenDetectionController.d.mts +38 -12
  15. package/dist/TokenDetectionController.d.mts.map +1 -1
  16. package/dist/TokenDetectionController.mjs +132 -208
  17. package/dist/TokenDetectionController.mjs.map +1 -1
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.d.cts +1 -1
  20. package/dist/index.d.cts.map +1 -1
  21. package/dist/index.d.mts +1 -1
  22. package/dist/index.d.mts.map +1 -1
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/multi-chain-accounts-service/api-balance-fetcher.cjs +13 -2
  25. package/dist/multi-chain-accounts-service/api-balance-fetcher.cjs.map +1 -1
  26. package/dist/multi-chain-accounts-service/api-balance-fetcher.d.cts.map +1 -1
  27. package/dist/multi-chain-accounts-service/api-balance-fetcher.d.mts.map +1 -1
  28. package/dist/multi-chain-accounts-service/api-balance-fetcher.mjs +13 -2
  29. package/dist/multi-chain-accounts-service/api-balance-fetcher.mjs.map +1 -1
  30. package/dist/multi-chain-accounts-service/types.cjs.map +1 -1
  31. package/dist/multi-chain-accounts-service/types.d.cts +2 -1
  32. package/dist/multi-chain-accounts-service/types.d.cts.map +1 -1
  33. package/dist/multi-chain-accounts-service/types.d.mts +2 -1
  34. package/dist/multi-chain-accounts-service/types.d.mts.map +1 -1
  35. package/dist/multi-chain-accounts-service/types.mjs.map +1 -1
  36. package/package.json +1 -1
@@ -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 _TokenBalancesController_instances, _TokenBalancesController_platform, _TokenBalancesController_queryAllAccounts, _TokenBalancesController_accountsApiChainIds, _TokenBalancesController_balanceFetchers, _TokenBalancesController_allTokens, _TokenBalancesController_detectedTokens, _TokenBalancesController_allIgnoredTokens, _TokenBalancesController_defaultInterval, _TokenBalancesController_websocketActivePollingInterval, _TokenBalancesController_chainPollingConfig, _TokenBalancesController_intervalPollingTimers, _TokenBalancesController_isControllerPollingActive, _TokenBalancesController_requestedChainIds, _TokenBalancesController_statusChangeDebouncer, _TokenBalancesController_normalizeAccountAddresses, _TokenBalancesController_chainIdsWithTokens, _TokenBalancesController_getProvider, _TokenBalancesController_getNetworkClient, _TokenBalancesController_createAccountsApiFetcher, _TokenBalancesController_startIntervalGroupPolling, _TokenBalancesController_startPollingForInterval, _TokenBalancesController_setPollingTimer, _TokenBalancesController_isTokenTracked, _TokenBalancesController_onTokensChanged, _TokenBalancesController_onNetworkChanged, _TokenBalancesController_onAccountRemoved, _TokenBalancesController_onAccountChanged, _TokenBalancesController_prepareBalanceUpdates, _TokenBalancesController_onAccountActivityBalanceUpdate, _TokenBalancesController_onAccountActivityStatusChanged, _TokenBalancesController_processAccumulatedStatusChanges;
12
+ var _TokenBalancesController_instances, _TokenBalancesController_platform, _TokenBalancesController_queryAllAccounts, _TokenBalancesController_accountsApiChainIds, _TokenBalancesController_balanceFetchers, _TokenBalancesController_allTokens, _TokenBalancesController_detectedTokens, _TokenBalancesController_allIgnoredTokens, _TokenBalancesController_defaultInterval, _TokenBalancesController_websocketActivePollingInterval, _TokenBalancesController_chainPollingConfig, _TokenBalancesController_intervalPollingTimers, _TokenBalancesController_isControllerPollingActive, _TokenBalancesController_isUnlocked, _TokenBalancesController_requestedChainIds, _TokenBalancesController_statusChangeDebouncer, _TokenBalancesController_subscribeToControllers, _TokenBalancesController_registerActions, _TokenBalancesController_normalizeAccountAddresses, _TokenBalancesController_chainIdsWithTokens, _TokenBalancesController_getProvider, _TokenBalancesController_getNetworkClient, _TokenBalancesController_createAccountsApiFetcher, _TokenBalancesController_startIntervalGroupPolling, _TokenBalancesController_startPollingForInterval, _TokenBalancesController_setPollingTimer, _TokenBalancesController_stopAllPolling, _TokenBalancesController_getTargetChains, _TokenBalancesController_getAccountsAndJwt, _TokenBalancesController_fetchAllBalances, _TokenBalancesController_filterByTokenAddresses, _TokenBalancesController_getAccountsToProcess, _TokenBalancesController_applyTokenBalancesToState, _TokenBalancesController_buildNativeBalanceUpdates, _TokenBalancesController_buildStakedBalanceUpdates, _TokenBalancesController_importUntrackedTokens, _TokenBalancesController_isTokenTracked, _TokenBalancesController_onTokensChanged, _TokenBalancesController_onNetworkChanged, _TokenBalancesController_onAccountRemoved, _TokenBalancesController_onAccountChanged, _TokenBalancesController_prepareBalanceUpdates, _TokenBalancesController_onAccountActivityBalanceUpdate, _TokenBalancesController_onAccountActivityStatusChanged, _TokenBalancesController_processAccumulatedStatusChanges;
13
13
  import { Web3Provider } from "@ethersproject/providers";
14
14
  import { BNToHex, isValidHexAddress, safelyExecuteWithTimeout, toChecksumHexAddress, toHex } from "@metamask/controller-utils";
15
15
  import { StaticIntervalPollingController } from "@metamask/polling-controller";
@@ -31,19 +31,14 @@ const metadata = {
31
31
  usedInUi: true,
32
32
  },
33
33
  };
34
- // endregion
35
- // ────────────────────────────────────────────────────────────────────────────
36
- // region: Helper utilities
37
34
  const draft = (base, fn) => produce(base, fn);
38
35
  const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
39
36
  const checksum = (addr) => toChecksumHexAddress(addr);
40
37
  /**
41
- * Convert CAIP chain ID or hex chain ID to hex chain ID
42
- * Handles both CAIP-2 format (e.g., "eip155:1") and hex format (e.g., "0x1")
38
+ * Convert CAIP chain ID or hex chain ID to hex chain ID.
43
39
  *
44
- * @param chainId - CAIP chain ID (e.g., "eip155:1") or hex chain ID (e.g., "0x1")
45
- * @returns Hex chain ID (e.g., "0x1")
46
- * @throws {Error} If chainId is neither a valid CAIP-2 chain ID nor a hex string
40
+ * @param chainId - CAIP chain ID or hex chain ID.
41
+ * @returns Hex chain ID.
47
42
  */
48
43
  export const caipChainIdToHex = (chainId) => {
49
44
  if (isStrictHexString(chainId)) {
@@ -55,30 +50,24 @@ export const caipChainIdToHex = (chainId) => {
55
50
  throw new Error('caipChainIdToHex - Failed to provide CAIP-2 or Hex chainId');
56
51
  };
57
52
  /**
58
- * Extract token address from asset type
59
- * Returns tuple of [tokenAddress, isNativeToken] or null if invalid
53
+ * Extract token address from asset type.
60
54
  *
61
- * @param assetType - Asset type string (e.g., 'eip155:1/erc20:0x...' or 'eip155:1/slip44:60')
62
- * @returns Tuple of [tokenAddress, isNativeToken] or null if invalid
55
+ * @param assetType - Asset type string.
56
+ * @returns Tuple of [tokenAddress, isNativeToken] or null if invalid.
63
57
  */
64
58
  export const parseAssetType = (assetType) => {
65
59
  if (!isCaipAssetType(assetType)) {
66
60
  return null;
67
61
  }
68
62
  const parsed = parseCaipAssetType(assetType);
69
- // ERC20 token (e.g., "eip155:1/erc20:0x...")
70
63
  if (parsed.assetNamespace === 'erc20') {
71
64
  return [parsed.assetReference, false];
72
65
  }
73
- // Native token (e.g., "eip155:1/slip44:60")
74
66
  if (parsed.assetNamespace === 'slip44') {
75
67
  return [ZERO_ADDRESS, true];
76
68
  }
77
69
  return null;
78
70
  };
79
- // endregion
80
- // ────────────────────────────────────────────────────────────────────────────
81
- // region: Main controller
82
71
  export class TokenBalancesController extends StaticIntervalPollingController() {
83
72
  constructor({ messenger, interval = DEFAULT_INTERVAL_MS, websocketActivePollingInterval = DEFAULT_WEBSOCKET_ACTIVE_POLLING_INTERVAL_MS, chainPollingIntervals = {}, state = {}, queryMultipleAccounts = true, accountsApiChainIds = () => [], allowExternalServices = () => true, platform, }) {
84
73
  super({
@@ -105,6 +94,8 @@ export class TokenBalancesController extends StaticIntervalPollingController() {
105
94
  _TokenBalancesController_intervalPollingTimers.set(this, new Map());
106
95
  /** Track if controller-level polling is active */
107
96
  _TokenBalancesController_isControllerPollingActive.set(this, false);
97
+ /** Track if the keyring is unlocked */
98
+ _TokenBalancesController_isUnlocked.set(this, false);
108
99
  /** Store original chainIds from startPolling to preserve intent */
109
100
  _TokenBalancesController_requestedChainIds.set(this, []);
110
101
  /** Debouncing for rapid status changes to prevent excessive HTTP calls */
@@ -114,52 +105,32 @@ export class TokenBalancesController extends StaticIntervalPollingController() {
114
105
  });
115
106
  _TokenBalancesController_getProvider.set(this, (chainId) => {
116
107
  const { networkConfigurationsByChainId } = this.messenger.call('NetworkController:getState');
117
- const cfg = networkConfigurationsByChainId[chainId];
118
- const { networkClientId } = cfg.rpcEndpoints[cfg.defaultRpcEndpointIndex];
108
+ const networkConfig = networkConfigurationsByChainId[chainId];
109
+ const { networkClientId } = networkConfig.rpcEndpoints[networkConfig.defaultRpcEndpointIndex];
119
110
  const client = this.messenger.call('NetworkController:getNetworkClientById', networkClientId);
120
111
  return new Web3Provider(client.provider);
121
112
  });
122
113
  _TokenBalancesController_getNetworkClient.set(this, (chainId) => {
123
114
  const { networkConfigurationsByChainId } = this.messenger.call('NetworkController:getState');
124
- const cfg = networkConfigurationsByChainId[chainId];
125
- const { networkClientId } = cfg.rpcEndpoints[cfg.defaultRpcEndpointIndex];
115
+ const networkConfig = networkConfigurationsByChainId[chainId];
116
+ const { networkClientId } = networkConfig.rpcEndpoints[networkConfig.defaultRpcEndpointIndex];
126
117
  return this.messenger.call('NetworkController:getNetworkClientById', networkClientId);
127
118
  });
128
- /**
129
- * Creates an AccountsApiBalanceFetcher that only supports chains in the accountsApiChainIds array
130
- *
131
- * @returns A BalanceFetcher that wraps AccountsApiBalanceFetcher with chainId filtering
132
- */
133
119
  _TokenBalancesController_createAccountsApiFetcher.set(this, () => {
134
120
  const originalFetcher = new AccountsApiBalanceFetcher(__classPrivateFieldGet(this, _TokenBalancesController_platform, "f"), __classPrivateFieldGet(this, _TokenBalancesController_getProvider, "f"));
135
121
  return {
136
- supports: (chainId) => {
137
- // Only support chains that are both:
138
- // 1. In our specified accountsApiChainIds array
139
- // 2. Actually supported by the AccountsApi
140
- return (__classPrivateFieldGet(this, _TokenBalancesController_accountsApiChainIds, "f").call(this).includes(chainId) &&
141
- originalFetcher.supports(chainId));
142
- },
122
+ supports: (chainId) => __classPrivateFieldGet(this, _TokenBalancesController_accountsApiChainIds, "f").call(this).includes(chainId) &&
123
+ originalFetcher.supports(chainId),
143
124
  fetch: originalFetcher.fetch.bind(originalFetcher),
144
125
  };
145
126
  });
146
127
  _TokenBalancesController_onTokensChanged.set(this, async (state) => {
147
128
  const changed = [];
148
129
  let hasChanges = false;
149
- // Get chains that have existing balances
150
- const chainsWithBalances = new Set();
151
- for (const address of Object.keys(this.state.tokenBalances)) {
152
- const addressKey = address;
153
- for (const chainId of Object.keys(this.state.tokenBalances[addressKey] || {})) {
154
- chainsWithBalances.add(chainId);
155
- }
156
- }
157
- // Only process chains that are explicitly mentioned in the incoming state change
158
130
  const incomingChainIds = new Set([
159
131
  ...Object.keys(state.allTokens),
160
132
  ...Object.keys(state.allDetectedTokens),
161
133
  ]);
162
- // Only proceed if there are actual changes to chains that have balances or are being added
163
134
  const relevantChainIds = Array.from(incomingChainIds).filter((chainId) => {
164
135
  const id = chainId;
165
136
  const hasTokensNow = (state.allTokens[id] && Object.keys(state.allTokens[id]).length > 0) ||
@@ -168,20 +139,16 @@ export class TokenBalancesController extends StaticIntervalPollingController() {
168
139
  const hadTokensBefore = (__classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")[id] && Object.keys(__classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")[id]).length > 0) ||
169
140
  (__classPrivateFieldGet(this, _TokenBalancesController_detectedTokens, "f")[id] &&
170
141
  Object.keys(__classPrivateFieldGet(this, _TokenBalancesController_detectedTokens, "f")[id]).length > 0);
171
- // Check if there's an actual change in token state
172
142
  const hasTokenChange = !isEqual(state.allTokens[id], __classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")[id]) ||
173
143
  !isEqual(state.allDetectedTokens[id], __classPrivateFieldGet(this, _TokenBalancesController_detectedTokens, "f")[id]);
174
- // Process chains that have actual changes OR are new chains getting tokens
175
144
  return hasTokenChange || (!hadTokensBefore && hasTokensNow);
176
145
  });
177
- if (relevantChainIds.length === 0) {
178
- // No relevant changes, just update internal state
146
+ if (!relevantChainIds.length) {
179
147
  __classPrivateFieldSet(this, _TokenBalancesController_allTokens, state.allTokens, "f");
180
148
  __classPrivateFieldSet(this, _TokenBalancesController_detectedTokens, state.allDetectedTokens, "f");
181
149
  return;
182
150
  }
183
- // Handle both cleanup and updates in a single state update
184
- this.update((s) => {
151
+ this.update((currentState) => {
185
152
  for (const chainId of relevantChainIds) {
186
153
  const id = chainId;
187
154
  const hasTokensNow = (state.allTokens[id] &&
@@ -192,20 +159,20 @@ export class TokenBalancesController extends StaticIntervalPollingController() {
192
159
  Object.keys(__classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")[id]).length > 0) ||
193
160
  (__classPrivateFieldGet(this, _TokenBalancesController_detectedTokens, "f")[id] &&
194
161
  Object.keys(__classPrivateFieldGet(this, _TokenBalancesController_detectedTokens, "f")[id]).length > 0);
195
- if (!isEqual(state.allTokens[id], __classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")[id]) ||
196
- !isEqual(state.allDetectedTokens[id], __classPrivateFieldGet(this, _TokenBalancesController_detectedTokens, "f")[id])) {
197
- if (hasTokensNow) {
198
- // Chain still has tokens - mark for async balance update
199
- changed.push(id);
200
- }
201
- else if (hadTokensBefore) {
202
- // Chain had tokens before but doesn't now - clean up balances immediately
203
- for (const address of Object.keys(s.tokenBalances)) {
204
- const addressKey = address;
205
- if (s.tokenBalances[addressKey]?.[id]) {
206
- s.tokenBalances[addressKey][id] = {};
207
- hasChanges = true;
208
- }
162
+ const tokensChanged = !isEqual(state.allTokens[id], __classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")[id]) ||
163
+ !isEqual(state.allDetectedTokens[id], __classPrivateFieldGet(this, _TokenBalancesController_detectedTokens, "f")[id]);
164
+ if (!tokensChanged) {
165
+ continue;
166
+ }
167
+ if (hasTokensNow) {
168
+ changed.push(id);
169
+ }
170
+ else if (hadTokensBefore) {
171
+ for (const address of Object.keys(currentState.tokenBalances)) {
172
+ const addressKey = address;
173
+ if (currentState.tokenBalances[addressKey]?.[id]) {
174
+ currentState.tokenBalances[addressKey][id] = {};
175
+ hasChanges = true;
209
176
  }
210
177
  }
211
178
  }
@@ -214,7 +181,6 @@ export class TokenBalancesController extends StaticIntervalPollingController() {
214
181
  __classPrivateFieldSet(this, _TokenBalancesController_allTokens, state.allTokens, "f");
215
182
  __classPrivateFieldSet(this, _TokenBalancesController_detectedTokens, state.allDetectedTokens, "f");
216
183
  __classPrivateFieldSet(this, _TokenBalancesController_allIgnoredTokens, state.allIgnoredTokens, "f");
217
- // Only update balances for chains that still have tokens (and only if we haven't already updated state)
218
184
  if (changed.length && !hasChanges) {
219
185
  this.updateBalances({ chainIds: changed }).catch((error) => {
220
186
  console.warn('Error updating balances after token change:', error);
@@ -222,9 +188,7 @@ export class TokenBalancesController extends StaticIntervalPollingController() {
222
188
  }
223
189
  });
224
190
  _TokenBalancesController_onNetworkChanged.set(this, (state) => {
225
- // Check if any networks were removed by comparing with previous state
226
191
  const currentNetworks = new Set(Object.keys(state.networkConfigurationsByChainId));
227
- // Get all networks that currently have balances
228
192
  const networksWithBalances = new Set();
229
193
  for (const address of Object.keys(this.state.tokenBalances)) {
230
194
  const addressKey = address;
@@ -232,83 +196,59 @@ export class TokenBalancesController extends StaticIntervalPollingController() {
232
196
  networksWithBalances.add(network);
233
197
  }
234
198
  }
235
- // Find networks that were removed
236
199
  const removedNetworks = Array.from(networksWithBalances).filter((network) => !currentNetworks.has(network));
237
- if (removedNetworks.length > 0) {
238
- this.update((s) => {
239
- // Remove balances for all accounts on the deleted networks
240
- for (const address of Object.keys(s.tokenBalances)) {
241
- const addressKey = address;
242
- for (const removedNetwork of removedNetworks) {
243
- const networkKey = removedNetwork;
244
- if (s.tokenBalances[addressKey]?.[networkKey]) {
245
- delete s.tokenBalances[addressKey][networkKey];
246
- }
200
+ if (!removedNetworks.length) {
201
+ return;
202
+ }
203
+ this.update((currentState) => {
204
+ for (const address of Object.keys(currentState.tokenBalances)) {
205
+ const addressKey = address;
206
+ for (const removedNetwork of removedNetworks) {
207
+ const networkKey = removedNetwork;
208
+ if (currentState.tokenBalances[addressKey]?.[networkKey]) {
209
+ delete currentState.tokenBalances[addressKey][networkKey];
247
210
  }
248
211
  }
249
- });
250
- }
212
+ }
213
+ });
251
214
  });
252
215
  _TokenBalancesController_onAccountRemoved.set(this, (addr) => {
253
216
  if (!isStrictHexString(addr) || !isValidHexAddress(addr)) {
254
217
  return;
255
218
  }
256
- this.update((s) => {
257
- delete s.tokenBalances[addr];
219
+ this.update((currentState) => {
220
+ delete currentState.tokenBalances[addr];
258
221
  });
259
222
  });
260
- /**
261
- * Handle account selection changes
262
- * Triggers immediate balance fetch to ensure we have the latest balances
263
- * since WebSocket only provides updates for changes going forward
264
- */
265
223
  _TokenBalancesController_onAccountChanged.set(this, () => {
266
- // Fetch balances for all chains with tokens when account changes
267
224
  const chainIds = __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_chainIdsWithTokens).call(this);
268
- if (chainIds.length > 0) {
269
- this.updateBalances({ chainIds }).catch(() => {
270
- // Silently handle polling errors
271
- });
225
+ if (!chainIds.length) {
226
+ return;
272
227
  }
228
+ this.updateBalances({ chainIds }).catch(() => {
229
+ // Silently handle polling errors
230
+ });
273
231
  });
274
- // ────────────────────────────────────────────────────────────────────────────
275
- // AccountActivityService event handlers
276
- /**
277
- * Handle real-time balance updates from AccountActivityService
278
- * Processes balance updates and updates the token balance state
279
- * If any balance update has an error, triggers fallback polling for the chain
280
- *
281
- * @param options0 - Balance update parameters
282
- * @param options0.address - Account address
283
- * @param options0.chain - CAIP chain identifier
284
- * @param options0.updates - Array of balance updates for the account
285
- */
286
232
  _TokenBalancesController_onAccountActivityBalanceUpdate.set(this, async ({ address, chain, updates, }) => {
287
233
  const chainId = caipChainIdToHex(chain);
288
234
  const checksummedAccount = checksum(address);
289
235
  try {
290
- // Process all balance updates at once
291
236
  const { tokenBalances, newTokens, nativeBalanceUpdates } = __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_prepareBalanceUpdates).call(this, updates, checksummedAccount, chainId);
292
- // Update state once with all token balances
293
237
  if (tokenBalances.length > 0) {
294
238
  this.update((state) => {
295
239
  var _a, _b;
296
- // Temporary until ADR to normalize all keys - tokenBalances state requires: account in lowercase, token in checksum
297
240
  const lowercaseAccount = checksummedAccount.toLowerCase();
298
241
  (_a = state.tokenBalances)[lowercaseAccount] ?? (_a[lowercaseAccount] = {});
299
242
  (_b = state.tokenBalances[lowercaseAccount])[chainId] ?? (_b[chainId] = {});
300
- // Apply all token balance updates
301
243
  for (const { tokenAddress, balance } of tokenBalances) {
302
244
  state.tokenBalances[lowercaseAccount][chainId][tokenAddress] =
303
245
  balance;
304
246
  }
305
247
  });
306
248
  }
307
- // Update native balances in AccountTrackerController
308
249
  if (nativeBalanceUpdates.length > 0) {
309
250
  this.messenger.call('AccountTrackerController:updateNativeBalances', nativeBalanceUpdates);
310
251
  }
311
- // Import any new tokens that were discovered (balance already updated from websocket)
312
252
  if (newTokens.length > 0) {
313
253
  await this.messenger.call('TokenDetectionController:addDetectedTokensViaWs', {
314
254
  tokensSlice: newTokens,
@@ -319,35 +259,22 @@ export class TokenBalancesController extends StaticIntervalPollingController() {
319
259
  catch (error) {
320
260
  console.warn(`Error updating balances from AccountActivityService for chain ${chain}, account ${address}:`, error);
321
261
  console.warn('Balance update data:', JSON.stringify(updates, null, 2));
322
- // On error, trigger fallback polling
323
262
  await this.updateBalances({ chainIds: [chainId] }).catch(() => {
324
263
  // Silently handle polling errors
325
264
  });
326
265
  }
327
266
  });
328
- /**
329
- * Handle status changes from AccountActivityService
330
- * Uses aggressive debouncing to prevent excessive HTTP calls from rapid up/down changes
331
- *
332
- * @param options0 - Status change event data
333
- * @param options0.chainIds - Array of chain identifiers
334
- * @param options0.status - Connection status ('up' for connected, 'down' for disconnected)
335
- */
336
267
  _TokenBalancesController_onAccountActivityStatusChanged.set(this, ({ chainIds, status, }) => {
337
- // Update pending changes (latest status wins for each chain)
338
268
  for (const chainId of chainIds) {
339
269
  __classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").pendingChanges.set(chainId, status);
340
270
  }
341
- // Clear existing timer to extend debounce window
342
271
  if (__classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").timer) {
343
272
  clearTimeout(__classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").timer);
344
273
  }
345
- // Set new timer - only process changes after activity settles
346
274
  __classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").timer = setTimeout(() => {
347
275
  __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_processAccumulatedStatusChanges).call(this);
348
- }, 5000); // 5-second debounce window
276
+ }, 5000);
349
277
  });
350
- // Normalize all account addresses to lowercase in existing state
351
278
  __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_normalizeAccountAddresses).call(this);
352
279
  __classPrivateFieldSet(this, _TokenBalancesController_platform, platform ?? 'extension', "f");
353
280
  __classPrivateFieldSet(this, _TokenBalancesController_queryAllAccounts, queryMultipleAccounts, "f");
@@ -355,7 +282,6 @@ export class TokenBalancesController extends StaticIntervalPollingController() {
355
282
  __classPrivateFieldSet(this, _TokenBalancesController_defaultInterval, interval, "f");
356
283
  __classPrivateFieldSet(this, _TokenBalancesController_websocketActivePollingInterval, websocketActivePollingInterval, "f");
357
284
  __classPrivateFieldSet(this, _TokenBalancesController_chainPollingConfig, { ...chainPollingIntervals }, "f");
358
- // Strategy order: API first, then RPC fallback
359
285
  __classPrivateFieldSet(this, _TokenBalancesController_balanceFetchers, [
360
286
  ...(accountsApiChainIds().length > 0 && allowExternalServices()
361
287
  ? [__classPrivateFieldGet(this, _TokenBalancesController_createAccountsApiFetcher, "f").call(this)]
@@ -366,302 +292,172 @@ export class TokenBalancesController extends StaticIntervalPollingController() {
366
292
  })),
367
293
  ], "f");
368
294
  this.setIntervalLength(interval);
369
- // initial token state & subscriptions
370
295
  const { allTokens, allDetectedTokens, allIgnoredTokens } = this.messenger.call('TokensController:getState');
371
296
  __classPrivateFieldSet(this, _TokenBalancesController_allTokens, allTokens, "f");
372
297
  __classPrivateFieldSet(this, _TokenBalancesController_detectedTokens, allDetectedTokens, "f");
373
298
  __classPrivateFieldSet(this, _TokenBalancesController_allIgnoredTokens, allIgnoredTokens, "f");
374
- this.messenger.subscribe('TokensController:stateChange', (tokensState) => {
375
- __classPrivateFieldGet(this, _TokenBalancesController_onTokensChanged, "f").call(this, tokensState).catch((error) => {
376
- console.warn('Error handling token state change:', error);
377
- });
378
- });
379
- this.messenger.subscribe('NetworkController:stateChange', __classPrivateFieldGet(this, _TokenBalancesController_onNetworkChanged, "f"));
380
- this.messenger.subscribe('KeyringController:accountRemoved', __classPrivateFieldGet(this, _TokenBalancesController_onAccountRemoved, "f"));
381
- this.messenger.subscribe('AccountsController:selectedEvmAccountChange', __classPrivateFieldGet(this, _TokenBalancesController_onAccountChanged, "f"));
382
- // Register action handlers for polling interval control
383
- this.messenger.registerActionHandler(`TokenBalancesController:updateChainPollingConfigs`, this.updateChainPollingConfigs.bind(this));
384
- this.messenger.registerActionHandler(`TokenBalancesController:getChainPollingConfig`, this.getChainPollingConfig.bind(this));
385
- // Subscribe to AccountActivityService balance updates for real-time updates
386
- this.messenger.subscribe('AccountActivityService:balanceUpdated', __classPrivateFieldGet(this, _TokenBalancesController_onAccountActivityBalanceUpdate, "f").bind(this));
387
- // Subscribe to AccountActivityService status changes for dynamic polling management
388
- this.messenger.subscribe('AccountActivityService:statusChanged', __classPrivateFieldGet(this, _TokenBalancesController_onAccountActivityStatusChanged, "f").bind(this));
299
+ const { isUnlocked } = this.messenger.call('KeyringController:getState');
300
+ __classPrivateFieldSet(this, _TokenBalancesController_isUnlocked, isUnlocked, "f");
301
+ __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_subscribeToControllers).call(this);
302
+ __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_registerActions).call(this);
389
303
  }
390
304
  /**
391
- * Override to support per-chain polling intervals by grouping chains by interval
305
+ * Whether the controller is active (keyring is unlocked).
306
+ * When locked, balance updates should be skipped.
392
307
  *
393
- * @param options0 - The polling options
394
- * @param options0.chainIds - Chain IDs to start polling for
308
+ * @returns Whether the keyring is unlocked.
395
309
  */
310
+ get isActive() {
311
+ return __classPrivateFieldGet(this, _TokenBalancesController_isUnlocked, "f");
312
+ }
396
313
  _startPolling({ chainIds }) {
397
- // Store the original chainIds to preserve intent across config updates
398
314
  __classPrivateFieldSet(this, _TokenBalancesController_requestedChainIds, [...chainIds], "f");
399
315
  __classPrivateFieldSet(this, _TokenBalancesController_isControllerPollingActive, true, "f");
400
316
  __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_startIntervalGroupPolling).call(this, chainIds, true);
401
317
  }
402
- /**
403
- * Override to handle our custom polling approach
404
- *
405
- * @param tokenSetId - The token set ID to stop polling for
406
- */
407
318
  _stopPollingByPollingTokenSetId(tokenSetId) {
408
- let parsedTokenSetId;
409
319
  let chainsToStop = [];
410
320
  try {
411
- parsedTokenSetId = JSON.parse(tokenSetId);
412
- chainsToStop = parsedTokenSetId.chainIds || [];
321
+ const parsedTokenSetId = JSON.parse(tokenSetId);
322
+ chainsToStop = parsedTokenSetId.chainIds ?? [];
413
323
  }
414
324
  catch (error) {
415
325
  console.warn('Failed to parse tokenSetId, stopping all polling:', error);
416
- // Fallback: stop all polling if we can't parse the tokenSetId
417
- __classPrivateFieldSet(this, _TokenBalancesController_isControllerPollingActive, false, "f");
418
- __classPrivateFieldSet(this, _TokenBalancesController_requestedChainIds, [], "f");
419
- __classPrivateFieldGet(this, _TokenBalancesController_intervalPollingTimers, "f").forEach((timer) => clearInterval(timer));
420
- __classPrivateFieldGet(this, _TokenBalancesController_intervalPollingTimers, "f").clear();
326
+ __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_stopAllPolling).call(this);
421
327
  return;
422
328
  }
423
- // Compare with current chains - only stop if it matches our current session
424
329
  const currentChainsSet = new Set(__classPrivateFieldGet(this, _TokenBalancesController_requestedChainIds, "f"));
425
330
  const stopChainsSet = new Set(chainsToStop);
426
- // Check if this stop request is for our current session
427
331
  const isCurrentSession = currentChainsSet.size === stopChainsSet.size &&
428
332
  [...currentChainsSet].every((chain) => stopChainsSet.has(chain));
429
333
  if (isCurrentSession) {
430
- __classPrivateFieldSet(this, _TokenBalancesController_isControllerPollingActive, false, "f");
431
- __classPrivateFieldSet(this, _TokenBalancesController_requestedChainIds, [], "f");
432
- __classPrivateFieldGet(this, _TokenBalancesController_intervalPollingTimers, "f").forEach((timer) => clearInterval(timer));
433
- __classPrivateFieldGet(this, _TokenBalancesController_intervalPollingTimers, "f").clear();
334
+ __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_stopAllPolling).call(this);
434
335
  }
435
336
  }
436
- /**
437
- * Get polling configuration for a chain (includes default fallback)
438
- *
439
- * @param chainId - The chain ID to get config for
440
- * @returns The polling configuration for the chain
441
- */
442
337
  getChainPollingConfig(chainId) {
443
338
  return (__classPrivateFieldGet(this, _TokenBalancesController_chainPollingConfig, "f")[chainId] ?? {
444
339
  interval: __classPrivateFieldGet(this, _TokenBalancesController_defaultInterval, "f"),
445
340
  });
446
341
  }
447
342
  async _executePoll({ chainIds, queryAllAccounts = false, }) {
448
- // This won't be called with our custom implementation, but keep for compatibility
449
343
  await this.updateBalances({ chainIds, queryAllAccounts });
450
344
  }
451
- /**
452
- * Update multiple chain polling configurations at once
453
- *
454
- * @param configs - Object mapping chain IDs to polling configurations
455
- * @param options - Optional configuration for the update behavior
456
- * @param options.immediateUpdate - Whether to immediately fetch balances after updating configs (default: true)
457
- */
458
345
  updateChainPollingConfigs(configs, options = { immediateUpdate: true }) {
459
346
  Object.assign(__classPrivateFieldGet(this, _TokenBalancesController_chainPollingConfig, "f"), configs);
460
- // If polling is currently active, restart with new interval groupings
461
347
  if (__classPrivateFieldGet(this, _TokenBalancesController_isControllerPollingActive, "f")) {
462
- // Restart polling with immediate fetch by default, unless explicitly disabled
463
348
  __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_startIntervalGroupPolling).call(this, __classPrivateFieldGet(this, _TokenBalancesController_requestedChainIds, "f"), options.immediateUpdate);
464
349
  }
465
350
  }
466
- async updateBalances({ chainIds, queryAllAccounts = false, } = {}) {
467
- const targetChains = chainIds ?? __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_chainIdsWithTokens).call(this);
468
- if (!targetChains.length) {
351
+ async updateBalances({ chainIds, tokenAddresses, queryAllAccounts = false, } = {}) {
352
+ if (!this.isActive) {
469
353
  return;
470
354
  }
471
- const { address: selected } = this.messenger.call('AccountsController:getSelectedAccount');
472
- const allAccounts = this.messenger.call('AccountsController:listAccounts');
473
- const jwtToken = await safelyExecuteWithTimeout(() => {
474
- return this.messenger.call('AuthenticationController:getBearerToken');
475
- }, false, 5000);
476
- const aggregated = [];
477
- let remainingChains = [...targetChains];
478
- // Try each fetcher in order, removing successfully processed chains
479
- for (const fetcher of __classPrivateFieldGet(this, _TokenBalancesController_balanceFetchers, "f")) {
480
- const supportedChains = remainingChains.filter((c) => fetcher.supports(c));
481
- if (!supportedChains.length) {
482
- continue;
483
- }
484
- try {
485
- const result = await fetcher.fetch({
486
- chainIds: supportedChains,
487
- queryAllAccounts: queryAllAccounts ?? __classPrivateFieldGet(this, _TokenBalancesController_queryAllAccounts, "f"),
488
- selectedAccount: selected,
489
- allAccounts,
490
- jwtToken,
491
- });
492
- if (result.balances && result.balances.length > 0) {
493
- aggregated.push(...result.balances);
494
- // Remove chains that were successfully processed
495
- const processedChains = new Set(result.balances.map((b) => b.chainId));
496
- remainingChains = remainingChains.filter((chain) => !processedChains.has(chain));
497
- }
498
- // Add unprocessed chains back to remainingChains for next fetcher
499
- if (result.unprocessedChainIds &&
500
- result.unprocessedChainIds.length > 0) {
501
- const currentRemainingChains = remainingChains;
502
- const chainsToAdd = result.unprocessedChainIds.filter((chainId) => supportedChains.includes(chainId) &&
503
- !currentRemainingChains.includes(chainId));
504
- remainingChains.push(...chainsToAdd);
505
- }
506
- }
507
- catch (error) {
508
- console.warn(`Balance fetcher failed for chains ${supportedChains.join(', ')}: ${String(error)}`);
509
- // Continue to next fetcher (fallback)
510
- }
511
- // If all chains have been processed, break early
512
- if (remainingChains.length === 0) {
513
- break;
514
- }
355
+ const targetChains = __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_getTargetChains).call(this, chainIds);
356
+ if (!targetChains.length) {
357
+ return;
515
358
  }
516
- // Determine which accounts to process based on queryAllAccounts parameter
517
- const accountsToProcess = (queryAllAccounts ?? __classPrivateFieldGet(this, _TokenBalancesController_queryAllAccounts, "f"))
518
- ? allAccounts.map((a) => a.address)
519
- : [selected];
359
+ const { selectedAccount, allAccounts, jwtToken } = await __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_getAccountsAndJwt).call(this);
360
+ const aggregatedBalances = await __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_fetchAllBalances).call(this, {
361
+ targetChains,
362
+ selectedAccount,
363
+ allAccounts,
364
+ jwtToken,
365
+ queryAllAccounts: queryAllAccounts ?? __classPrivateFieldGet(this, _TokenBalancesController_queryAllAccounts, "f"),
366
+ });
367
+ const filteredAggregated = __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_filterByTokenAddresses).call(this, aggregatedBalances, tokenAddresses);
368
+ const accountsToProcess = __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_getAccountsToProcess).call(this, queryAllAccounts, allAccounts, selectedAccount);
520
369
  const prev = this.state;
521
- const next = draft(prev, (d) => {
522
- var _a, _b;
523
- // Initialize account and chain structures if they don't exist, but preserve existing balances
524
- for (const chainId of targetChains) {
525
- for (const account of accountsToProcess) {
526
- // Ensure the nested structure exists without overwriting existing balances
527
- (_a = d.tokenBalances)[account] ?? (_a[account] = {});
528
- (_b = d.tokenBalances[account])[chainId] ?? (_b[chainId] = {});
529
- // Initialize tokens from allTokens only if they don't exist yet
530
- const chainTokens = __classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")[chainId];
531
- if (chainTokens?.[account]) {
532
- Object.values(chainTokens[account]).forEach((token) => {
533
- const tokenAddress = checksum(token.address);
534
- // Only initialize if the token balance doesn't exist yet
535
- if (!(tokenAddress in d.tokenBalances[account][chainId])) {
536
- d.tokenBalances[account][chainId][tokenAddress] = '0x0';
537
- }
538
- });
539
- }
540
- // Initialize tokens from allDetectedTokens only if they don't exist yet
541
- const detectedChainTokens = __classPrivateFieldGet(this, _TokenBalancesController_detectedTokens, "f")[chainId];
542
- if (detectedChainTokens?.[account]) {
543
- Object.values(detectedChainTokens[account]).forEach((token) => {
544
- const tokenAddress = checksum(token.address);
545
- // Only initialize if the token balance doesn't exist yet
546
- if (!(tokenAddress in d.tokenBalances[account][chainId])) {
547
- d.tokenBalances[account][chainId][tokenAddress] = '0x0';
548
- }
549
- });
550
- }
551
- }
552
- }
553
- // Update with actual fetched balances only if the value has changed
554
- aggregated.forEach(({ success, value, account, token, chainId }) => {
555
- var _a, _b;
556
- if (success && value !== undefined) {
557
- // Ensure all accounts we add/update are in lower-case
558
- const lowerCaseAccount = account.toLowerCase();
559
- const newBalance = toHex(value);
560
- const tokenAddress = checksum(token);
561
- const currentBalance = d.tokenBalances[lowerCaseAccount]?.[chainId]?.[tokenAddress];
562
- // Only update if the balance has actually changed
563
- if (currentBalance !== newBalance) {
564
- ((_b = ((_a = d.tokenBalances)[lowerCaseAccount] ?? (_a[lowerCaseAccount] = {})))[chainId] ?? (_b[chainId] = {}))[tokenAddress] = newBalance;
565
- }
566
- }
567
- });
370
+ const next = __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_applyTokenBalancesToState).call(this, {
371
+ prev,
372
+ targetChains,
373
+ accountsToProcess,
374
+ balances: filteredAggregated,
568
375
  });
569
376
  if (!isEqual(prev, next)) {
570
377
  this.update(() => next);
571
- const nativeBalances = aggregated.filter((r) => r.success && r.token === ZERO_ADDRESS);
572
- // Get current AccountTracker state to compare existing balances
573
378
  const accountTrackerState = this.messenger.call('AccountTrackerController:getState');
574
- // Update native token balances only if they have changed
575
- if (nativeBalances.length > 0) {
576
- const balanceUpdates = nativeBalances
577
- .map((balance) => ({
578
- address: balance.account,
579
- chainId: balance.chainId,
580
- balance: balance.value ? BNToHex(balance.value) : '0x0',
581
- }))
582
- .filter((update) => {
583
- const currentBalance = accountTrackerState.accountsByChainId[update.chainId]?.[checksum(update.address)]?.balance;
584
- // Only include if the balance has actually changed
585
- return currentBalance !== update.balance;
586
- });
587
- if (balanceUpdates.length > 0) {
588
- this.messenger.call('AccountTrackerController:updateNativeBalances', balanceUpdates);
589
- }
379
+ const nativeUpdates = __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_buildNativeBalanceUpdates).call(this, filteredAggregated, accountTrackerState);
380
+ if (nativeUpdates.length > 0) {
381
+ this.messenger.call('AccountTrackerController:updateNativeBalances', nativeUpdates);
590
382
  }
591
- // Filter and update staked balances in a single batch operation for better performance
592
- const stakedBalances = aggregated.filter((r) => {
593
- if (!r.success || r.token === ZERO_ADDRESS) {
594
- return false;
595
- }
596
- // Check if the chainId and token address match any staking contract
597
- const stakingContractAddress = STAKING_CONTRACT_ADDRESS_BY_CHAINID[r.chainId];
598
- return (stakingContractAddress &&
599
- stakingContractAddress.toLowerCase() === r.token.toLowerCase());
600
- });
601
- if (stakedBalances.length > 0) {
602
- const stakedBalanceUpdates = stakedBalances
603
- .map((balance) => ({
604
- address: balance.account,
605
- chainId: balance.chainId,
606
- stakedBalance: balance.value ? toHex(balance.value) : '0x0',
607
- }))
608
- .filter((update) => {
609
- const currentStakedBalance = accountTrackerState.accountsByChainId[update.chainId]?.[checksum(update.address)]?.stakedBalance;
610
- // Only include if the staked balance has actually changed
611
- return currentStakedBalance !== update.stakedBalance;
612
- });
613
- if (stakedBalanceUpdates.length > 0) {
614
- this.messenger.call('AccountTrackerController:updateStakedBalances', stakedBalanceUpdates);
615
- }
383
+ const stakedUpdates = __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_buildStakedBalanceUpdates).call(this, filteredAggregated, accountTrackerState);
384
+ if (stakedUpdates.length > 0) {
385
+ this.messenger.call('AccountTrackerController:updateStakedBalances', stakedUpdates);
616
386
  }
617
387
  }
388
+ await __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_importUntrackedTokens).call(this, filteredAggregated);
618
389
  }
619
390
  resetState() {
620
391
  this.update(() => ({ tokenBalances: {} }));
621
392
  }
622
- /**
623
- * Clean up all timers and resources when controller is destroyed
624
- */
625
393
  destroy() {
626
394
  __classPrivateFieldSet(this, _TokenBalancesController_isControllerPollingActive, false, "f");
627
395
  __classPrivateFieldGet(this, _TokenBalancesController_intervalPollingTimers, "f").forEach((timer) => clearInterval(timer));
628
396
  __classPrivateFieldGet(this, _TokenBalancesController_intervalPollingTimers, "f").clear();
629
- // Clean up debouncing timer
630
397
  if (__classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").timer) {
631
398
  clearTimeout(__classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").timer);
632
399
  __classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").timer = null;
633
400
  }
634
- // Unregister action handlers
635
401
  this.messenger.unregisterActionHandler(`TokenBalancesController:updateChainPollingConfigs`);
636
402
  this.messenger.unregisterActionHandler(`TokenBalancesController:getChainPollingConfig`);
637
403
  super.destroy();
638
404
  }
639
405
  }
640
- _TokenBalancesController_platform = new WeakMap(), _TokenBalancesController_queryAllAccounts = new WeakMap(), _TokenBalancesController_accountsApiChainIds = new WeakMap(), _TokenBalancesController_balanceFetchers = new WeakMap(), _TokenBalancesController_allTokens = new WeakMap(), _TokenBalancesController_detectedTokens = new WeakMap(), _TokenBalancesController_allIgnoredTokens = new WeakMap(), _TokenBalancesController_defaultInterval = new WeakMap(), _TokenBalancesController_websocketActivePollingInterval = new WeakMap(), _TokenBalancesController_chainPollingConfig = new WeakMap(), _TokenBalancesController_intervalPollingTimers = new WeakMap(), _TokenBalancesController_isControllerPollingActive = new WeakMap(), _TokenBalancesController_requestedChainIds = new WeakMap(), _TokenBalancesController_statusChangeDebouncer = new WeakMap(), _TokenBalancesController_getProvider = new WeakMap(), _TokenBalancesController_getNetworkClient = new WeakMap(), _TokenBalancesController_createAccountsApiFetcher = new WeakMap(), _TokenBalancesController_onTokensChanged = new WeakMap(), _TokenBalancesController_onNetworkChanged = new WeakMap(), _TokenBalancesController_onAccountRemoved = new WeakMap(), _TokenBalancesController_onAccountChanged = new WeakMap(), _TokenBalancesController_onAccountActivityBalanceUpdate = new WeakMap(), _TokenBalancesController_onAccountActivityStatusChanged = new WeakMap(), _TokenBalancesController_instances = new WeakSet(), _TokenBalancesController_normalizeAccountAddresses = function _TokenBalancesController_normalizeAccountAddresses() {
406
+ _TokenBalancesController_platform = new WeakMap(), _TokenBalancesController_queryAllAccounts = new WeakMap(), _TokenBalancesController_accountsApiChainIds = new WeakMap(), _TokenBalancesController_balanceFetchers = new WeakMap(), _TokenBalancesController_allTokens = new WeakMap(), _TokenBalancesController_detectedTokens = new WeakMap(), _TokenBalancesController_allIgnoredTokens = new WeakMap(), _TokenBalancesController_defaultInterval = new WeakMap(), _TokenBalancesController_websocketActivePollingInterval = new WeakMap(), _TokenBalancesController_chainPollingConfig = new WeakMap(), _TokenBalancesController_intervalPollingTimers = new WeakMap(), _TokenBalancesController_isControllerPollingActive = new WeakMap(), _TokenBalancesController_isUnlocked = new WeakMap(), _TokenBalancesController_requestedChainIds = new WeakMap(), _TokenBalancesController_statusChangeDebouncer = new WeakMap(), _TokenBalancesController_getProvider = new WeakMap(), _TokenBalancesController_getNetworkClient = new WeakMap(), _TokenBalancesController_createAccountsApiFetcher = new WeakMap(), _TokenBalancesController_onTokensChanged = new WeakMap(), _TokenBalancesController_onNetworkChanged = new WeakMap(), _TokenBalancesController_onAccountRemoved = new WeakMap(), _TokenBalancesController_onAccountChanged = new WeakMap(), _TokenBalancesController_onAccountActivityBalanceUpdate = new WeakMap(), _TokenBalancesController_onAccountActivityStatusChanged = new WeakMap(), _TokenBalancesController_instances = new WeakSet(), _TokenBalancesController_subscribeToControllers = function _TokenBalancesController_subscribeToControllers() {
407
+ this.messenger.subscribe('TokensController:stateChange', (tokensState) => {
408
+ __classPrivateFieldGet(this, _TokenBalancesController_onTokensChanged, "f").call(this, tokensState).catch((error) => {
409
+ console.warn('Error handling token state change:', error);
410
+ });
411
+ });
412
+ this.messenger.subscribe('NetworkController:stateChange', __classPrivateFieldGet(this, _TokenBalancesController_onNetworkChanged, "f"));
413
+ this.messenger.subscribe('KeyringController:unlock', () => {
414
+ __classPrivateFieldSet(this, _TokenBalancesController_isUnlocked, true, "f");
415
+ });
416
+ this.messenger.subscribe('KeyringController:lock', () => {
417
+ __classPrivateFieldSet(this, _TokenBalancesController_isUnlocked, false, "f");
418
+ });
419
+ this.messenger.subscribe('KeyringController:accountRemoved', __classPrivateFieldGet(this, _TokenBalancesController_onAccountRemoved, "f"));
420
+ this.messenger.subscribe('AccountsController:selectedEvmAccountChange', __classPrivateFieldGet(this, _TokenBalancesController_onAccountChanged, "f"));
421
+ this.messenger.subscribe('AccountActivityService:balanceUpdated', (event) => {
422
+ __classPrivateFieldGet(this, _TokenBalancesController_onAccountActivityBalanceUpdate, "f").call(this, event).catch((error) => {
423
+ console.warn('Error handling balance update:', error);
424
+ });
425
+ });
426
+ this.messenger.subscribe('AccountActivityService:statusChanged', __classPrivateFieldGet(this, _TokenBalancesController_onAccountActivityStatusChanged, "f").bind(this));
427
+ this.messenger.subscribe('TransactionController:transactionConfirmed', (transactionMeta) => {
428
+ this.updateBalances({
429
+ chainIds: [transactionMeta.chainId],
430
+ }).catch(() => {
431
+ // Silently handle balance update errors
432
+ });
433
+ });
434
+ this.messenger.subscribe('TransactionController:incomingTransactionsReceived', (incomingTransactions) => {
435
+ this.updateBalances({
436
+ chainIds: incomingTransactions.map((tx) => tx.chainId),
437
+ }).catch(() => {
438
+ // Silently handle balance update errors
439
+ });
440
+ });
441
+ }, _TokenBalancesController_registerActions = function _TokenBalancesController_registerActions() {
442
+ this.messenger.registerActionHandler(`TokenBalancesController:updateChainPollingConfigs`, this.updateChainPollingConfigs.bind(this));
443
+ this.messenger.registerActionHandler(`TokenBalancesController:getChainPollingConfig`, this.getChainPollingConfig.bind(this));
444
+ }, _TokenBalancesController_normalizeAccountAddresses = function _TokenBalancesController_normalizeAccountAddresses() {
445
+ var _a;
641
446
  const currentState = this.state.tokenBalances;
642
447
  const normalizedBalances = {};
643
- // Iterate through all accounts and normalize to lowercase
644
448
  for (const address of Object.keys(currentState)) {
645
449
  const lowercaseAddress = address.toLowerCase();
646
450
  const accountBalances = currentState[address];
647
451
  if (!accountBalances) {
648
452
  continue;
649
453
  }
650
- // If this lowercase address doesn't exist yet, create it
651
- if (!normalizedBalances[lowercaseAddress]) {
652
- normalizedBalances[lowercaseAddress] = {};
653
- }
654
- // Merge chain data
454
+ normalizedBalances[lowercaseAddress] ?? (normalizedBalances[lowercaseAddress] = {});
655
455
  for (const chainId of Object.keys(accountBalances)) {
656
456
  const chainIdKey = chainId;
657
- if (!normalizedBalances[lowercaseAddress][chainIdKey]) {
658
- normalizedBalances[lowercaseAddress][chainIdKey] = {};
659
- }
660
- // Merge token balances (later values override earlier ones if duplicates exist)
457
+ (_a = normalizedBalances[lowercaseAddress])[chainIdKey] ?? (_a[chainIdKey] = {});
661
458
  Object.assign(normalizedBalances[lowercaseAddress][chainIdKey], accountBalances[chainIdKey]);
662
459
  }
663
460
  }
664
- // Only update if there were changes
665
461
  if (Object.keys(currentState).length !==
666
462
  Object.keys(normalizedBalances).length ||
667
463
  Object.keys(currentState).some((addr) => addr !== addr.toLowerCase())) {
@@ -675,18 +471,15 @@ _TokenBalancesController_platform = new WeakMap(), _TokenBalancesController_quer
675
471
  ]),
676
472
  ];
677
473
  }, _TokenBalancesController_startIntervalGroupPolling = function _TokenBalancesController_startIntervalGroupPolling(chainIds, immediate = true) {
678
- // Stop any existing interval timers
679
474
  __classPrivateFieldGet(this, _TokenBalancesController_intervalPollingTimers, "f").forEach((timer) => clearInterval(timer));
680
475
  __classPrivateFieldGet(this, _TokenBalancesController_intervalPollingTimers, "f").clear();
681
- // Group chains by their polling intervals
682
476
  const intervalGroups = new Map();
683
477
  for (const chainId of chainIds) {
684
478
  const config = this.getChainPollingConfig(chainId);
685
- const existing = intervalGroups.get(config.interval) || [];
686
- existing.push(chainId);
687
- intervalGroups.set(config.interval, existing);
479
+ const group = intervalGroups.get(config.interval) ?? [];
480
+ group.push(chainId);
481
+ intervalGroups.set(config.interval, group);
688
482
  }
689
- // Start separate polling loop for each interval group
690
483
  for (const [interval, chainIdsGroup] of intervalGroups) {
691
484
  __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_startPollingForInterval).call(this, interval, chainIdsGroup, immediate);
692
485
  }
@@ -702,33 +495,221 @@ _TokenBalancesController_platform = new WeakMap(), _TokenBalancesController_quer
702
495
  console.warn(`Polling failed for chains ${chainIds.join(', ')} with interval ${interval}:`, error);
703
496
  }
704
497
  };
705
- // Poll immediately first if requested
706
498
  if (immediate) {
707
499
  pollFunction().catch((error) => {
708
500
  console.warn(`Immediate polling failed for chains ${chainIds.join(', ')}:`, error);
709
501
  });
710
502
  }
711
- // Then start regular interval polling
712
503
  __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_setPollingTimer).call(this, interval, chainIds, pollFunction);
713
504
  }, _TokenBalancesController_setPollingTimer = function _TokenBalancesController_setPollingTimer(interval, chainIds, pollFunction) {
714
- // Clear any existing timer for this interval first
715
- const existingTimer = __classPrivateFieldGet(this, _TokenBalancesController_intervalPollingTimers, "f").get(interval);
716
- if (existingTimer) {
717
- clearInterval(existingTimer);
718
- }
719
505
  const timer = setInterval(() => {
720
506
  pollFunction().catch((error) => {
721
507
  console.warn(`Interval polling failed for chains ${chainIds.join(', ')}:`, error);
722
508
  });
723
509
  }, interval);
724
510
  __classPrivateFieldGet(this, _TokenBalancesController_intervalPollingTimers, "f").set(interval, timer);
511
+ }, _TokenBalancesController_stopAllPolling = function _TokenBalancesController_stopAllPolling() {
512
+ __classPrivateFieldSet(this, _TokenBalancesController_isControllerPollingActive, false, "f");
513
+ __classPrivateFieldSet(this, _TokenBalancesController_requestedChainIds, [], "f");
514
+ __classPrivateFieldGet(this, _TokenBalancesController_intervalPollingTimers, "f").forEach((timer) => clearInterval(timer));
515
+ __classPrivateFieldGet(this, _TokenBalancesController_intervalPollingTimers, "f").clear();
516
+ }, _TokenBalancesController_getTargetChains = function _TokenBalancesController_getTargetChains(chainIds) {
517
+ return chainIds?.length ? chainIds : __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_chainIdsWithTokens).call(this);
518
+ }, _TokenBalancesController_getAccountsAndJwt = async function _TokenBalancesController_getAccountsAndJwt() {
519
+ const { address: selected } = this.messenger.call('AccountsController:getSelectedAccount');
520
+ const allAccounts = this.messenger.call('AccountsController:listAccounts');
521
+ const jwtToken = await safelyExecuteWithTimeout(() => {
522
+ return this.messenger.call('AuthenticationController:getBearerToken');
523
+ }, false, 5000);
524
+ return {
525
+ selectedAccount: selected,
526
+ allAccounts,
527
+ jwtToken,
528
+ };
529
+ }, _TokenBalancesController_fetchAllBalances = async function _TokenBalancesController_fetchAllBalances({ targetChains, selectedAccount, allAccounts, jwtToken, queryAllAccounts, }) {
530
+ const aggregated = [];
531
+ let remainingChains = [...targetChains];
532
+ for (const fetcher of __classPrivateFieldGet(this, _TokenBalancesController_balanceFetchers, "f")) {
533
+ const supportedChains = remainingChains.filter((chain) => fetcher.supports(chain));
534
+ if (!supportedChains.length) {
535
+ continue;
536
+ }
537
+ try {
538
+ const result = await fetcher.fetch({
539
+ chainIds: supportedChains,
540
+ queryAllAccounts,
541
+ selectedAccount,
542
+ allAccounts,
543
+ jwtToken,
544
+ });
545
+ if (result.balances?.length) {
546
+ aggregated.push(...result.balances);
547
+ const processed = new Set(result.balances.map((b) => b.chainId));
548
+ remainingChains = remainingChains.filter((chain) => !processed.has(chain));
549
+ }
550
+ if (result.unprocessedChainIds?.length) {
551
+ const currentRemaining = [...remainingChains];
552
+ const chainsToAdd = result.unprocessedChainIds.filter((chainId) => supportedChains.includes(chainId) &&
553
+ !currentRemaining.includes(chainId));
554
+ remainingChains.push(...chainsToAdd);
555
+ this.messenger
556
+ .call('TokenDetectionController:detectTokens', {
557
+ chainIds: result.unprocessedChainIds,
558
+ forceRpc: true,
559
+ })
560
+ .catch(() => {
561
+ // Silently handle token detection errors
562
+ });
563
+ }
564
+ }
565
+ catch (error) {
566
+ console.warn(`Balance fetcher failed for chains ${supportedChains.join(', ')}: ${String(error)}`);
567
+ this.messenger
568
+ .call('TokenDetectionController:detectTokens', {
569
+ chainIds: supportedChains,
570
+ forceRpc: true,
571
+ })
572
+ .catch(() => {
573
+ // Silently handle token detection errors
574
+ });
575
+ }
576
+ if (!remainingChains.length) {
577
+ break;
578
+ }
579
+ }
580
+ return aggregated;
581
+ }, _TokenBalancesController_filterByTokenAddresses = function _TokenBalancesController_filterByTokenAddresses(balances, tokenAddresses) {
582
+ if (!tokenAddresses?.length) {
583
+ return balances;
584
+ }
585
+ const lowered = tokenAddresses.map((a) => a.toLowerCase());
586
+ return balances.filter((balance) => lowered.includes(balance.token.toLowerCase()));
587
+ }, _TokenBalancesController_getAccountsToProcess = function _TokenBalancesController_getAccountsToProcess(queryAllAccountsParam, allAccounts, selectedAccount) {
588
+ const effectiveQueryAll = queryAllAccountsParam ?? __classPrivateFieldGet(this, _TokenBalancesController_queryAllAccounts, "f") ?? false;
589
+ if (!effectiveQueryAll) {
590
+ return [selectedAccount];
591
+ }
592
+ return allAccounts.map((account) => account.address);
593
+ }, _TokenBalancesController_applyTokenBalancesToState = function _TokenBalancesController_applyTokenBalancesToState({ prev, targetChains, accountsToProcess, balances, }) {
594
+ return draft(prev, (draftState) => {
595
+ var _a, _b;
596
+ for (const chainId of targetChains) {
597
+ for (const account of accountsToProcess) {
598
+ (_a = draftState.tokenBalances)[account] ?? (_a[account] = {});
599
+ (_b = draftState.tokenBalances[account])[chainId] ?? (_b[chainId] = {});
600
+ const chainTokens = __classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")[chainId];
601
+ if (chainTokens?.[account]) {
602
+ Object.values(chainTokens[account]).forEach((token) => {
603
+ var _a;
604
+ const tokenAddress = checksum(token.address);
605
+ (_a = draftState.tokenBalances[account][chainId])[tokenAddress] ?? (_a[tokenAddress] = '0x0');
606
+ });
607
+ }
608
+ const detectedChainTokens = __classPrivateFieldGet(this, _TokenBalancesController_detectedTokens, "f")[chainId];
609
+ if (detectedChainTokens?.[account]) {
610
+ Object.values(detectedChainTokens[account]).forEach((token) => {
611
+ var _a;
612
+ const tokenAddress = checksum(token.address);
613
+ (_a = draftState.tokenBalances[account][chainId])[tokenAddress] ?? (_a[tokenAddress] = '0x0');
614
+ });
615
+ }
616
+ }
617
+ }
618
+ balances.forEach(({ success, value, account, token, chainId }) => {
619
+ var _a, _b;
620
+ if (!success || value === undefined) {
621
+ return;
622
+ }
623
+ const lowerCaseAccount = account.toLowerCase();
624
+ const newBalance = toHex(value);
625
+ const tokenAddress = checksum(token);
626
+ const currentBalance = draftState.tokenBalances[lowerCaseAccount]?.[chainId]?.[tokenAddress];
627
+ if (currentBalance !== newBalance) {
628
+ ((_b = ((_a = draftState.tokenBalances)[lowerCaseAccount] ?? (_a[lowerCaseAccount] = {})))[chainId] ?? (_b[chainId] = {}))[tokenAddress] = newBalance;
629
+ }
630
+ });
631
+ });
632
+ }, _TokenBalancesController_buildNativeBalanceUpdates = function _TokenBalancesController_buildNativeBalanceUpdates(balances, accountTrackerState) {
633
+ const nativeBalances = balances.filter((balance) => balance.success && balance.token === ZERO_ADDRESS);
634
+ if (!nativeBalances.length) {
635
+ return [];
636
+ }
637
+ return nativeBalances
638
+ .map((balance) => ({
639
+ address: balance.account,
640
+ chainId: balance.chainId,
641
+ balance: balance.value ? BNToHex(balance.value) : '0x0',
642
+ }))
643
+ .filter((update) => {
644
+ const currentBalance = accountTrackerState.accountsByChainId[update.chainId]?.[checksum(update.address)]?.balance;
645
+ return currentBalance !== update.balance;
646
+ });
647
+ }, _TokenBalancesController_buildStakedBalanceUpdates = function _TokenBalancesController_buildStakedBalanceUpdates(balances, accountTrackerState) {
648
+ const stakedBalances = balances.filter((balance) => {
649
+ if (!balance.success || balance.token === ZERO_ADDRESS) {
650
+ return false;
651
+ }
652
+ const stakingContractAddress = STAKING_CONTRACT_ADDRESS_BY_CHAINID[balance.chainId];
653
+ return (stakingContractAddress &&
654
+ stakingContractAddress.toLowerCase() === balance.token.toLowerCase());
655
+ });
656
+ if (!stakedBalances.length) {
657
+ return [];
658
+ }
659
+ return stakedBalances
660
+ .map((balance) => ({
661
+ address: balance.account,
662
+ chainId: balance.chainId,
663
+ stakedBalance: balance.value ? toHex(balance.value) : '0x0',
664
+ }))
665
+ .filter((update) => {
666
+ const currentStakedBalance = accountTrackerState.accountsByChainId[update.chainId]?.[checksum(update.address)]?.stakedBalance;
667
+ return currentStakedBalance !== update.stakedBalance;
668
+ });
669
+ }, _TokenBalancesController_importUntrackedTokens =
670
+ /**
671
+ * Import untracked tokens that have non-zero balances.
672
+ * This mirrors the v2 behavior where only tokens with actual balances are added.
673
+ * Delegates to TokenDetectionController:addDetectedTokensViaPolling which handles:
674
+ * - Checking if useTokenDetection preference is enabled
675
+ * - Filtering tokens already in allTokens or allIgnoredTokens
676
+ * - Token metadata lookup and addition via TokensController
677
+ *
678
+ * @param balances - Array of processed balance results from fetchers
679
+ */
680
+ async function _TokenBalancesController_importUntrackedTokens(balances) {
681
+ const tokensByChain = new Map();
682
+ for (const balance of balances) {
683
+ // Skip failed fetches, native tokens, and zero balances (like v2 did)
684
+ if (!balance.success ||
685
+ balance.token === ZERO_ADDRESS ||
686
+ !balance.value ||
687
+ balance.value.isZero()) {
688
+ continue;
689
+ }
690
+ const tokenAddress = checksum(balance.token);
691
+ const existing = tokensByChain.get(balance.chainId) ?? [];
692
+ if (!existing.includes(tokenAddress)) {
693
+ existing.push(tokenAddress);
694
+ tokensByChain.set(balance.chainId, existing);
695
+ }
696
+ }
697
+ // Add detected tokens via TokenDetectionController (handles preference check,
698
+ // filtering of allTokens/allIgnoredTokens, and metadata lookup)
699
+ for (const [chainId, tokenAddresses] of tokensByChain) {
700
+ if (tokenAddresses.length) {
701
+ await this.messenger.call('TokenDetectionController:addDetectedTokensViaPolling', {
702
+ tokensSlice: tokenAddresses,
703
+ chainId,
704
+ });
705
+ }
706
+ }
725
707
  }, _TokenBalancesController_isTokenTracked = function _TokenBalancesController_isTokenTracked(tokenAddress, account, chainId) {
726
- // Check if token exists in allTokens
727
- if (__classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")?.[chainId]?.[account.toLowerCase()]?.some((token) => token.address === tokenAddress)) {
708
+ const normalizedAccount = account.toLowerCase();
709
+ if (__classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")?.[chainId]?.[normalizedAccount]?.some((token) => token.address === tokenAddress)) {
728
710
  return true;
729
711
  }
730
- // Check if token exists in allIgnoredTokens
731
- if (__classPrivateFieldGet(this, _TokenBalancesController_allIgnoredTokens, "f")?.[chainId]?.[account.toLowerCase()]?.some((token) => token === tokenAddress)) {
712
+ if (__classPrivateFieldGet(this, _TokenBalancesController_allIgnoredTokens, "f")?.[chainId]?.[normalizedAccount]?.some((token) => token === tokenAddress)) {
732
713
  return true;
733
714
  }
734
715
  return false;
@@ -738,31 +719,25 @@ _TokenBalancesController_platform = new WeakMap(), _TokenBalancesController_quer
738
719
  const nativeBalanceUpdates = [];
739
720
  for (const update of updates) {
740
721
  const { asset, postBalance } = update;
741
- // Throw if balance update has an error
742
722
  if (postBalance.error) {
743
723
  throw new Error('Balance update has error');
744
724
  }
745
- // Parse token address from asset type
746
725
  const parsed = parseAssetType(asset.type);
747
726
  if (!parsed) {
748
727
  throw new Error('Failed to parse asset type');
749
728
  }
750
729
  const [tokenAddress, isNativeToken] = parsed;
751
- // Validate token address
752
730
  if (!isStrictHexString(tokenAddress) ||
753
731
  !isValidHexAddress(tokenAddress)) {
754
732
  throw new Error('Invalid token address');
755
733
  }
756
734
  const checksumTokenAddress = checksum(tokenAddress);
757
735
  const isTracked = __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_isTokenTracked).call(this, checksumTokenAddress, account, chainId);
758
- // postBalance.amount is in hex format (raw units)
759
736
  const balanceHex = postBalance.amount;
760
- // Add token balance (tracked tokens, ignored tokens, and native tokens all get balance updates)
761
737
  tokenBalances.push({
762
738
  tokenAddress: checksumTokenAddress,
763
739
  balance: balanceHex,
764
740
  });
765
- // Add native balance update if this is a native token
766
741
  if (isNativeToken) {
767
742
  nativeBalanceUpdates.push({
768
743
  address: account,
@@ -770,7 +745,6 @@ _TokenBalancesController_platform = new WeakMap(), _TokenBalancesController_quer
770
745
  balance: balanceHex,
771
746
  });
772
747
  }
773
- // Handle untracked ERC20 tokens - queue for import
774
748
  if (!isNativeToken && !isTracked) {
775
749
  newTokens.push(checksumTokenAddress);
776
750
  }
@@ -780,28 +754,18 @@ _TokenBalancesController_platform = new WeakMap(), _TokenBalancesController_quer
780
754
  const changes = Array.from(__classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").pendingChanges.entries());
781
755
  __classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").pendingChanges.clear();
782
756
  __classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").timer = null;
783
- if (changes.length === 0) {
757
+ if (!changes.length) {
784
758
  return;
785
759
  }
786
- // Calculate final polling configurations
787
760
  const chainConfigs = {};
788
761
  for (const [chainId, status] of changes) {
789
- // Convert CAIP format (eip155:1) to hex format (0x1)
790
- // chainId is always in CAIP format from AccountActivityService
791
762
  const hexChainId = caipChainIdToHex(chainId);
792
- if (status === 'down') {
793
- // Chain is down - use default polling since no real-time updates available
794
- chainConfigs[hexChainId] = { interval: __classPrivateFieldGet(this, _TokenBalancesController_defaultInterval, "f") };
795
- }
796
- else {
797
- // Chain is up - use longer intervals since WebSocket provides real-time updates
798
- chainConfigs[hexChainId] = {
799
- interval: __classPrivateFieldGet(this, _TokenBalancesController_websocketActivePollingInterval, "f"),
800
- };
801
- }
763
+ chainConfigs[hexChainId] =
764
+ status === 'down'
765
+ ? { interval: __classPrivateFieldGet(this, _TokenBalancesController_defaultInterval, "f") }
766
+ : { interval: __classPrivateFieldGet(this, _TokenBalancesController_websocketActivePollingInterval, "f") };
802
767
  }
803
- // Add jitter to prevent synchronized requests across instances
804
- const jitterDelay = Math.random() * __classPrivateFieldGet(this, _TokenBalancesController_defaultInterval, "f"); // 0 to default interval
768
+ const jitterDelay = Math.random() * __classPrivateFieldGet(this, _TokenBalancesController_defaultInterval, "f");
805
769
  setTimeout(() => {
806
770
  this.updateChainPollingConfigs(chainConfigs, { immediateUpdate: true });
807
771
  }, jitterDelay);