@metamask/assets-controllers 79.0.1 → 81.0.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 (41) hide show
  1. package/CHANGELOG.md +43 -1
  2. package/README.md +1 -1
  3. package/dist/NftDetectionController.cjs +1 -72
  4. package/dist/NftDetectionController.cjs.map +1 -1
  5. package/dist/NftDetectionController.d.cts +0 -1
  6. package/dist/NftDetectionController.d.cts.map +1 -1
  7. package/dist/NftDetectionController.d.mts +0 -1
  8. package/dist/NftDetectionController.d.mts.map +1 -1
  9. package/dist/NftDetectionController.mjs +1 -72
  10. package/dist/NftDetectionController.mjs.map +1 -1
  11. package/dist/TokenBalancesController.cjs +229 -9
  12. package/dist/TokenBalancesController.cjs.map +1 -1
  13. package/dist/TokenBalancesController.d.cts +27 -3
  14. package/dist/TokenBalancesController.d.cts.map +1 -1
  15. package/dist/TokenBalancesController.d.mts +27 -3
  16. package/dist/TokenBalancesController.d.mts.map +1 -1
  17. package/dist/TokenBalancesController.mjs +227 -9
  18. package/dist/TokenBalancesController.mjs.map +1 -1
  19. package/dist/TokenDetectionController.cjs +76 -44
  20. package/dist/TokenDetectionController.cjs.map +1 -1
  21. package/dist/TokenDetectionController.d.cts +37 -9
  22. package/dist/TokenDetectionController.d.cts.map +1 -1
  23. package/dist/TokenDetectionController.d.mts +37 -9
  24. package/dist/TokenDetectionController.d.mts.map +1 -1
  25. package/dist/TokenDetectionController.mjs +77 -45
  26. package/dist/TokenDetectionController.mjs.map +1 -1
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.cts +1 -1
  29. package/dist/index.d.cts.map +1 -1
  30. package/dist/index.d.mts +1 -1
  31. package/dist/index.d.mts.map +1 -1
  32. package/dist/index.mjs.map +1 -1
  33. package/dist/multi-chain-accounts-service/api-balance-fetcher.cjs +6 -2
  34. package/dist/multi-chain-accounts-service/api-balance-fetcher.cjs.map +1 -1
  35. package/dist/multi-chain-accounts-service/api-balance-fetcher.d.cts +1 -1
  36. package/dist/multi-chain-accounts-service/api-balance-fetcher.d.cts.map +1 -1
  37. package/dist/multi-chain-accounts-service/api-balance-fetcher.d.mts +1 -1
  38. package/dist/multi-chain-accounts-service/api-balance-fetcher.d.mts.map +1 -1
  39. package/dist/multi-chain-accounts-service/api-balance-fetcher.mjs +6 -2
  40. package/dist/multi-chain-accounts-service/api-balance-fetcher.mjs.map +1 -1
  41. package/package.json +5 -3
@@ -9,11 +9,11 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
9
9
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
10
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
11
  };
12
- var _TokenBalancesController_instances, _TokenBalancesController_platform, _TokenBalancesController_queryAllAccounts, _TokenBalancesController_accountsApiChainIds, _TokenBalancesController_balanceFetchers, _TokenBalancesController_allTokens, _TokenBalancesController_detectedTokens, _TokenBalancesController_defaultInterval, _TokenBalancesController_chainPollingConfig, _TokenBalancesController_intervalPollingTimers, _TokenBalancesController_isControllerPollingActive, _TokenBalancesController_requestedChainIds, _TokenBalancesController_chainIdsWithTokens, _TokenBalancesController_getProvider, _TokenBalancesController_getNetworkClient, _TokenBalancesController_createAccountsApiFetcher, _TokenBalancesController_startIntervalGroupPolling, _TokenBalancesController_startPollingForInterval, _TokenBalancesController_setPollingTimer, _TokenBalancesController_onTokensChanged, _TokenBalancesController_onNetworkChanged, _TokenBalancesController_onAccountRemoved;
12
+ var _TokenBalancesController_instances, _TokenBalancesController_platform, _TokenBalancesController_queryAllAccounts, _TokenBalancesController_accountsApiChainIds, _TokenBalancesController_balanceFetchers, _TokenBalancesController_allTokens, _TokenBalancesController_detectedTokens, _TokenBalancesController_allIgnoredTokens, _TokenBalancesController_defaultInterval, _TokenBalancesController_websocketActivePollingInterval, _TokenBalancesController_chainPollingConfig, _TokenBalancesController_intervalPollingTimers, _TokenBalancesController_isControllerPollingActive, _TokenBalancesController_requestedChainIds, _TokenBalancesController_statusChangeDebouncer, _TokenBalancesController_chainIdsWithTokens, _TokenBalancesController_getProvider, _TokenBalancesController_getNetworkClient, _TokenBalancesController_createAccountsApiFetcher, _TokenBalancesController_startIntervalGroupPolling, _TokenBalancesController_startPollingForInterval, _TokenBalancesController_setPollingTimer, _TokenBalancesController_isTokenTracked, _TokenBalancesController_onTokensChanged, _TokenBalancesController_onNetworkChanged, _TokenBalancesController_onAccountRemoved, _TokenBalancesController_prepareBalanceUpdates, _TokenBalancesController_onAccountActivityBalanceUpdate, _TokenBalancesController_onAccountActivityStatusChanged, _TokenBalancesController_processAccumulatedStatusChanges;
13
13
  import { Web3Provider } from "@ethersproject/providers";
14
14
  import { BNToHex, isValidHexAddress, toChecksumHexAddress, toHex } from "@metamask/controller-utils";
15
15
  import { StaticIntervalPollingController } from "@metamask/polling-controller";
16
- import { isStrictHexString } from "@metamask/utils";
16
+ import { isCaipAssetType, isCaipChainId, isStrictHexString, parseCaipAssetType, parseCaipChainId } from "@metamask/utils";
17
17
  import { produce } from "immer";
18
18
  import $lodash from "lodash";
19
19
  const { isEqual } = $lodash;
@@ -21,7 +21,8 @@ import { STAKING_CONTRACT_ADDRESS_BY_CHAINID } from "./AssetsContractController.
21
21
  import { AccountsApiBalanceFetcher } from "./multi-chain-accounts-service/api-balance-fetcher.mjs";
22
22
  import { RpcBalanceFetcher } from "./rpc-service/rpc-balance-fetcher.mjs";
23
23
  const CONTROLLER = 'TokenBalancesController';
24
- const DEFAULT_INTERVAL_MS = 180000; // 3 minutes
24
+ const DEFAULT_INTERVAL_MS = 30000; // 30 seconds
25
+ const DEFAULT_WEBSOCKET_ACTIVE_POLLING_INTERVAL_MS = 300000; // 5 minutes
25
26
  const metadata = {
26
27
  tokenBalances: {
27
28
  includeInStateLogs: false,
@@ -36,11 +37,50 @@ const metadata = {
36
37
  const draft = (base, fn) => produce(base, fn);
37
38
  const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
38
39
  const checksum = (addr) => toChecksumHexAddress(addr);
40
+ /**
41
+ * Convert CAIP chain ID or hex chain ID to hex chain ID
42
+ * Handles both CAIP-2 format (e.g., "eip155:1") and hex format (e.g., "0x1")
43
+ *
44
+ * @param chainId - CAIP chain ID (e.g., "eip155:1") or hex chain ID (e.g., "0x1")
45
+ * @returns Hex chain ID (e.g., "0x1")
46
+ * @throws {Error} If chainId is neither a valid CAIP-2 chain ID nor a hex string
47
+ */
48
+ export const caipChainIdToHex = (chainId) => {
49
+ if (isStrictHexString(chainId)) {
50
+ return chainId;
51
+ }
52
+ if (isCaipChainId(chainId)) {
53
+ return toHex(parseCaipChainId(chainId).reference);
54
+ }
55
+ throw new Error('caipChainIdToHex - Failed to provide CAIP-2 or Hex chainId');
56
+ };
57
+ /**
58
+ * Extract token address from asset type
59
+ * Returns tuple of [tokenAddress, isNativeToken] or null if invalid
60
+ *
61
+ * @param assetType - Asset type string (e.g., 'eip155:1/erc20:0x...' or 'eip155:1/slip44:60')
62
+ * @returns Tuple of [tokenAddress, isNativeToken] or null if invalid
63
+ */
64
+ export const parseAssetType = (assetType) => {
65
+ if (!isCaipAssetType(assetType)) {
66
+ return null;
67
+ }
68
+ const parsed = parseCaipAssetType(assetType);
69
+ // ERC20 token (e.g., "eip155:1/erc20:0x...")
70
+ if (parsed.assetNamespace === 'erc20') {
71
+ return [parsed.assetReference, false];
72
+ }
73
+ // Native token (e.g., "eip155:1/slip44:60")
74
+ if (parsed.assetNamespace === 'slip44') {
75
+ return [ZERO_ADDRESS, true];
76
+ }
77
+ return null;
78
+ };
39
79
  // endregion
40
80
  // ────────────────────────────────────────────────────────────────────────────
41
81
  // region: Main controller
42
82
  export class TokenBalancesController extends StaticIntervalPollingController() {
43
- constructor({ messenger, interval = DEFAULT_INTERVAL_MS, chainPollingIntervals = {}, state = {}, queryMultipleAccounts = true, accountsApiChainIds = () => [], allowExternalServices = () => true, platform, }) {
83
+ constructor({ messenger, interval = DEFAULT_INTERVAL_MS, websocketActivePollingInterval = DEFAULT_WEBSOCKET_ACTIVE_POLLING_INTERVAL_MS, chainPollingIntervals = {}, state = {}, queryMultipleAccounts = true, accountsApiChainIds = () => [], allowExternalServices = () => true, platform, }) {
44
84
  super({
45
85
  name: CONTROLLER,
46
86
  messenger,
@@ -54,8 +94,11 @@ export class TokenBalancesController extends StaticIntervalPollingController() {
54
94
  _TokenBalancesController_balanceFetchers.set(this, void 0);
55
95
  _TokenBalancesController_allTokens.set(this, {});
56
96
  _TokenBalancesController_detectedTokens.set(this, {});
97
+ _TokenBalancesController_allIgnoredTokens.set(this, {});
57
98
  /** Default polling interval for chains without specific configuration */
58
99
  _TokenBalancesController_defaultInterval.set(this, void 0);
100
+ /** Polling interval when WebSocket is active and providing real-time updates */
101
+ _TokenBalancesController_websocketActivePollingInterval.set(this, void 0);
59
102
  /** Per-chain polling configuration */
60
103
  _TokenBalancesController_chainPollingConfig.set(this, void 0);
61
104
  /** Active polling timers grouped by interval */
@@ -64,6 +107,11 @@ export class TokenBalancesController extends StaticIntervalPollingController() {
64
107
  _TokenBalancesController_isControllerPollingActive.set(this, false);
65
108
  /** Store original chainIds from startPolling to preserve intent */
66
109
  _TokenBalancesController_requestedChainIds.set(this, []);
110
+ /** Debouncing for rapid status changes to prevent excessive HTTP calls */
111
+ _TokenBalancesController_statusChangeDebouncer.set(this, {
112
+ timer: null,
113
+ pendingChanges: new Map(),
114
+ });
67
115
  _TokenBalancesController_getProvider.set(this, (chainId) => {
68
116
  const { networkConfigurationsByChainId } = this.messagingSystem.call('NetworkController:getState');
69
117
  const cfg = networkConfigurationsByChainId[chainId];
@@ -165,6 +213,7 @@ export class TokenBalancesController extends StaticIntervalPollingController() {
165
213
  });
166
214
  __classPrivateFieldSet(this, _TokenBalancesController_allTokens, state.allTokens, "f");
167
215
  __classPrivateFieldSet(this, _TokenBalancesController_detectedTokens, state.allDetectedTokens, "f");
216
+ __classPrivateFieldSet(this, _TokenBalancesController_allIgnoredTokens, state.allIgnoredTokens, "f");
168
217
  // Only update balances for chains that still have tokens (and only if we haven't already updated state)
169
218
  if (changed.length && !hasChanges) {
170
219
  this.updateBalances({ chainIds: changed }).catch((error) => {
@@ -208,10 +257,87 @@ export class TokenBalancesController extends StaticIntervalPollingController() {
208
257
  delete s.tokenBalances[addr];
209
258
  });
210
259
  });
260
+ // ────────────────────────────────────────────────────────────────────────────
261
+ // AccountActivityService event handlers
262
+ /**
263
+ * Handle real-time balance updates from AccountActivityService
264
+ * Processes balance updates and updates the token balance state
265
+ * If any balance update has an error, triggers fallback polling for the chain
266
+ *
267
+ * @param options0 - Balance update parameters
268
+ * @param options0.address - Account address
269
+ * @param options0.chain - CAIP chain identifier
270
+ * @param options0.updates - Array of balance updates for the account
271
+ */
272
+ _TokenBalancesController_onAccountActivityBalanceUpdate.set(this, async ({ address, chain, updates, }) => {
273
+ const chainId = caipChainIdToHex(chain);
274
+ const checksummedAccount = checksum(address);
275
+ try {
276
+ // Process all balance updates at once
277
+ const { tokenBalances, newTokens, nativeBalanceUpdates } = __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_prepareBalanceUpdates).call(this, updates, checksummedAccount, chainId);
278
+ // Update state once with all token balances
279
+ if (tokenBalances.length > 0) {
280
+ this.update((state) => {
281
+ var _a, _b;
282
+ // Temporary until ADR to normalize all keys - tokenBalances state requires: account in lowercase, token in checksum
283
+ const lowercaseAccount = checksummedAccount.toLowerCase();
284
+ (_a = state.tokenBalances)[lowercaseAccount] ?? (_a[lowercaseAccount] = {});
285
+ (_b = state.tokenBalances[lowercaseAccount])[chainId] ?? (_b[chainId] = {});
286
+ // Apply all token balance updates
287
+ for (const { tokenAddress, balance } of tokenBalances) {
288
+ state.tokenBalances[lowercaseAccount][chainId][tokenAddress] =
289
+ balance;
290
+ }
291
+ });
292
+ }
293
+ // Update native balances in AccountTrackerController
294
+ if (nativeBalanceUpdates.length > 0) {
295
+ this.messagingSystem.call('AccountTrackerController:updateNativeBalances', nativeBalanceUpdates);
296
+ }
297
+ // Import any new tokens that were discovered (balance already updated from websocket)
298
+ if (newTokens.length > 0) {
299
+ await this.messagingSystem.call('TokenDetectionController:addDetectedTokensViaWs', {
300
+ tokensSlice: newTokens,
301
+ chainId: chainId,
302
+ });
303
+ }
304
+ }
305
+ catch (error) {
306
+ console.warn(`Error updating balances from AccountActivityService for chain ${chain}, account ${address}:`, error);
307
+ console.warn('Balance update data:', JSON.stringify(updates, null, 2));
308
+ // On error, trigger fallback polling
309
+ await this.updateBalances({ chainIds: [chainId] }).catch(() => {
310
+ // Silently handle polling errors
311
+ });
312
+ }
313
+ });
314
+ /**
315
+ * Handle status changes from AccountActivityService
316
+ * Uses aggressive debouncing to prevent excessive HTTP calls from rapid up/down changes
317
+ *
318
+ * @param options0 - Status change event data
319
+ * @param options0.chainIds - Array of chain identifiers
320
+ * @param options0.status - Connection status ('up' for connected, 'down' for disconnected)
321
+ */
322
+ _TokenBalancesController_onAccountActivityStatusChanged.set(this, ({ chainIds, status, }) => {
323
+ // Update pending changes (latest status wins for each chain)
324
+ for (const chainId of chainIds) {
325
+ __classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").pendingChanges.set(chainId, status);
326
+ }
327
+ // Clear existing timer to extend debounce window
328
+ if (__classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").timer) {
329
+ clearTimeout(__classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").timer);
330
+ }
331
+ // Set new timer - only process changes after activity settles
332
+ __classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").timer = setTimeout(() => {
333
+ __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_processAccumulatedStatusChanges).call(this);
334
+ }, 5000); // 5-second debounce window
335
+ });
211
336
  __classPrivateFieldSet(this, _TokenBalancesController_platform, platform ?? 'extension', "f");
212
337
  __classPrivateFieldSet(this, _TokenBalancesController_queryAllAccounts, queryMultipleAccounts, "f");
213
338
  __classPrivateFieldSet(this, _TokenBalancesController_accountsApiChainIds, accountsApiChainIds, "f");
214
339
  __classPrivateFieldSet(this, _TokenBalancesController_defaultInterval, interval, "f");
340
+ __classPrivateFieldSet(this, _TokenBalancesController_websocketActivePollingInterval, websocketActivePollingInterval, "f");
215
341
  __classPrivateFieldSet(this, _TokenBalancesController_chainPollingConfig, { ...chainPollingIntervals }, "f");
216
342
  // Strategy order: API first, then RPC fallback
217
343
  __classPrivateFieldSet(this, _TokenBalancesController_balanceFetchers, [
@@ -225,9 +351,10 @@ export class TokenBalancesController extends StaticIntervalPollingController() {
225
351
  ], "f");
226
352
  this.setIntervalLength(interval);
227
353
  // initial token state & subscriptions
228
- const { allTokens, allDetectedTokens } = this.messagingSystem.call('TokensController:getState');
354
+ const { allTokens, allDetectedTokens, allIgnoredTokens } = this.messagingSystem.call('TokensController:getState');
229
355
  __classPrivateFieldSet(this, _TokenBalancesController_allTokens, allTokens, "f");
230
356
  __classPrivateFieldSet(this, _TokenBalancesController_detectedTokens, allDetectedTokens, "f");
357
+ __classPrivateFieldSet(this, _TokenBalancesController_allIgnoredTokens, allIgnoredTokens, "f");
231
358
  this.messagingSystem.subscribe('TokensController:stateChange', (tokensState) => {
232
359
  __classPrivateFieldGet(this, _TokenBalancesController_onTokensChanged, "f").call(this, tokensState).catch((error) => {
233
360
  console.warn('Error handling token state change:', error);
@@ -238,6 +365,10 @@ export class TokenBalancesController extends StaticIntervalPollingController() {
238
365
  // Register action handlers for polling interval control
239
366
  this.messagingSystem.registerActionHandler(`TokenBalancesController:updateChainPollingConfigs`, this.updateChainPollingConfigs.bind(this));
240
367
  this.messagingSystem.registerActionHandler(`TokenBalancesController:getChainPollingConfig`, this.getChainPollingConfig.bind(this));
368
+ // Subscribe to AccountActivityService balance updates for real-time updates
369
+ this.messagingSystem.subscribe('AccountActivityService:balanceUpdated', __classPrivateFieldGet(this, _TokenBalancesController_onAccountActivityBalanceUpdate, "f").bind(this));
370
+ // Subscribe to AccountActivityService status changes for dynamic polling management
371
+ this.messagingSystem.subscribe('AccountActivityService:statusChanged', __classPrivateFieldGet(this, _TokenBalancesController_onAccountActivityStatusChanged, "f").bind(this));
241
372
  }
242
373
  /**
243
374
  * Override to support per-chain polling intervals by grouping chains by interval
@@ -392,15 +523,14 @@ export class TokenBalancesController extends StaticIntervalPollingController() {
392
523
  }
393
524
  // Update with actual fetched balances only if the value has changed
394
525
  aggregated.forEach(({ success, value, account, token, chainId }) => {
395
- var _a, _b;
526
+ var _a, _b, _c;
396
527
  if (success && value !== undefined) {
397
528
  const newBalance = toHex(value);
398
529
  const tokenAddress = checksum(token);
399
530
  const currentBalance = d.tokenBalances[account]?.[chainId]?.[tokenAddress];
400
531
  // Only update if the balance has actually changed
401
532
  if (currentBalance !== newBalance) {
402
- ((_b = ((_a = d.tokenBalances)[account] ?? (_a[account] = {})))[chainId] ?? (_b[chainId] = {}))[tokenAddress] =
403
- newBalance;
533
+ ((_c = ((_a = d.tokenBalances)[_b = account] ?? (_a[_b] = {})))[chainId] ?? (_c[chainId] = {}))[tokenAddress] = newBalance;
404
534
  }
405
535
  }
406
536
  });
@@ -465,13 +595,18 @@ export class TokenBalancesController extends StaticIntervalPollingController() {
465
595
  __classPrivateFieldSet(this, _TokenBalancesController_isControllerPollingActive, false, "f");
466
596
  __classPrivateFieldGet(this, _TokenBalancesController_intervalPollingTimers, "f").forEach((timer) => clearInterval(timer));
467
597
  __classPrivateFieldGet(this, _TokenBalancesController_intervalPollingTimers, "f").clear();
598
+ // Clean up debouncing timer
599
+ if (__classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").timer) {
600
+ clearTimeout(__classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").timer);
601
+ __classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").timer = null;
602
+ }
468
603
  // Unregister action handlers
469
604
  this.messagingSystem.unregisterActionHandler(`TokenBalancesController:updateChainPollingConfigs`);
470
605
  this.messagingSystem.unregisterActionHandler(`TokenBalancesController:getChainPollingConfig`);
471
606
  super.destroy();
472
607
  }
473
608
  }
474
- _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_defaultInterval = new WeakMap(), _TokenBalancesController_chainPollingConfig = new WeakMap(), _TokenBalancesController_intervalPollingTimers = new WeakMap(), _TokenBalancesController_isControllerPollingActive = new WeakMap(), _TokenBalancesController_requestedChainIds = 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_instances = new WeakSet(), _TokenBalancesController_chainIdsWithTokens = function _TokenBalancesController_chainIdsWithTokens() {
609
+ _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_onAccountActivityBalanceUpdate = new WeakMap(), _TokenBalancesController_onAccountActivityStatusChanged = new WeakMap(), _TokenBalancesController_instances = new WeakSet(), _TokenBalancesController_chainIdsWithTokens = function _TokenBalancesController_chainIdsWithTokens() {
475
610
  return [
476
611
  ...new Set([
477
612
  ...Object.keys(__classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")),
@@ -526,6 +661,89 @@ _TokenBalancesController_platform = new WeakMap(), _TokenBalancesController_quer
526
661
  });
527
662
  }, interval);
528
663
  __classPrivateFieldGet(this, _TokenBalancesController_intervalPollingTimers, "f").set(interval, timer);
664
+ }, _TokenBalancesController_isTokenTracked = function _TokenBalancesController_isTokenTracked(tokenAddress, account, chainId) {
665
+ // Check if token exists in allTokens
666
+ if (__classPrivateFieldGet(this, _TokenBalancesController_allTokens, "f")?.[chainId]?.[account.toLowerCase()]?.some((token) => token.address === tokenAddress)) {
667
+ return true;
668
+ }
669
+ // Check if token exists in allIgnoredTokens
670
+ if (__classPrivateFieldGet(this, _TokenBalancesController_allIgnoredTokens, "f")?.[chainId]?.[account.toLowerCase()]?.some((token) => token === tokenAddress)) {
671
+ return true;
672
+ }
673
+ return false;
674
+ }, _TokenBalancesController_prepareBalanceUpdates = function _TokenBalancesController_prepareBalanceUpdates(updates, account, chainId) {
675
+ const tokenBalances = [];
676
+ const newTokens = [];
677
+ const nativeBalanceUpdates = [];
678
+ for (const update of updates) {
679
+ const { asset, postBalance } = update;
680
+ // Throw if balance update has an error
681
+ if (postBalance.error) {
682
+ throw new Error('Balance update has error');
683
+ }
684
+ // Parse token address from asset type
685
+ const parsed = parseAssetType(asset.type);
686
+ if (!parsed) {
687
+ throw new Error('Failed to parse asset type');
688
+ }
689
+ const [tokenAddress, isNativeToken] = parsed;
690
+ // Validate token address
691
+ if (!isStrictHexString(tokenAddress) ||
692
+ !isValidHexAddress(tokenAddress)) {
693
+ throw new Error('Invalid token address');
694
+ }
695
+ const checksumTokenAddress = checksum(tokenAddress);
696
+ const isTracked = __classPrivateFieldGet(this, _TokenBalancesController_instances, "m", _TokenBalancesController_isTokenTracked).call(this, checksumTokenAddress, account, chainId);
697
+ // postBalance.amount is in hex format (raw units)
698
+ const balanceHex = postBalance.amount;
699
+ // Add token balance (tracked tokens, ignored tokens, and native tokens all get balance updates)
700
+ tokenBalances.push({
701
+ tokenAddress: checksumTokenAddress,
702
+ balance: balanceHex,
703
+ });
704
+ // Add native balance update if this is a native token
705
+ if (isNativeToken) {
706
+ nativeBalanceUpdates.push({
707
+ address: account,
708
+ chainId,
709
+ balance: balanceHex,
710
+ });
711
+ }
712
+ // Handle untracked ERC20 tokens - queue for import
713
+ if (!isNativeToken && !isTracked) {
714
+ newTokens.push(checksumTokenAddress);
715
+ }
716
+ }
717
+ return { tokenBalances, newTokens, nativeBalanceUpdates };
718
+ }, _TokenBalancesController_processAccumulatedStatusChanges = function _TokenBalancesController_processAccumulatedStatusChanges() {
719
+ const changes = Array.from(__classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").pendingChanges.entries());
720
+ __classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").pendingChanges.clear();
721
+ __classPrivateFieldGet(this, _TokenBalancesController_statusChangeDebouncer, "f").timer = null;
722
+ if (changes.length === 0) {
723
+ return;
724
+ }
725
+ // Calculate final polling configurations
726
+ const chainConfigs = {};
727
+ for (const [chainId, status] of changes) {
728
+ // Convert CAIP format (eip155:1) to hex format (0x1)
729
+ // chainId is always in CAIP format from AccountActivityService
730
+ const hexChainId = caipChainIdToHex(chainId);
731
+ if (status === 'down') {
732
+ // Chain is down - use default polling since no real-time updates available
733
+ chainConfigs[hexChainId] = { interval: __classPrivateFieldGet(this, _TokenBalancesController_defaultInterval, "f") };
734
+ }
735
+ else {
736
+ // Chain is up - use longer intervals since WebSocket provides real-time updates
737
+ chainConfigs[hexChainId] = {
738
+ interval: __classPrivateFieldGet(this, _TokenBalancesController_websocketActivePollingInterval, "f"),
739
+ };
740
+ }
741
+ }
742
+ // Add jitter to prevent synchronized requests across instances
743
+ const jitterDelay = Math.random() * __classPrivateFieldGet(this, _TokenBalancesController_defaultInterval, "f"); // 0 to default interval
744
+ setTimeout(() => {
745
+ this.updateChainPollingConfigs(chainConfigs, { immediateUpdate: true });
746
+ }, jitterDelay);
529
747
  };
530
748
  export default TokenBalancesController;
531
749
  //# sourceMappingURL=TokenBalancesController.mjs.map