@metamask/assets-controllers 94.0.0 → 94.1.0

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