@metamask/assets-controller 9.1.0 → 10.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 (76) hide show
  1. package/CHANGELOG.md +21 -1
  2. package/dist/AssetsController.cjs +351 -90
  3. package/dist/AssetsController.cjs.map +1 -1
  4. package/dist/AssetsController.d.cts +11 -8
  5. package/dist/AssetsController.d.cts.map +1 -1
  6. package/dist/AssetsController.d.mts +11 -8
  7. package/dist/AssetsController.d.mts.map +1 -1
  8. package/dist/AssetsController.mjs +351 -90
  9. package/dist/AssetsController.mjs.map +1 -1
  10. package/dist/data-sources/AccountsApiDataSource.cjs +14 -1
  11. package/dist/data-sources/AccountsApiDataSource.cjs.map +1 -1
  12. package/dist/data-sources/AccountsApiDataSource.d.cts +8 -0
  13. package/dist/data-sources/AccountsApiDataSource.d.cts.map +1 -1
  14. package/dist/data-sources/AccountsApiDataSource.d.mts +8 -0
  15. package/dist/data-sources/AccountsApiDataSource.d.mts.map +1 -1
  16. package/dist/data-sources/AccountsApiDataSource.mjs +14 -1
  17. package/dist/data-sources/AccountsApiDataSource.mjs.map +1 -1
  18. package/dist/data-sources/BackendWebsocketDataSource.cjs +236 -149
  19. package/dist/data-sources/BackendWebsocketDataSource.cjs.map +1 -1
  20. package/dist/data-sources/BackendWebsocketDataSource.d.cts +15 -0
  21. package/dist/data-sources/BackendWebsocketDataSource.d.cts.map +1 -1
  22. package/dist/data-sources/BackendWebsocketDataSource.d.mts +15 -0
  23. package/dist/data-sources/BackendWebsocketDataSource.d.mts.map +1 -1
  24. package/dist/data-sources/BackendWebsocketDataSource.mjs +236 -146
  25. package/dist/data-sources/BackendWebsocketDataSource.mjs.map +1 -1
  26. package/dist/data-sources/PriceDataSource.cjs +18 -9
  27. package/dist/data-sources/PriceDataSource.cjs.map +1 -1
  28. package/dist/data-sources/PriceDataSource.d.cts.map +1 -1
  29. package/dist/data-sources/PriceDataSource.d.mts.map +1 -1
  30. package/dist/data-sources/PriceDataSource.mjs +19 -10
  31. package/dist/data-sources/PriceDataSource.mjs.map +1 -1
  32. package/dist/data-sources/RpcDataSource.cjs +29 -9
  33. package/dist/data-sources/RpcDataSource.cjs.map +1 -1
  34. package/dist/data-sources/RpcDataSource.d.cts +5 -0
  35. package/dist/data-sources/RpcDataSource.d.cts.map +1 -1
  36. package/dist/data-sources/RpcDataSource.d.mts +5 -0
  37. package/dist/data-sources/RpcDataSource.d.mts.map +1 -1
  38. package/dist/data-sources/RpcDataSource.mjs +29 -9
  39. package/dist/data-sources/RpcDataSource.mjs.map +1 -1
  40. package/dist/middlewares/DetectionMiddleware.cjs +22 -1
  41. package/dist/middlewares/DetectionMiddleware.cjs.map +1 -1
  42. package/dist/middlewares/DetectionMiddleware.d.cts +3 -0
  43. package/dist/middlewares/DetectionMiddleware.d.cts.map +1 -1
  44. package/dist/middlewares/DetectionMiddleware.d.mts +3 -0
  45. package/dist/middlewares/DetectionMiddleware.d.mts.map +1 -1
  46. package/dist/middlewares/DetectionMiddleware.mjs +22 -1
  47. package/dist/middlewares/DetectionMiddleware.mjs.map +1 -1
  48. package/dist/middlewares/ParallelMiddleware.cjs +7 -0
  49. package/dist/middlewares/ParallelMiddleware.cjs.map +1 -1
  50. package/dist/middlewares/ParallelMiddleware.d.cts.map +1 -1
  51. package/dist/middlewares/ParallelMiddleware.d.mts.map +1 -1
  52. package/dist/middlewares/ParallelMiddleware.mjs +7 -0
  53. package/dist/middlewares/ParallelMiddleware.mjs.map +1 -1
  54. package/dist/types.cjs.map +1 -1
  55. package/dist/types.d.cts +20 -4
  56. package/dist/types.d.cts.map +1 -1
  57. package/dist/types.d.mts +20 -4
  58. package/dist/types.d.mts.map +1 -1
  59. package/dist/types.mjs.map +1 -1
  60. package/dist/utils/dedupingBatchFetcher.cjs +11 -0
  61. package/dist/utils/dedupingBatchFetcher.cjs.map +1 -1
  62. package/dist/utils/dedupingBatchFetcher.d.cts +7 -0
  63. package/dist/utils/dedupingBatchFetcher.d.cts.map +1 -1
  64. package/dist/utils/dedupingBatchFetcher.d.mts +7 -0
  65. package/dist/utils/dedupingBatchFetcher.d.mts.map +1 -1
  66. package/dist/utils/dedupingBatchFetcher.mjs +11 -0
  67. package/dist/utils/dedupingBatchFetcher.mjs.map +1 -1
  68. package/dist/utils/processAccountActivityBalanceUpdates.cjs +54 -0
  69. package/dist/utils/processAccountActivityBalanceUpdates.cjs.map +1 -0
  70. package/dist/utils/processAccountActivityBalanceUpdates.d.cts +13 -0
  71. package/dist/utils/processAccountActivityBalanceUpdates.d.cts.map +1 -0
  72. package/dist/utils/processAccountActivityBalanceUpdates.d.mts +13 -0
  73. package/dist/utils/processAccountActivityBalanceUpdates.d.mts.map +1 -0
  74. package/dist/utils/processAccountActivityBalanceUpdates.mjs +47 -0
  75. package/dist/utils/processAccountActivityBalanceUpdates.mjs.map +1 -0
  76. package/package.json +3 -3
@@ -9,7 +9,7 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
9
9
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
10
10
  return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
11
11
  };
12
- var _AssetsController_instances, _AssetsController_isEnabled, _AssetsController_isBasicFunctionality, _AssetsController_defaultUpdateInterval, _AssetsController_trace, _AssetsController_captureException, _AssetsController_firstInitFetchReported, _AssetsController_stateSizeReported, _AssetsController_emitTrace, _AssetsController_emitStateSizeTrace, _AssetsController_uiOpen, _AssetsController_keyringUnlocked, _AssetsController_controllerMutex, _AssetsController_activeSubscriptions, _AssetsController_enabledChains, _AssetsController_lastKnownAccountIds, _AssetsController_getSelectedAccounts, _AssetsController_backendWebsocketDataSource, _AssetsController_accountsApiDataSource, _AssetsController_snapDataSource, _AssetsController_rpcDataSource, _AssetsController_stakedBalanceDataSource, _AssetsController_allBalanceDataSources_get, _AssetsController_priceDataSource, _AssetsController_detectionMiddleware, _AssetsController_customAssetGraduationMiddleware, _AssetsController_rpcFallbackMiddleware, _AssetsController_tokenDataSource, _AssetsController_unsubscribeBasicFunctionality, _AssetsController_queryApiClient, _AssetsController_onActiveChainsUpdated, _AssetsController_initializeNativeAssetsMap, _AssetsController_initializeState, _AssetsController_extractEnabledChains, _AssetsController_normalizeChainReference, _AssetsController_subscribeToEvents, _AssetsController_onUnapprovedTransactionAdded, _AssetsController_updateActive, _AssetsController_handleAccountTreeStateChange, _AssetsController_registerActionHandlers, _AssetsController_handleActiveChainsUpdate, _AssetsController_executeMiddlewares, _AssetsController_getNativeAssetMap, _AssetsController_isNativeAsset, _AssetsController_getAssetType, _AssetsController_resolveNativeAssetIds, _AssetsController_getNativeAssetIdsForEnabledChains, _AssetsController_getNativeAssetIdsForAccount, _AssetsController_ensureNativeBalancesDefaultZero, _AssetsController_ensureDefaultTrackedAssetsSeeded, _AssetsController_updateState, _AssetsController_getAssetsFromState, _AssetsController_shouldHideNativeToken, _AssetsController_tokenStandardToAssetType, _AssetsController_start, _AssetsController_stop, _AssetsController_subscribeAssets, _AssetsController_subscribeAssetsBalance, _AssetsController_subscribeRpcCustomAssetsSupplement, _AssetsController_unsubscribeBySubscriptionKey, _AssetsController_subscribeStakedBalance, _AssetsController_buildChainToAccountsMap, _AssetsController_subscribeDataSource, _AssetsController_unsubscribeDataSource, _AssetsController_buildDataRequest, _AssetsController_getEnabledChainsForAccount, _AssetsController_handleAccountGroupChanged, _AssetsController_handleEnabledNetworksChanged, _AssetsController_handleNetworkAdded, _AssetsController_refreshAssetsAfterNetworkChange;
12
+ var _AssetsController_instances, _AssetsController_isEnabled, _AssetsController_isBasicFunctionality, _AssetsController_defaultUpdateInterval, _AssetsController_trace, _AssetsController_captureException, _AssetsController_firstInitFetchReported, _AssetsController_stateSizeReported, _AssetsController_emitTrace, _AssetsController_emitStateSizeTrace, _AssetsController_uiOpen, _AssetsController_keyringUnlocked, _AssetsController_controllerMutex, _AssetsController_accountRefreshMutex, _AssetsController_activeSubscriptions, _AssetsController_enabledChains, _AssetsController_lastKnownAccountIds, _AssetsController_getSelectedAccounts, _AssetsController_backendWebsocketDataSource, _AssetsController_accountsApiDataSource, _AssetsController_snapDataSource, _AssetsController_rpcDataSource, _AssetsController_stakedBalanceDataSource, _AssetsController_allBalanceDataSources_get, _AssetsController_priceDataSource, _AssetsController_detectionMiddleware, _AssetsController_customAssetGraduationMiddleware, _AssetsController_rpcFallbackMiddleware, _AssetsController_tokenDataSource, _AssetsController_unsubscribeBasicFunctionality, _AssetsController_queryApiClient, _AssetsController_onActiveChainsUpdated, _AssetsController_initializeNativeAssetsMap, _AssetsController_initializeState, _AssetsController_extractEnabledChains, _AssetsController_normalizeChainReference, _AssetsController_subscribeToEvents, _AssetsController_onUnapprovedTransactionAdded, _AssetsController_onTransactionConfirmed, _AssetsController_onAccountActivityBalanceUpdated, _AssetsController_updateActive, _AssetsController_handleAccountTreeStateChange, _AssetsController_runAccountTreeRefresh, _AssetsController_runStartupRefresh, _AssetsController_registerActionHandlers, _AssetsController_handleActiveChainsUpdate, _AssetsController_executeMiddlewares, _AssetsController_fetchMissingPricesWithoutCache, _AssetsController_getNativeAssetMap, _AssetsController_isNativeAsset, _AssetsController_getAssetType, _AssetsController_resolveNativeAssetIds, _AssetsController_getNativeAssetIdsForEnabledChains, _AssetsController_getNativeAssetIdsForAccount, _AssetsController_getSlowPipelineChainIds, _AssetsController_ensureNativeBalancesDefaultZero, _AssetsController_ensureDefaultTrackedAssetsSeeded, _AssetsController_updateState, _AssetsController_getAssetsFromState, _AssetsController_shouldHideNativeToken, _AssetsController_tokenStandardToAssetType, _AssetsController_start, _AssetsController_stop, _AssetsController_subscribeAssets, _AssetsController_subscribeAssetsBalance, _AssetsController_subscribeRpcCustomAssetsSupplement, _AssetsController_unsubscribeBySubscriptionKey, _AssetsController_subscribeStakedBalance, _AssetsController_buildChainToAccountsMap, _AssetsController_subscribeDataSource, _AssetsController_unsubscribeDataSource, _AssetsController_buildDataRequest, _AssetsController_getEnabledChainsForAccount, _AssetsController_handleAccountGroupChanged, _AssetsController_handleEnabledNetworksChanged, _AssetsController_handleNetworkAdded, _AssetsController_refreshActiveChainsOnNetworkSwitch, _AssetsController_getSelectedEvmChainIdFromNetworkState, _AssetsController_handleNetworkDidChange, _AssetsController_refreshAssetsAfterNetworkChange, _AssetsController_refreshAssetsAfterNetworkAdded;
13
13
  import { BaseController } from "@metamask/base-controller";
14
14
  import { clientControllerSelectors } from "@metamask/client-controller";
15
15
  import { isCaipChainId, isStrictHexString, KnownCaipNamespace, parseCaipAssetType, parseCaipChainId } from "@metamask/utils";
@@ -35,6 +35,7 @@ import { RpcFallbackMiddleware } from "./middlewares/RpcFallbackMiddleware.mjs";
35
35
  import { normalizeAmountString, normalizeAssetId, formatExchangeRatesForBridge, formatStateForTransactionPay, buildNativeAssetsFromConstant, buildNativeAssetsFromApi } from "./utils/index.mjs";
36
36
  import { ZERO_ADDRESS } from "./utils/constants.mjs";
37
37
  import { pickRpcCustomAssetsSupplement } from "./utils/customAssetsRpcSupplement.mjs";
38
+ import { processAccountActivityBalanceUpdates } from "./utils/processAccountActivityBalanceUpdates.mjs";
38
39
  const NATIVE_ASSETS_QUERY_KEY = ['nativeAssets'];
39
40
  // ============================================================================
40
41
  // CONTROLLER CONSTANTS
@@ -186,8 +187,41 @@ function normalizeResponse(response) {
186
187
  if (response.updateMode) {
187
188
  normalized.updateMode = response.updateMode;
188
189
  }
190
+ if (response.replaceCoveredChainBalances) {
191
+ normalized.replaceCoveredChainBalances = true;
192
+ }
189
193
  return normalized;
190
194
  }
195
+ /**
196
+ * Merge account balances from a data-source response into prior state.
197
+ *
198
+ * @param previousBalances - Balances already in state for this account.
199
+ * @param accountBalances - Balances from the incoming response.
200
+ * @param customAssetIds - Custom assets to preserve when replacing covered chains.
201
+ * @param replaceCoveredChains - When true, drop prior balances on chains present
202
+ * in the response before applying it (authoritative chain slice).
203
+ * @returns The merged balance map for the account.
204
+ */
205
+ function mergeAccountBalances(previousBalances, accountBalances, customAssetIds, replaceCoveredChains) {
206
+ if (!replaceCoveredChains) {
207
+ return { ...previousBalances, ...accountBalances };
208
+ }
209
+ const coveredChains = new Set(Object.keys(accountBalances).map((assetId) => assetId.split('/')[0]));
210
+ const next = {};
211
+ for (const [assetId, balance] of Object.entries(previousBalances)) {
212
+ if (!coveredChains.has(assetId.split('/')[0])) {
213
+ next[assetId] = balance;
214
+ }
215
+ }
216
+ Object.assign(next, accountBalances);
217
+ for (const customId of customAssetIds) {
218
+ if (!Object.prototype.hasOwnProperty.call(next, customId)) {
219
+ const prev = previousBalances[customId];
220
+ next[customId] = prev ?? { amount: '0' };
221
+ }
222
+ }
223
+ return next;
224
+ }
191
225
  // ============================================================================
192
226
  // CONTROLLER IMPLEMENTATION
193
227
  // ============================================================================
@@ -249,6 +283,8 @@ export class AssetsController extends BaseController {
249
283
  /** Whether the keyring is unlocked. Combined with #uiOpen for #updateActive. */
250
284
  _AssetsController_keyringUnlocked.set(this, false);
251
285
  _AssetsController_controllerMutex.set(this, new Mutex());
286
+ /** Serializes account-switch fetch + subscribe to prevent overlapping races. */
287
+ _AssetsController_accountRefreshMutex.set(this, new Mutex());
252
288
  /**
253
289
  * Active balance subscriptions keyed by account ID.
254
290
  * Each account has one logical subscription that may span multiple data sources.
@@ -412,9 +448,9 @@ export class AssetsController extends BaseController {
412
448
  // creation, RPC is slow on many chains). Results are committed to state
413
449
  // immediately so the UI can display balances without waiting for them.
414
450
  //
415
- // Both the fast and background pipelines use 'merge' mode because neither
416
- // alone represents the full set of data sources. Using 'full' in either
417
- // would wipe balances from the sources handled by the other pipeline.
451
+ // Fast/slow pipelines use merge so partial API snapshots cannot wipe
452
+ // tokens missing from the response (e.g. USDC when only native balance
453
+ // is returned). Balances present in the response are still refreshed.
418
454
  const fastSources = __classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)
419
455
  ? [
420
456
  createParallelBalanceMiddleware([
@@ -434,31 +470,36 @@ export class AssetsController extends BaseController {
434
470
  ]
435
471
  : [__classPrivateFieldGet(this, _AssetsController_stakedBalanceDataSource, "f"), __classPrivateFieldGet(this, _AssetsController_detectionMiddleware, "f")];
436
472
  const { response, durationByDataSource } = await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, fastSources, request);
437
- // The fast pipeline only contains a subset of data sources (AccountsApi +
438
- // StakedBalance), so it must always merge to avoid wiping Snap/RPC
439
- // balances that the background pipeline hasn't yet replaced.
440
- await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, { ...response, updateMode: 'merge' });
473
+ await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, {
474
+ ...response,
475
+ updateMode: 'merge',
476
+ replaceCoveredChainBalances: true,
477
+ });
441
478
  // Background pipeline: snap and RPC run in parallel after the fast path
442
479
  // commits to state. Their balances are merged together before detection.
443
480
  // Token + price enrichment matches the pre-split behavior: only when basic
444
481
  // functionality is on (RPC-only mode must not call token/price APIs).
445
- const slowSources = __classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)
446
- ? [__classPrivateFieldGet(this, _AssetsController_snapDataSource, "f"), __classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f")]
447
- : [__classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f")];
448
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, [
449
- createParallelBalanceMiddleware(slowSources),
450
- __classPrivateFieldGet(this, _AssetsController_detectionMiddleware, "f"),
451
- ...(__classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)
452
- ? [
453
- createParallelMiddleware([
454
- __classPrivateFieldGet(this, _AssetsController_tokenDataSource, "f"),
455
- __classPrivateFieldGet(this, _AssetsController_priceDataSource, "f"),
456
- ]),
457
- ]
458
- : []),
459
- ], request)
460
- .then(({ response: slowResponse }) => __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, { ...slowResponse, updateMode: 'merge' }))
461
- .catch((error) => log('Background pipeline failed', { error }));
482
+ const slowPipelineChainIds = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSlowPipelineChainIds).call(this, chainIds, response);
483
+ if (slowPipelineChainIds.length > 0) {
484
+ const slowSources = __classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)
485
+ ? [__classPrivateFieldGet(this, _AssetsController_snapDataSource, "f"), __classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f")]
486
+ : [__classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f")];
487
+ const slowRequest = { ...request, chainIds: slowPipelineChainIds };
488
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, [
489
+ createParallelBalanceMiddleware(slowSources),
490
+ __classPrivateFieldGet(this, _AssetsController_detectionMiddleware, "f"),
491
+ ...(__classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)
492
+ ? [
493
+ createParallelMiddleware([
494
+ __classPrivateFieldGet(this, _AssetsController_tokenDataSource, "f"),
495
+ __classPrivateFieldGet(this, _AssetsController_priceDataSource, "f"),
496
+ ]),
497
+ ]
498
+ : []),
499
+ ], slowRequest)
500
+ .then(({ response: slowResponse }) => __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, { ...slowResponse, updateMode: 'merge' }))
501
+ .catch((error) => log('Background pipeline failed', { error }));
502
+ }
462
503
  const durationMs = performance.now() - startTime;
463
504
  // Emit trace for every full fetch (Assets Health dashboard)
464
505
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_emitTrace).call(this, TRACE_FULL_FETCH, {
@@ -850,7 +891,8 @@ export class AssetsController extends BaseController {
850
891
  // RPC already fetches custom assets on purpose, and Snap handles non-EVM
851
892
  // chains the rule does not apply to, so skip the middleware for those.
852
893
  const shouldGraduateCustomAssets = sourceId === 'AccountsApiDataSource' ||
853
- sourceId === 'BackendWebsocketDataSource';
894
+ sourceId === 'BackendWebsocketDataSource' ||
895
+ sourceId === 'AccountActivityService';
854
896
  const enrichmentSources = [
855
897
  ...(shouldGraduateCustomAssets
856
898
  ? [__classPrivateFieldGet(this, _AssetsController_customAssetGraduationMiddleware, "f")]
@@ -864,7 +906,10 @@ export class AssetsController extends BaseController {
864
906
  ]));
865
907
  }
866
908
  const { response: enrichedResponse } = await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, enrichmentSources, pipelineRequest, response);
867
- await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, enrichedResponse);
909
+ await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, {
910
+ ...enrichedResponse,
911
+ replaceCoveredChainBalances: response.replaceCoveredChainBalances,
912
+ });
868
913
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_emitTrace).call(this, TRACE_UPDATE_PIPELINE, {
869
914
  source: sourceId,
870
915
  duration_ms: performance.now() - updateStart,
@@ -911,7 +956,7 @@ export class AssetsController extends BaseController {
911
956
  this.messenger.unregisterActionHandler('AssetsController:setSelectedCurrency');
912
957
  }
913
958
  }
914
- _AssetsController_isEnabled = new WeakMap(), _AssetsController_isBasicFunctionality = new WeakMap(), _AssetsController_defaultUpdateInterval = new WeakMap(), _AssetsController_trace = new WeakMap(), _AssetsController_captureException = new WeakMap(), _AssetsController_firstInitFetchReported = new WeakMap(), _AssetsController_stateSizeReported = new WeakMap(), _AssetsController_uiOpen = new WeakMap(), _AssetsController_keyringUnlocked = new WeakMap(), _AssetsController_controllerMutex = new WeakMap(), _AssetsController_activeSubscriptions = new WeakMap(), _AssetsController_enabledChains = new WeakMap(), _AssetsController_lastKnownAccountIds = new WeakMap(), _AssetsController_backendWebsocketDataSource = new WeakMap(), _AssetsController_accountsApiDataSource = new WeakMap(), _AssetsController_snapDataSource = new WeakMap(), _AssetsController_rpcDataSource = new WeakMap(), _AssetsController_stakedBalanceDataSource = new WeakMap(), _AssetsController_priceDataSource = new WeakMap(), _AssetsController_detectionMiddleware = new WeakMap(), _AssetsController_customAssetGraduationMiddleware = new WeakMap(), _AssetsController_rpcFallbackMiddleware = new WeakMap(), _AssetsController_tokenDataSource = new WeakMap(), _AssetsController_unsubscribeBasicFunctionality = new WeakMap(), _AssetsController_queryApiClient = new WeakMap(), _AssetsController_onActiveChainsUpdated = new WeakMap(), _AssetsController_instances = new WeakSet(), _AssetsController_emitTrace = function _AssetsController_emitTrace(name, data, tags = {
959
+ _AssetsController_isEnabled = new WeakMap(), _AssetsController_isBasicFunctionality = new WeakMap(), _AssetsController_defaultUpdateInterval = new WeakMap(), _AssetsController_trace = new WeakMap(), _AssetsController_captureException = new WeakMap(), _AssetsController_firstInitFetchReported = new WeakMap(), _AssetsController_stateSizeReported = new WeakMap(), _AssetsController_uiOpen = new WeakMap(), _AssetsController_keyringUnlocked = new WeakMap(), _AssetsController_controllerMutex = new WeakMap(), _AssetsController_accountRefreshMutex = new WeakMap(), _AssetsController_activeSubscriptions = new WeakMap(), _AssetsController_enabledChains = new WeakMap(), _AssetsController_lastKnownAccountIds = new WeakMap(), _AssetsController_backendWebsocketDataSource = new WeakMap(), _AssetsController_accountsApiDataSource = new WeakMap(), _AssetsController_snapDataSource = new WeakMap(), _AssetsController_rpcDataSource = new WeakMap(), _AssetsController_stakedBalanceDataSource = new WeakMap(), _AssetsController_priceDataSource = new WeakMap(), _AssetsController_detectionMiddleware = new WeakMap(), _AssetsController_customAssetGraduationMiddleware = new WeakMap(), _AssetsController_rpcFallbackMiddleware = new WeakMap(), _AssetsController_tokenDataSource = new WeakMap(), _AssetsController_unsubscribeBasicFunctionality = new WeakMap(), _AssetsController_queryApiClient = new WeakMap(), _AssetsController_onActiveChainsUpdated = new WeakMap(), _AssetsController_instances = new WeakSet(), _AssetsController_emitTrace = function _AssetsController_emitTrace(name, data, tags = {
915
960
  controller: 'AssetsController',
916
961
  }) {
917
962
  if (!__classPrivateFieldGet(this, _AssetsController_trace, "f")) {
@@ -1037,11 +1082,11 @@ _AssetsController_isEnabled = new WeakMap(), _AssetsController_isBasicFunctional
1037
1082
  // The base-controller `:stateChange` event is guaranteed to fire
1038
1083
  // when init() calls this.update(). #start() is idempotent so
1039
1084
  // repeated fires are safe.
1040
- this.messenger.subscribe('AccountTreeController:stateChange', () => {
1085
+ this.messenger.subscribe('AccountTreeController:stateChanged', () => {
1041
1086
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_handleAccountTreeStateChange).call(this);
1042
1087
  });
1043
1088
  // Subscribe to network enablement changes (only enabledNetworkMap)
1044
- this.messenger.subscribe('NetworkEnablementController:stateChange', ({ enabledNetworkMap }) => {
1089
+ this.messenger.subscribe('NetworkEnablementController:stateChanged', ({ enabledNetworkMap }) => {
1045
1090
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_handleEnabledNetworksChanged).call(this, enabledNetworkMap).catch(console.error);
1046
1091
  });
1047
1092
  // When a network is added or removed from NetworkController, refresh
@@ -1052,13 +1097,20 @@ _AssetsController_isEnabled = new WeakMap(), _AssetsController_isBasicFunctional
1052
1097
  // enabled in NetworkEnablementController.
1053
1098
  this.messenger.subscribe('NetworkController:networkAdded', (networkConfiguration) => {
1054
1099
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_handleNetworkAdded).call(this, networkConfiguration.chainId);
1055
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_refreshAssetsAfterNetworkChange).call(this);
1100
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_refreshAssetsAfterNetworkAdded).call(this, networkConfiguration.chainId).catch((error) => {
1101
+ log('Failed to refresh assets after network added', { error });
1102
+ });
1056
1103
  });
1057
1104
  this.messenger.subscribe('NetworkController:networkRemoved', () => {
1058
1105
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_refreshAssetsAfterNetworkChange).call(this);
1059
1106
  });
1107
+ // Selected EVM network switch (network picker). Enablement changes are
1108
+ // handled separately via NetworkEnablementController:stateChanged.
1109
+ this.messenger.subscribe('NetworkController:networkDidChange', (networkState) => {
1110
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_handleNetworkDidChange).call(this, networkState).catch(console.error);
1111
+ });
1060
1112
  // Client + Keyring lifecycle: only run when UI is open AND keyring is unlocked
1061
- this.messenger.subscribe('ClientController:stateChange', (isUiOpen) => {
1113
+ this.messenger.subscribe('ClientController:stateChanged', (isUiOpen) => {
1062
1114
  __classPrivateFieldSet(this, _AssetsController_uiOpen, isUiOpen, "f");
1063
1115
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateActive).call(this);
1064
1116
  }, clientControllerSelectors.selectIsUiOpen);
@@ -1075,6 +1127,18 @@ _AssetsController_isEnabled = new WeakMap(), _AssetsController_isBasicFunctional
1075
1127
  this.messenger.subscribe('TransactionController:unapprovedTransactionAdded', (transactionMeta) => {
1076
1128
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_onUnapprovedTransactionAdded).call(this, transactionMeta);
1077
1129
  });
1130
+ // Post-tx refresh via the full fetch pipeline (Accounts API + RPC fallback).
1131
+ // RpcDataSource also listens for transactionConfirmed, but only refreshes
1132
+ // chains it owns via an active subscription.
1133
+ this.messenger.subscribe('TransactionController:transactionConfirmed', (transactionMeta) => {
1134
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_onTransactionConfirmed).call(this, transactionMeta);
1135
+ });
1136
+ // Real-time post-tx balances from AccountActivityService (same WS payload as
1137
+ // TokenBalancesController; BackendWebsocketDataSource may not receive the
1138
+ // callback when AccountActivityService owns the server subscription).
1139
+ this.messenger.subscribe('AccountActivityService:balanceUpdated', (event) => {
1140
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_onAccountActivityBalanceUpdated).call(this, event);
1141
+ });
1078
1142
  }, _AssetsController_onUnapprovedTransactionAdded = function _AssetsController_onUnapprovedTransactionAdded(transactionMeta) {
1079
1143
  const hexChainId = transactionMeta.chainId;
1080
1144
  if (!hexChainId) {
@@ -1097,6 +1161,46 @@ _AssetsController_isEnabled = new WeakMap(), _AssetsController_isBasicFunctional
1097
1161
  error,
1098
1162
  });
1099
1163
  });
1164
+ }, _AssetsController_onTransactionConfirmed = function _AssetsController_onTransactionConfirmed(transactionMeta) {
1165
+ const hexChainId = transactionMeta.chainId;
1166
+ if (!hexChainId) {
1167
+ return;
1168
+ }
1169
+ const caipChainId = `eip155:${parseInt(hexChainId, 16)}`;
1170
+ const fromAddress = transactionMeta.txParams.from?.toLowerCase();
1171
+ if (!fromAddress) {
1172
+ return;
1173
+ }
1174
+ const matchedAccount = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSelectedAccounts).call(this).find((account) => account.address.toLowerCase() === fromAddress);
1175
+ if (!matchedAccount) {
1176
+ return;
1177
+ }
1178
+ this.getAssets([matchedAccount], {
1179
+ chainIds: [caipChainId],
1180
+ forceUpdate: true,
1181
+ }).catch((error) => {
1182
+ log('Failed to refresh assets after transaction confirmed', { error });
1183
+ });
1184
+ }, _AssetsController_onAccountActivityBalanceUpdated = function _AssetsController_onAccountActivityBalanceUpdated({ address, chain, updates, }) {
1185
+ const account = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSelectedAccounts).call(this).find((a) => a.address.startsWith('0x')
1186
+ ? a.address.toLowerCase() === address.toLowerCase()
1187
+ : a.address === address);
1188
+ if (!account) {
1189
+ return;
1190
+ }
1191
+ const chainId = chain;
1192
+ const response = processAccountActivityBalanceUpdates(updates, account.id, (assetId) => __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getAssetType).call(this, assetId));
1193
+ if (!response.assetsBalance) {
1194
+ return;
1195
+ }
1196
+ const request = {
1197
+ accountsWithSupportedChains: [{ account, supportedChains: [chainId] }],
1198
+ chainIds: [chainId],
1199
+ dataTypes: ['balance', 'metadata'],
1200
+ };
1201
+ this.handleAssetsUpdate(response, 'AccountActivityService', request).catch((error) => {
1202
+ log('Failed to apply AccountActivityService balance update', { error });
1203
+ });
1100
1204
  }, _AssetsController_updateActive = function _AssetsController_updateActive() {
1101
1205
  const shouldRun = __classPrivateFieldGet(this, _AssetsController_uiOpen, "f") && __classPrivateFieldGet(this, _AssetsController_keyringUnlocked, "f");
1102
1206
  if (shouldRun) {
@@ -1118,24 +1222,66 @@ _AssetsController_isEnabled = new WeakMap(), _AssetsController_isBasicFunctional
1118
1222
  if (!accountsChanged) {
1119
1223
  return;
1120
1224
  }
1225
+ const hasOverlap = [...currentIds].some((id) => __classPrivateFieldGet(this, _AssetsController_lastKnownAccountIds, "f").has(id));
1226
+ if (!hasOverlap && __classPrivateFieldGet(this, _AssetsController_lastKnownAccountIds, "f").size > 0) {
1227
+ return;
1228
+ }
1121
1229
  log('Account tree changed with new accounts, re-subscribing', {
1122
1230
  previousCount: __classPrivateFieldGet(this, _AssetsController_lastKnownAccountIds, "f").size,
1123
1231
  currentCount: currentIds.size,
1124
1232
  });
1125
1233
  __classPrivateFieldSet(this, _AssetsController_lastKnownAccountIds, currentIds, "f");
1126
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1127
1234
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
1128
1235
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureDefaultTrackedAssetsSeeded).call(this);
1129
- this.getAssets(accounts, {
1130
- chainIds: [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")],
1131
- forceUpdate: true,
1132
- }).catch((error) => {
1133
- log('Failed to fetch assets after tree change', error);
1236
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_runAccountTreeRefresh).call(this, accounts).catch((error) => {
1237
+ log('Failed to refresh assets after tree change', error);
1134
1238
  });
1135
1239
  }
1136
1240
  else {
1137
1241
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_start).call(this);
1138
1242
  }
1243
+ }, _AssetsController_runAccountTreeRefresh = async function _AssetsController_runAccountTreeRefresh(accounts) {
1244
+ const releaseLock = await __classPrivateFieldGet(this, _AssetsController_accountRefreshMutex, "f").acquire();
1245
+ try {
1246
+ await this.getAssets(accounts, {
1247
+ chainIds: [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")],
1248
+ forceUpdate: true,
1249
+ });
1250
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1251
+ }
1252
+ catch (error) {
1253
+ log('Failed to fetch assets after tree change', error);
1254
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1255
+ }
1256
+ finally {
1257
+ releaseLock();
1258
+ }
1259
+ }, _AssetsController_runStartupRefresh =
1260
+ /**
1261
+ * Force-fetch balances then subscribe. Used on unlock / first startup.
1262
+ *
1263
+ * @param accounts - Selected accounts to refresh.
1264
+ */
1265
+ async function _AssetsController_runStartupRefresh(accounts) {
1266
+ const releaseLock = await __classPrivateFieldGet(this, _AssetsController_accountRefreshMutex, "f").acquire();
1267
+ try {
1268
+ await this.getAssets(accounts, {
1269
+ chainIds: [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")],
1270
+ forceUpdate: true,
1271
+ });
1272
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1273
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
1274
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureDefaultTrackedAssetsSeeded).call(this);
1275
+ }
1276
+ catch (error) {
1277
+ log('Failed to fetch assets on startup', error);
1278
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1279
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
1280
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureDefaultTrackedAssetsSeeded).call(this);
1281
+ }
1282
+ finally {
1283
+ releaseLock();
1284
+ }
1139
1285
  }, _AssetsController_registerActionHandlers = function _AssetsController_registerActionHandlers() {
1140
1286
  this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
1141
1287
  }, _AssetsController_handleActiveChainsUpdate = function _AssetsController_handleActiveChainsUpdate(dataSourceId, activeChains, previousChains) {
@@ -1249,6 +1395,41 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1249
1395
  });
1250
1396
  }
1251
1397
  return { response: result.response, durationByDataSource };
1398
+ }, _AssetsController_fetchMissingPricesWithoutCache = function _AssetsController_fetchMissingPricesWithoutCache(accounts, chainIds) {
1399
+ if (!__classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this) || accounts.length === 0) {
1400
+ return;
1401
+ }
1402
+ const accountIds = new Set(accounts.map((account) => account.id));
1403
+ const chainFilter = new Set(chainIds);
1404
+ const assetsForPriceUpdate = [];
1405
+ const prices = this.state.assetsPrice;
1406
+ for (const [accountId, accountBalances] of Object.entries(this.state.assetsBalance)) {
1407
+ if (!accountIds.has(accountId)) {
1408
+ continue;
1409
+ }
1410
+ for (const assetId of Object.keys(accountBalances)) {
1411
+ if (!chainFilter.has(assetId.split('/')[0])) {
1412
+ continue;
1413
+ }
1414
+ const normalizedAssetId = normalizeAssetId(assetId);
1415
+ if (prices[normalizedAssetId] ?? prices[assetId]) {
1416
+ continue;
1417
+ }
1418
+ assetsForPriceUpdate.push(normalizedAssetId);
1419
+ }
1420
+ }
1421
+ if (assetsForPriceUpdate.length === 0) {
1422
+ return;
1423
+ }
1424
+ __classPrivateFieldGet(this, _AssetsController_priceDataSource, "f").invalidatePriceCache();
1425
+ this.getAssets(accounts, {
1426
+ forceUpdate: true,
1427
+ dataTypes: ['price'],
1428
+ chainIds,
1429
+ assetsForPriceUpdate,
1430
+ }).catch((error) => {
1431
+ log('Failed to fetch missing prices', { error });
1432
+ });
1252
1433
  }, _AssetsController_getNativeAssetMap = function _AssetsController_getNativeAssetMap() {
1253
1434
  return (__classPrivateFieldGet(this, _AssetsController_queryApiClient, "f").getCachedData(NATIVE_ASSETS_QUERY_KEY) ?? {});
1254
1435
  }, _AssetsController_isNativeAsset = function _AssetsController_isNativeAsset(assetId) {
@@ -1290,6 +1471,17 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1290
1471
  return __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_resolveNativeAssetIds).call(this, __classPrivateFieldGet(this, _AssetsController_enabledChains, "f"));
1291
1472
  }, _AssetsController_getNativeAssetIdsForAccount = function _AssetsController_getNativeAssetIdsForAccount(account) {
1292
1473
  return __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_resolveNativeAssetIds).call(this, __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getEnabledChainsForAccount).call(this, account));
1474
+ }, _AssetsController_getSlowPipelineChainIds = function _AssetsController_getSlowPipelineChainIds(chainIds, fastResponse) {
1475
+ const accountsApiChains = new Set(__classPrivateFieldGet(this, _AssetsController_accountsApiDataSource, "f").getActiveChainsSync());
1476
+ return chainIds.filter((chainId) => {
1477
+ if (fastResponse.errors?.[chainId]) {
1478
+ return true;
1479
+ }
1480
+ if (!accountsApiChains.has(chainId)) {
1481
+ return true;
1482
+ }
1483
+ return false;
1484
+ });
1293
1485
  }, _AssetsController_ensureNativeBalancesDefaultZero = function _AssetsController_ensureNativeBalancesDefaultZero() {
1294
1486
  const accounts = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSelectedAccounts).call(this);
1295
1487
  if (accounts.length === 0) {
@@ -1307,7 +1499,7 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1307
1499
  balances[accountId] = {};
1308
1500
  }
1309
1501
  for (const nativeAssetId of nativeAssetIds) {
1310
- if (!(nativeAssetId in balances[accountId])) {
1502
+ if (!Object.prototype.hasOwnProperty.call(balances[accountId], nativeAssetId)) {
1311
1503
  balances[accountId][nativeAssetId] = { amount: '0' };
1312
1504
  }
1313
1505
  }
@@ -1424,43 +1616,16 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1424
1616
  for (const [accountId, accountBalances] of Object.entries(normalizedResponse.assetsBalance)) {
1425
1617
  const previousBalances = previousState.assetsBalance[accountId] ?? {};
1426
1618
  const customAssetIds = state.customAssets[accountId] ?? [];
1427
- // Full: response is authoritative for the chains it covered;
1428
- // balances for chains not in the response are preserved from
1429
- // previous state so unsupported chains (e.g. Ink on AccountsAPI)
1430
- // are never inadvertently reset to zero.
1431
- // Merge: response overlays previous balances.
1432
- const effective = mode === 'merge'
1433
- ? { ...previousBalances, ...accountBalances }
1434
- : (() => {
1435
- // Determine which chain namespaces this response covers.
1436
- const coveredChains = new Set(Object.keys(accountBalances).map((assetId) => assetId.split('/')[0]));
1437
- // Start from previous balances, dropping only entries for
1438
- // chains this response is authoritative over.
1439
- const next = {};
1440
- for (const [assetId, balance] of Object.entries(previousBalances)) {
1441
- if (!coveredChains.has(assetId.split('/')[0])) {
1442
- next[assetId] = balance;
1443
- }
1444
- }
1445
- // Apply the response (authoritative for covered chains).
1446
- Object.assign(next, accountBalances);
1447
- // Preserve custom assets that the response omitted.
1448
- for (const customId of customAssetIds) {
1449
- if (!(customId in next)) {
1450
- const prev = previousBalances[customId];
1451
- next[customId] =
1452
- prev ?? { amount: '0' };
1453
- }
1454
- }
1455
- return next;
1456
- })();
1619
+ const replaceCoveredChains = mode === 'full' ||
1620
+ normalizedResponse.replaceCoveredChainBalances === true;
1621
+ const effective = mergeAccountBalances(previousBalances, accountBalances, customAssetIds, replaceCoveredChains);
1457
1622
  // Ensure native tokens have an entry (0 if missing) for chains this account supports
1458
1623
  const account = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSelectedAccounts).call(this).find((a) => a.id === accountId);
1459
1624
  const nativeAssetIdsForAccount = account
1460
1625
  ? __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getNativeAssetIdsForAccount).call(this, account)
1461
1626
  : __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getNativeAssetIdsForEnabledChains).call(this);
1462
1627
  for (const nativeAssetId of nativeAssetIdsForAccount) {
1463
- if (!(nativeAssetId in effective)) {
1628
+ if (!Object.prototype.hasOwnProperty.call(effective, nativeAssetId)) {
1464
1629
  effective[nativeAssetId] = { amount: '0' };
1465
1630
  }
1466
1631
  }
@@ -1492,7 +1657,6 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1492
1657
  balances[accountId] = effective;
1493
1658
  }
1494
1659
  }
1495
- // Update prices in state
1496
1660
  if (normalizedResponse.assetsPrice) {
1497
1661
  for (const [key, value] of Object.entries(normalizedResponse.assetsPrice)) {
1498
1662
  prices[key] = value;
@@ -1653,14 +1817,8 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1653
1817
  enabledChainCount: chainIds.length,
1654
1818
  });
1655
1819
  __classPrivateFieldSet(this, _AssetsController_lastKnownAccountIds, new Set(accounts.map((a) => a.id)), "f");
1656
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1657
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
1658
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureDefaultTrackedAssetsSeeded).call(this);
1659
- this.getAssets(accounts, {
1660
- chainIds,
1661
- forceUpdate: true,
1662
- }).catch((error) => {
1663
- log('Failed to fetch assets', error);
1820
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_runStartupRefresh).call(this, accounts).catch((error) => {
1821
+ log('Failed to start asset tracking', error);
1664
1822
  });
1665
1823
  }, _AssetsController_stop = function _AssetsController_stop() {
1666
1824
  log('Stopping asset tracking', {
@@ -1738,7 +1896,13 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1738
1896
  const seenIds = new Set();
1739
1897
  const accountsForSource = assignedChains
1740
1898
  .flatMap((chainId) => chainToAccounts.get(chainId) ?? [])
1741
- .filter((account) => !seenIds.has(account.id) && (seenIds.add(account.id), true));
1899
+ .filter((account) => {
1900
+ if (seenIds.has(account.id)) {
1901
+ return false;
1902
+ }
1903
+ seenIds.add(account.id);
1904
+ return true;
1905
+ });
1742
1906
  if (accountsForSource.length > 0) {
1743
1907
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeDataSource).call(this, source, accountsForSource, assignedChains);
1744
1908
  }
@@ -1925,16 +2089,22 @@ async function _AssetsController_handleAccountGroupChanged() {
1925
2089
  accountIds: accounts.map((a) => a.id),
1926
2090
  });
1927
2091
  __classPrivateFieldSet(this, _AssetsController_lastKnownAccountIds, new Set(accounts.map((a) => a.id)), "f");
1928
- // Subscribe and fetch for the new account group
1929
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1930
- if (accounts.length > 0) {
1931
- await this.getAssets(accounts, {
1932
- chainIds: [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")],
1933
- forceUpdate: true,
1934
- });
2092
+ const releaseLock = await __classPrivateFieldGet(this, _AssetsController_accountRefreshMutex, "f").acquire();
2093
+ try {
2094
+ if (accounts.length > 0) {
2095
+ await this.getAssets(accounts, {
2096
+ chainIds: [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")],
2097
+ forceUpdate: true,
2098
+ });
2099
+ }
2100
+ // Subscribe after fetch so WS notifications can recover state
2101
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
2102
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
2103
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureDefaultTrackedAssetsSeeded).call(this);
2104
+ }
2105
+ finally {
2106
+ releaseLock();
1935
2107
  }
1936
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
1937
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureDefaultTrackedAssetsSeeded).call(this);
1938
2108
  }, _AssetsController_handleEnabledNetworksChanged = async function _AssetsController_handleEnabledNetworksChanged(enabledNetworkMap) {
1939
2109
  const previousChains = __classPrivateFieldGet(this, _AssetsController_enabledChains, "f");
1940
2110
  __classPrivateFieldSet(this, _AssetsController_enabledChains, __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_extractEnabledChains).call(this, enabledNetworkMap), "f");
@@ -1994,11 +2164,102 @@ async function _AssetsController_handleAccountGroupChanged() {
1994
2164
  caipChainId,
1995
2165
  });
1996
2166
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureDefaultTrackedAssetsSeeded).call(this, [caipChainId]);
2167
+ }, _AssetsController_refreshActiveChainsOnNetworkSwitch =
2168
+ /**
2169
+ * Refresh data-source `activeChains` after an EVM network switch so API/WS/Rpc
2170
+ * chain claiming is not stuck on an empty or stale init-time list.
2171
+ */
2172
+ async function _AssetsController_refreshActiveChainsOnNetworkSwitch() {
2173
+ await Promise.all([
2174
+ __classPrivateFieldGet(this, _AssetsController_accountsApiDataSource, "f").refreshActiveChains(),
2175
+ __classPrivateFieldGet(this, _AssetsController_backendWebsocketDataSource, "f").refreshActiveChains(),
2176
+ ]);
2177
+ __classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f").refreshActiveChainsFromNetworkState();
2178
+ }, _AssetsController_getSelectedEvmChainIdFromNetworkState = function _AssetsController_getSelectedEvmChainIdFromNetworkState(networkState) {
2179
+ try {
2180
+ const networkClient = this.messenger.call('NetworkController:getNetworkClientById', networkState.selectedNetworkClientId);
2181
+ const hexChainId = networkClient.configuration?.chainId;
2182
+ if (!hexChainId) {
2183
+ return undefined;
2184
+ }
2185
+ return `eip155:${parseInt(hexChainId, 16)}`;
2186
+ }
2187
+ catch {
2188
+ return undefined;
2189
+ }
2190
+ }, _AssetsController_handleNetworkDidChange =
2191
+ /**
2192
+ * Refresh subscriptions and fetch balances when the user switches the
2193
+ * selected EVM network in the UI (`NetworkController:networkDidChange`).
2194
+ *
2195
+ * @param networkState - NetworkController state after the switch.
2196
+ */
2197
+ async function _AssetsController_handleNetworkDidChange(networkState) {
2198
+ if (!__classPrivateFieldGet(this, _AssetsController_uiOpen, "f") || !__classPrivateFieldGet(this, _AssetsController_keyringUnlocked, "f") || !__classPrivateFieldGet(this, _AssetsController_isEnabled, "f").call(this)) {
2199
+ return;
2200
+ }
2201
+ const accounts = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSelectedAccounts).call(this);
2202
+ if (accounts.length === 0) {
2203
+ return;
2204
+ }
2205
+ const selectedChainId = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSelectedEvmChainIdFromNetworkState).call(this, networkState);
2206
+ if (!selectedChainId) {
2207
+ return;
2208
+ }
2209
+ log('Selected EVM network switched', {
2210
+ selectedNetworkClientId: networkState.selectedNetworkClientId,
2211
+ selectedChainId,
2212
+ });
2213
+ const releaseLock = await __classPrivateFieldGet(this, _AssetsController_accountRefreshMutex, "f").acquire();
2214
+ try {
2215
+ await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_refreshActiveChainsOnNetworkSwitch).call(this);
2216
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
2217
+ await this.getAssets(accounts, {
2218
+ chainIds: [selectedChainId],
2219
+ forceUpdate: true,
2220
+ dataTypes: ['balance', 'metadata', 'price'],
2221
+ });
2222
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
2223
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_fetchMissingPricesWithoutCache).call(this, accounts, [selectedChainId]);
2224
+ }
2225
+ finally {
2226
+ releaseLock();
2227
+ }
1997
2228
  }, _AssetsController_refreshAssetsAfterNetworkChange = function _AssetsController_refreshAssetsAfterNetworkChange() {
1998
- this.getAssets(__classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSelectedAccounts).call(this), {
2229
+ const accounts = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSelectedAccounts).call(this);
2230
+ if (accounts.length === 0) {
2231
+ return;
2232
+ }
2233
+ this.getAssets(accounts, {
1999
2234
  forceUpdate: true,
2235
+ dataTypes: ['balance', 'metadata'],
2000
2236
  }).catch((error) => {
2001
2237
  log('Failed to refresh assets after network change', { error });
2002
2238
  });
2239
+ }, _AssetsController_refreshAssetsAfterNetworkAdded =
2240
+ /**
2241
+ * Refresh balances and fetch missing prices after a network is added.
2242
+ *
2243
+ * @param hexChainId - Hex chain id of the newly-added network.
2244
+ */
2245
+ async function _AssetsController_refreshAssetsAfterNetworkAdded(hexChainId) {
2246
+ const accounts = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSelectedAccounts).call(this);
2247
+ if (accounts.length === 0) {
2248
+ return;
2249
+ }
2250
+ let caipChainId;
2251
+ try {
2252
+ caipChainId = `eip155:${parseInt(hexChainId, 16)}`;
2253
+ }
2254
+ catch {
2255
+ return;
2256
+ }
2257
+ await this.getAssets(accounts, {
2258
+ chainIds: [caipChainId],
2259
+ forceUpdate: true,
2260
+ dataTypes: ['balance', 'metadata', 'price'],
2261
+ });
2262
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
2263
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_fetchMissingPricesWithoutCache).call(this, accounts, [caipChainId]);
2003
2264
  };
2004
2265
  //# sourceMappingURL=AssetsController.mjs.map