@metamask-previews/assets-controllers 93.1.0-preview-c92c27f1 → 93.1.0-preview-d717276a

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