@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
@@ -13,7 +13,7 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
13
13
  var __importDefault = (this && this.__importDefault) || function (mod) {
14
14
  return (mod && mod.__esModule) ? mod : { "default": mod };
15
15
  };
16
- 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;
16
+ 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;
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
18
  exports.AssetsController = exports.getDefaultAssetsControllerState = void 0;
19
19
  const base_controller_1 = require("@metamask/base-controller");
@@ -40,6 +40,7 @@ const RpcFallbackMiddleware_1 = require("./middlewares/RpcFallbackMiddleware.cjs
40
40
  const utils_2 = require("./utils/index.cjs");
41
41
  const constants_1 = require("./utils/constants.cjs");
42
42
  const customAssetsRpcSupplement_1 = require("./utils/customAssetsRpcSupplement.cjs");
43
+ const processAccountActivityBalanceUpdates_1 = require("./utils/processAccountActivityBalanceUpdates.cjs");
43
44
  const NATIVE_ASSETS_QUERY_KEY = ['nativeAssets'];
44
45
  // ============================================================================
45
46
  // CONTROLLER CONSTANTS
@@ -192,8 +193,41 @@ function normalizeResponse(response) {
192
193
  if (response.updateMode) {
193
194
  normalized.updateMode = response.updateMode;
194
195
  }
196
+ if (response.replaceCoveredChainBalances) {
197
+ normalized.replaceCoveredChainBalances = true;
198
+ }
195
199
  return normalized;
196
200
  }
201
+ /**
202
+ * Merge account balances from a data-source response into prior state.
203
+ *
204
+ * @param previousBalances - Balances already in state for this account.
205
+ * @param accountBalances - Balances from the incoming response.
206
+ * @param customAssetIds - Custom assets to preserve when replacing covered chains.
207
+ * @param replaceCoveredChains - When true, drop prior balances on chains present
208
+ * in the response before applying it (authoritative chain slice).
209
+ * @returns The merged balance map for the account.
210
+ */
211
+ function mergeAccountBalances(previousBalances, accountBalances, customAssetIds, replaceCoveredChains) {
212
+ if (!replaceCoveredChains) {
213
+ return { ...previousBalances, ...accountBalances };
214
+ }
215
+ const coveredChains = new Set(Object.keys(accountBalances).map((assetId) => assetId.split('/')[0]));
216
+ const next = {};
217
+ for (const [assetId, balance] of Object.entries(previousBalances)) {
218
+ if (!coveredChains.has(assetId.split('/')[0])) {
219
+ next[assetId] = balance;
220
+ }
221
+ }
222
+ Object.assign(next, accountBalances);
223
+ for (const customId of customAssetIds) {
224
+ if (!Object.prototype.hasOwnProperty.call(next, customId)) {
225
+ const prev = previousBalances[customId];
226
+ next[customId] = prev ?? { amount: '0' };
227
+ }
228
+ }
229
+ return next;
230
+ }
197
231
  // ============================================================================
198
232
  // CONTROLLER IMPLEMENTATION
199
233
  // ============================================================================
@@ -255,6 +289,8 @@ class AssetsController extends base_controller_1.BaseController {
255
289
  /** Whether the keyring is unlocked. Combined with #uiOpen for #updateActive. */
256
290
  _AssetsController_keyringUnlocked.set(this, false);
257
291
  _AssetsController_controllerMutex.set(this, new async_mutex_1.Mutex());
292
+ /** Serializes account-switch fetch + subscribe to prevent overlapping races. */
293
+ _AssetsController_accountRefreshMutex.set(this, new async_mutex_1.Mutex());
258
294
  /**
259
295
  * Active balance subscriptions keyed by account ID.
260
296
  * Each account has one logical subscription that may span multiple data sources.
@@ -418,9 +454,9 @@ class AssetsController extends base_controller_1.BaseController {
418
454
  // creation, RPC is slow on many chains). Results are committed to state
419
455
  // immediately so the UI can display balances without waiting for them.
420
456
  //
421
- // Both the fast and background pipelines use 'merge' mode because neither
422
- // alone represents the full set of data sources. Using 'full' in either
423
- // would wipe balances from the sources handled by the other pipeline.
457
+ // Fast/slow pipelines use merge so partial API snapshots cannot wipe
458
+ // tokens missing from the response (e.g. USDC when only native balance
459
+ // is returned). Balances present in the response are still refreshed.
424
460
  const fastSources = __classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)
425
461
  ? [
426
462
  (0, ParallelMiddleware_1.createParallelBalanceMiddleware)([
@@ -440,31 +476,36 @@ class AssetsController extends base_controller_1.BaseController {
440
476
  ]
441
477
  : [__classPrivateFieldGet(this, _AssetsController_stakedBalanceDataSource, "f"), __classPrivateFieldGet(this, _AssetsController_detectionMiddleware, "f")];
442
478
  const { response, durationByDataSource } = await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, fastSources, request);
443
- // The fast pipeline only contains a subset of data sources (AccountsApi +
444
- // StakedBalance), so it must always merge to avoid wiping Snap/RPC
445
- // balances that the background pipeline hasn't yet replaced.
446
- await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, { ...response, updateMode: 'merge' });
479
+ await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, {
480
+ ...response,
481
+ updateMode: 'merge',
482
+ replaceCoveredChainBalances: true,
483
+ });
447
484
  // Background pipeline: snap and RPC run in parallel after the fast path
448
485
  // commits to state. Their balances are merged together before detection.
449
486
  // Token + price enrichment matches the pre-split behavior: only when basic
450
487
  // functionality is on (RPC-only mode must not call token/price APIs).
451
- const slowSources = __classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)
452
- ? [__classPrivateFieldGet(this, _AssetsController_snapDataSource, "f"), __classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f")]
453
- : [__classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f")];
454
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, [
455
- (0, ParallelMiddleware_1.createParallelBalanceMiddleware)(slowSources),
456
- __classPrivateFieldGet(this, _AssetsController_detectionMiddleware, "f"),
457
- ...(__classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)
458
- ? [
459
- (0, ParallelMiddleware_1.createParallelMiddleware)([
460
- __classPrivateFieldGet(this, _AssetsController_tokenDataSource, "f"),
461
- __classPrivateFieldGet(this, _AssetsController_priceDataSource, "f"),
462
- ]),
463
- ]
464
- : []),
465
- ], request)
466
- .then(({ response: slowResponse }) => __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, { ...slowResponse, updateMode: 'merge' }))
467
- .catch((error) => log('Background pipeline failed', { error }));
488
+ const slowPipelineChainIds = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSlowPipelineChainIds).call(this, chainIds, response);
489
+ if (slowPipelineChainIds.length > 0) {
490
+ const slowSources = __classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)
491
+ ? [__classPrivateFieldGet(this, _AssetsController_snapDataSource, "f"), __classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f")]
492
+ : [__classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f")];
493
+ const slowRequest = { ...request, chainIds: slowPipelineChainIds };
494
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, [
495
+ (0, ParallelMiddleware_1.createParallelBalanceMiddleware)(slowSources),
496
+ __classPrivateFieldGet(this, _AssetsController_detectionMiddleware, "f"),
497
+ ...(__classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)
498
+ ? [
499
+ (0, ParallelMiddleware_1.createParallelMiddleware)([
500
+ __classPrivateFieldGet(this, _AssetsController_tokenDataSource, "f"),
501
+ __classPrivateFieldGet(this, _AssetsController_priceDataSource, "f"),
502
+ ]),
503
+ ]
504
+ : []),
505
+ ], slowRequest)
506
+ .then(({ response: slowResponse }) => __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, { ...slowResponse, updateMode: 'merge' }))
507
+ .catch((error) => log('Background pipeline failed', { error }));
508
+ }
468
509
  const durationMs = performance.now() - startTime;
469
510
  // Emit trace for every full fetch (Assets Health dashboard)
470
511
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_emitTrace).call(this, TRACE_FULL_FETCH, {
@@ -856,7 +897,8 @@ class AssetsController extends base_controller_1.BaseController {
856
897
  // RPC already fetches custom assets on purpose, and Snap handles non-EVM
857
898
  // chains the rule does not apply to, so skip the middleware for those.
858
899
  const shouldGraduateCustomAssets = sourceId === 'AccountsApiDataSource' ||
859
- sourceId === 'BackendWebsocketDataSource';
900
+ sourceId === 'BackendWebsocketDataSource' ||
901
+ sourceId === 'AccountActivityService';
860
902
  const enrichmentSources = [
861
903
  ...(shouldGraduateCustomAssets
862
904
  ? [__classPrivateFieldGet(this, _AssetsController_customAssetGraduationMiddleware, "f")]
@@ -870,7 +912,10 @@ class AssetsController extends base_controller_1.BaseController {
870
912
  ]));
871
913
  }
872
914
  const { response: enrichedResponse } = await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, enrichmentSources, pipelineRequest, response);
873
- await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, enrichedResponse);
915
+ await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, {
916
+ ...enrichedResponse,
917
+ replaceCoveredChainBalances: response.replaceCoveredChainBalances,
918
+ });
874
919
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_emitTrace).call(this, TRACE_UPDATE_PIPELINE, {
875
920
  source: sourceId,
876
921
  duration_ms: performance.now() - updateStart,
@@ -918,7 +963,7 @@ class AssetsController extends base_controller_1.BaseController {
918
963
  }
919
964
  }
920
965
  exports.AssetsController = AssetsController;
921
- _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 = {
966
+ _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 = {
922
967
  controller: 'AssetsController',
923
968
  }) {
924
969
  if (!__classPrivateFieldGet(this, _AssetsController_trace, "f")) {
@@ -1044,11 +1089,11 @@ _AssetsController_isEnabled = new WeakMap(), _AssetsController_isBasicFunctional
1044
1089
  // The base-controller `:stateChange` event is guaranteed to fire
1045
1090
  // when init() calls this.update(). #start() is idempotent so
1046
1091
  // repeated fires are safe.
1047
- this.messenger.subscribe('AccountTreeController:stateChange', () => {
1092
+ this.messenger.subscribe('AccountTreeController:stateChanged', () => {
1048
1093
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_handleAccountTreeStateChange).call(this);
1049
1094
  });
1050
1095
  // Subscribe to network enablement changes (only enabledNetworkMap)
1051
- this.messenger.subscribe('NetworkEnablementController:stateChange', ({ enabledNetworkMap }) => {
1096
+ this.messenger.subscribe('NetworkEnablementController:stateChanged', ({ enabledNetworkMap }) => {
1052
1097
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_handleEnabledNetworksChanged).call(this, enabledNetworkMap).catch(console.error);
1053
1098
  });
1054
1099
  // When a network is added or removed from NetworkController, refresh
@@ -1059,13 +1104,20 @@ _AssetsController_isEnabled = new WeakMap(), _AssetsController_isBasicFunctional
1059
1104
  // enabled in NetworkEnablementController.
1060
1105
  this.messenger.subscribe('NetworkController:networkAdded', (networkConfiguration) => {
1061
1106
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_handleNetworkAdded).call(this, networkConfiguration.chainId);
1062
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_refreshAssetsAfterNetworkChange).call(this);
1107
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_refreshAssetsAfterNetworkAdded).call(this, networkConfiguration.chainId).catch((error) => {
1108
+ log('Failed to refresh assets after network added', { error });
1109
+ });
1063
1110
  });
1064
1111
  this.messenger.subscribe('NetworkController:networkRemoved', () => {
1065
1112
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_refreshAssetsAfterNetworkChange).call(this);
1066
1113
  });
1114
+ // Selected EVM network switch (network picker). Enablement changes are
1115
+ // handled separately via NetworkEnablementController:stateChanged.
1116
+ this.messenger.subscribe('NetworkController:networkDidChange', (networkState) => {
1117
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_handleNetworkDidChange).call(this, networkState).catch(console.error);
1118
+ });
1067
1119
  // Client + Keyring lifecycle: only run when UI is open AND keyring is unlocked
1068
- this.messenger.subscribe('ClientController:stateChange', (isUiOpen) => {
1120
+ this.messenger.subscribe('ClientController:stateChanged', (isUiOpen) => {
1069
1121
  __classPrivateFieldSet(this, _AssetsController_uiOpen, isUiOpen, "f");
1070
1122
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateActive).call(this);
1071
1123
  }, client_controller_1.clientControllerSelectors.selectIsUiOpen);
@@ -1082,6 +1134,18 @@ _AssetsController_isEnabled = new WeakMap(), _AssetsController_isBasicFunctional
1082
1134
  this.messenger.subscribe('TransactionController:unapprovedTransactionAdded', (transactionMeta) => {
1083
1135
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_onUnapprovedTransactionAdded).call(this, transactionMeta);
1084
1136
  });
1137
+ // Post-tx refresh via the full fetch pipeline (Accounts API + RPC fallback).
1138
+ // RpcDataSource also listens for transactionConfirmed, but only refreshes
1139
+ // chains it owns via an active subscription.
1140
+ this.messenger.subscribe('TransactionController:transactionConfirmed', (transactionMeta) => {
1141
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_onTransactionConfirmed).call(this, transactionMeta);
1142
+ });
1143
+ // Real-time post-tx balances from AccountActivityService (same WS payload as
1144
+ // TokenBalancesController; BackendWebsocketDataSource may not receive the
1145
+ // callback when AccountActivityService owns the server subscription).
1146
+ this.messenger.subscribe('AccountActivityService:balanceUpdated', (event) => {
1147
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_onAccountActivityBalanceUpdated).call(this, event);
1148
+ });
1085
1149
  }, _AssetsController_onUnapprovedTransactionAdded = function _AssetsController_onUnapprovedTransactionAdded(transactionMeta) {
1086
1150
  const hexChainId = transactionMeta.chainId;
1087
1151
  if (!hexChainId) {
@@ -1104,6 +1168,46 @@ _AssetsController_isEnabled = new WeakMap(), _AssetsController_isBasicFunctional
1104
1168
  error,
1105
1169
  });
1106
1170
  });
1171
+ }, _AssetsController_onTransactionConfirmed = function _AssetsController_onTransactionConfirmed(transactionMeta) {
1172
+ const hexChainId = transactionMeta.chainId;
1173
+ if (!hexChainId) {
1174
+ return;
1175
+ }
1176
+ const caipChainId = `eip155:${parseInt(hexChainId, 16)}`;
1177
+ const fromAddress = transactionMeta.txParams.from?.toLowerCase();
1178
+ if (!fromAddress) {
1179
+ return;
1180
+ }
1181
+ const matchedAccount = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSelectedAccounts).call(this).find((account) => account.address.toLowerCase() === fromAddress);
1182
+ if (!matchedAccount) {
1183
+ return;
1184
+ }
1185
+ this.getAssets([matchedAccount], {
1186
+ chainIds: [caipChainId],
1187
+ forceUpdate: true,
1188
+ }).catch((error) => {
1189
+ log('Failed to refresh assets after transaction confirmed', { error });
1190
+ });
1191
+ }, _AssetsController_onAccountActivityBalanceUpdated = function _AssetsController_onAccountActivityBalanceUpdated({ address, chain, updates, }) {
1192
+ const account = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSelectedAccounts).call(this).find((a) => a.address.startsWith('0x')
1193
+ ? a.address.toLowerCase() === address.toLowerCase()
1194
+ : a.address === address);
1195
+ if (!account) {
1196
+ return;
1197
+ }
1198
+ const chainId = chain;
1199
+ const response = (0, processAccountActivityBalanceUpdates_1.processAccountActivityBalanceUpdates)(updates, account.id, (assetId) => __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getAssetType).call(this, assetId));
1200
+ if (!response.assetsBalance) {
1201
+ return;
1202
+ }
1203
+ const request = {
1204
+ accountsWithSupportedChains: [{ account, supportedChains: [chainId] }],
1205
+ chainIds: [chainId],
1206
+ dataTypes: ['balance', 'metadata'],
1207
+ };
1208
+ this.handleAssetsUpdate(response, 'AccountActivityService', request).catch((error) => {
1209
+ log('Failed to apply AccountActivityService balance update', { error });
1210
+ });
1107
1211
  }, _AssetsController_updateActive = function _AssetsController_updateActive() {
1108
1212
  const shouldRun = __classPrivateFieldGet(this, _AssetsController_uiOpen, "f") && __classPrivateFieldGet(this, _AssetsController_keyringUnlocked, "f");
1109
1213
  if (shouldRun) {
@@ -1125,24 +1229,66 @@ _AssetsController_isEnabled = new WeakMap(), _AssetsController_isBasicFunctional
1125
1229
  if (!accountsChanged) {
1126
1230
  return;
1127
1231
  }
1232
+ const hasOverlap = [...currentIds].some((id) => __classPrivateFieldGet(this, _AssetsController_lastKnownAccountIds, "f").has(id));
1233
+ if (!hasOverlap && __classPrivateFieldGet(this, _AssetsController_lastKnownAccountIds, "f").size > 0) {
1234
+ return;
1235
+ }
1128
1236
  log('Account tree changed with new accounts, re-subscribing', {
1129
1237
  previousCount: __classPrivateFieldGet(this, _AssetsController_lastKnownAccountIds, "f").size,
1130
1238
  currentCount: currentIds.size,
1131
1239
  });
1132
1240
  __classPrivateFieldSet(this, _AssetsController_lastKnownAccountIds, currentIds, "f");
1133
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1134
1241
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
1135
1242
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureDefaultTrackedAssetsSeeded).call(this);
1136
- this.getAssets(accounts, {
1137
- chainIds: [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")],
1138
- forceUpdate: true,
1139
- }).catch((error) => {
1140
- log('Failed to fetch assets after tree change', error);
1243
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_runAccountTreeRefresh).call(this, accounts).catch((error) => {
1244
+ log('Failed to refresh assets after tree change', error);
1141
1245
  });
1142
1246
  }
1143
1247
  else {
1144
1248
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_start).call(this);
1145
1249
  }
1250
+ }, _AssetsController_runAccountTreeRefresh = async function _AssetsController_runAccountTreeRefresh(accounts) {
1251
+ const releaseLock = await __classPrivateFieldGet(this, _AssetsController_accountRefreshMutex, "f").acquire();
1252
+ try {
1253
+ await this.getAssets(accounts, {
1254
+ chainIds: [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")],
1255
+ forceUpdate: true,
1256
+ });
1257
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1258
+ }
1259
+ catch (error) {
1260
+ log('Failed to fetch assets after tree change', error);
1261
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1262
+ }
1263
+ finally {
1264
+ releaseLock();
1265
+ }
1266
+ }, _AssetsController_runStartupRefresh =
1267
+ /**
1268
+ * Force-fetch balances then subscribe. Used on unlock / first startup.
1269
+ *
1270
+ * @param accounts - Selected accounts to refresh.
1271
+ */
1272
+ async function _AssetsController_runStartupRefresh(accounts) {
1273
+ const releaseLock = await __classPrivateFieldGet(this, _AssetsController_accountRefreshMutex, "f").acquire();
1274
+ try {
1275
+ await this.getAssets(accounts, {
1276
+ chainIds: [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")],
1277
+ forceUpdate: true,
1278
+ });
1279
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1280
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
1281
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureDefaultTrackedAssetsSeeded).call(this);
1282
+ }
1283
+ catch (error) {
1284
+ log('Failed to fetch assets on startup', error);
1285
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1286
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
1287
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureDefaultTrackedAssetsSeeded).call(this);
1288
+ }
1289
+ finally {
1290
+ releaseLock();
1291
+ }
1146
1292
  }, _AssetsController_registerActionHandlers = function _AssetsController_registerActionHandlers() {
1147
1293
  this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
1148
1294
  }, _AssetsController_handleActiveChainsUpdate = function _AssetsController_handleActiveChainsUpdate(dataSourceId, activeChains, previousChains) {
@@ -1256,6 +1402,41 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1256
1402
  });
1257
1403
  }
1258
1404
  return { response: result.response, durationByDataSource };
1405
+ }, _AssetsController_fetchMissingPricesWithoutCache = function _AssetsController_fetchMissingPricesWithoutCache(accounts, chainIds) {
1406
+ if (!__classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this) || accounts.length === 0) {
1407
+ return;
1408
+ }
1409
+ const accountIds = new Set(accounts.map((account) => account.id));
1410
+ const chainFilter = new Set(chainIds);
1411
+ const assetsForPriceUpdate = [];
1412
+ const prices = this.state.assetsPrice;
1413
+ for (const [accountId, accountBalances] of Object.entries(this.state.assetsBalance)) {
1414
+ if (!accountIds.has(accountId)) {
1415
+ continue;
1416
+ }
1417
+ for (const assetId of Object.keys(accountBalances)) {
1418
+ if (!chainFilter.has(assetId.split('/')[0])) {
1419
+ continue;
1420
+ }
1421
+ const normalizedAssetId = (0, utils_2.normalizeAssetId)(assetId);
1422
+ if (prices[normalizedAssetId] ?? prices[assetId]) {
1423
+ continue;
1424
+ }
1425
+ assetsForPriceUpdate.push(normalizedAssetId);
1426
+ }
1427
+ }
1428
+ if (assetsForPriceUpdate.length === 0) {
1429
+ return;
1430
+ }
1431
+ __classPrivateFieldGet(this, _AssetsController_priceDataSource, "f").invalidatePriceCache();
1432
+ this.getAssets(accounts, {
1433
+ forceUpdate: true,
1434
+ dataTypes: ['price'],
1435
+ chainIds,
1436
+ assetsForPriceUpdate,
1437
+ }).catch((error) => {
1438
+ log('Failed to fetch missing prices', { error });
1439
+ });
1259
1440
  }, _AssetsController_getNativeAssetMap = function _AssetsController_getNativeAssetMap() {
1260
1441
  return (__classPrivateFieldGet(this, _AssetsController_queryApiClient, "f").getCachedData(NATIVE_ASSETS_QUERY_KEY) ?? {});
1261
1442
  }, _AssetsController_isNativeAsset = function _AssetsController_isNativeAsset(assetId) {
@@ -1297,6 +1478,17 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1297
1478
  return __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_resolveNativeAssetIds).call(this, __classPrivateFieldGet(this, _AssetsController_enabledChains, "f"));
1298
1479
  }, _AssetsController_getNativeAssetIdsForAccount = function _AssetsController_getNativeAssetIdsForAccount(account) {
1299
1480
  return __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_resolveNativeAssetIds).call(this, __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getEnabledChainsForAccount).call(this, account));
1481
+ }, _AssetsController_getSlowPipelineChainIds = function _AssetsController_getSlowPipelineChainIds(chainIds, fastResponse) {
1482
+ const accountsApiChains = new Set(__classPrivateFieldGet(this, _AssetsController_accountsApiDataSource, "f").getActiveChainsSync());
1483
+ return chainIds.filter((chainId) => {
1484
+ if (fastResponse.errors?.[chainId]) {
1485
+ return true;
1486
+ }
1487
+ if (!accountsApiChains.has(chainId)) {
1488
+ return true;
1489
+ }
1490
+ return false;
1491
+ });
1300
1492
  }, _AssetsController_ensureNativeBalancesDefaultZero = function _AssetsController_ensureNativeBalancesDefaultZero() {
1301
1493
  const accounts = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSelectedAccounts).call(this);
1302
1494
  if (accounts.length === 0) {
@@ -1314,7 +1506,7 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1314
1506
  balances[accountId] = {};
1315
1507
  }
1316
1508
  for (const nativeAssetId of nativeAssetIds) {
1317
- if (!(nativeAssetId in balances[accountId])) {
1509
+ if (!Object.prototype.hasOwnProperty.call(balances[accountId], nativeAssetId)) {
1318
1510
  balances[accountId][nativeAssetId] = { amount: '0' };
1319
1511
  }
1320
1512
  }
@@ -1431,43 +1623,16 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1431
1623
  for (const [accountId, accountBalances] of Object.entries(normalizedResponse.assetsBalance)) {
1432
1624
  const previousBalances = previousState.assetsBalance[accountId] ?? {};
1433
1625
  const customAssetIds = state.customAssets[accountId] ?? [];
1434
- // Full: response is authoritative for the chains it covered;
1435
- // balances for chains not in the response are preserved from
1436
- // previous state so unsupported chains (e.g. Ink on AccountsAPI)
1437
- // are never inadvertently reset to zero.
1438
- // Merge: response overlays previous balances.
1439
- const effective = mode === 'merge'
1440
- ? { ...previousBalances, ...accountBalances }
1441
- : (() => {
1442
- // Determine which chain namespaces this response covers.
1443
- const coveredChains = new Set(Object.keys(accountBalances).map((assetId) => assetId.split('/')[0]));
1444
- // Start from previous balances, dropping only entries for
1445
- // chains this response is authoritative over.
1446
- const next = {};
1447
- for (const [assetId, balance] of Object.entries(previousBalances)) {
1448
- if (!coveredChains.has(assetId.split('/')[0])) {
1449
- next[assetId] = balance;
1450
- }
1451
- }
1452
- // Apply the response (authoritative for covered chains).
1453
- Object.assign(next, accountBalances);
1454
- // Preserve custom assets that the response omitted.
1455
- for (const customId of customAssetIds) {
1456
- if (!(customId in next)) {
1457
- const prev = previousBalances[customId];
1458
- next[customId] =
1459
- prev ?? { amount: '0' };
1460
- }
1461
- }
1462
- return next;
1463
- })();
1626
+ const replaceCoveredChains = mode === 'full' ||
1627
+ normalizedResponse.replaceCoveredChainBalances === true;
1628
+ const effective = mergeAccountBalances(previousBalances, accountBalances, customAssetIds, replaceCoveredChains);
1464
1629
  // Ensure native tokens have an entry (0 if missing) for chains this account supports
1465
1630
  const account = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSelectedAccounts).call(this).find((a) => a.id === accountId);
1466
1631
  const nativeAssetIdsForAccount = account
1467
1632
  ? __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getNativeAssetIdsForAccount).call(this, account)
1468
1633
  : __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getNativeAssetIdsForEnabledChains).call(this);
1469
1634
  for (const nativeAssetId of nativeAssetIdsForAccount) {
1470
- if (!(nativeAssetId in effective)) {
1635
+ if (!Object.prototype.hasOwnProperty.call(effective, nativeAssetId)) {
1471
1636
  effective[nativeAssetId] = { amount: '0' };
1472
1637
  }
1473
1638
  }
@@ -1499,7 +1664,6 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1499
1664
  balances[accountId] = effective;
1500
1665
  }
1501
1666
  }
1502
- // Update prices in state
1503
1667
  if (normalizedResponse.assetsPrice) {
1504
1668
  for (const [key, value] of Object.entries(normalizedResponse.assetsPrice)) {
1505
1669
  prices[key] = value;
@@ -1660,14 +1824,8 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1660
1824
  enabledChainCount: chainIds.length,
1661
1825
  });
1662
1826
  __classPrivateFieldSet(this, _AssetsController_lastKnownAccountIds, new Set(accounts.map((a) => a.id)), "f");
1663
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1664
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
1665
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureDefaultTrackedAssetsSeeded).call(this);
1666
- this.getAssets(accounts, {
1667
- chainIds,
1668
- forceUpdate: true,
1669
- }).catch((error) => {
1670
- log('Failed to fetch assets', error);
1827
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_runStartupRefresh).call(this, accounts).catch((error) => {
1828
+ log('Failed to start asset tracking', error);
1671
1829
  });
1672
1830
  }, _AssetsController_stop = function _AssetsController_stop() {
1673
1831
  log('Stopping asset tracking', {
@@ -1745,7 +1903,13 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1745
1903
  const seenIds = new Set();
1746
1904
  const accountsForSource = assignedChains
1747
1905
  .flatMap((chainId) => chainToAccounts.get(chainId) ?? [])
1748
- .filter((account) => !seenIds.has(account.id) && (seenIds.add(account.id), true));
1906
+ .filter((account) => {
1907
+ if (seenIds.has(account.id)) {
1908
+ return false;
1909
+ }
1910
+ seenIds.add(account.id);
1911
+ return true;
1912
+ });
1749
1913
  if (accountsForSource.length > 0) {
1750
1914
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeDataSource).call(this, source, accountsForSource, assignedChains);
1751
1915
  }
@@ -1932,16 +2096,22 @@ async function _AssetsController_handleAccountGroupChanged() {
1932
2096
  accountIds: accounts.map((a) => a.id),
1933
2097
  });
1934
2098
  __classPrivateFieldSet(this, _AssetsController_lastKnownAccountIds, new Set(accounts.map((a) => a.id)), "f");
1935
- // Subscribe and fetch for the new account group
1936
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1937
- if (accounts.length > 0) {
1938
- await this.getAssets(accounts, {
1939
- chainIds: [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")],
1940
- forceUpdate: true,
1941
- });
2099
+ const releaseLock = await __classPrivateFieldGet(this, _AssetsController_accountRefreshMutex, "f").acquire();
2100
+ try {
2101
+ if (accounts.length > 0) {
2102
+ await this.getAssets(accounts, {
2103
+ chainIds: [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")],
2104
+ forceUpdate: true,
2105
+ });
2106
+ }
2107
+ // Subscribe after fetch so WS notifications can recover state
2108
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
2109
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
2110
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureDefaultTrackedAssetsSeeded).call(this);
2111
+ }
2112
+ finally {
2113
+ releaseLock();
1942
2114
  }
1943
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
1944
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureDefaultTrackedAssetsSeeded).call(this);
1945
2115
  }, _AssetsController_handleEnabledNetworksChanged = async function _AssetsController_handleEnabledNetworksChanged(enabledNetworkMap) {
1946
2116
  const previousChains = __classPrivateFieldGet(this, _AssetsController_enabledChains, "f");
1947
2117
  __classPrivateFieldSet(this, _AssetsController_enabledChains, __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_extractEnabledChains).call(this, enabledNetworkMap), "f");
@@ -2001,11 +2171,102 @@ async function _AssetsController_handleAccountGroupChanged() {
2001
2171
  caipChainId,
2002
2172
  });
2003
2173
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureDefaultTrackedAssetsSeeded).call(this, [caipChainId]);
2174
+ }, _AssetsController_refreshActiveChainsOnNetworkSwitch =
2175
+ /**
2176
+ * Refresh data-source `activeChains` after an EVM network switch so API/WS/Rpc
2177
+ * chain claiming is not stuck on an empty or stale init-time list.
2178
+ */
2179
+ async function _AssetsController_refreshActiveChainsOnNetworkSwitch() {
2180
+ await Promise.all([
2181
+ __classPrivateFieldGet(this, _AssetsController_accountsApiDataSource, "f").refreshActiveChains(),
2182
+ __classPrivateFieldGet(this, _AssetsController_backendWebsocketDataSource, "f").refreshActiveChains(),
2183
+ ]);
2184
+ __classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f").refreshActiveChainsFromNetworkState();
2185
+ }, _AssetsController_getSelectedEvmChainIdFromNetworkState = function _AssetsController_getSelectedEvmChainIdFromNetworkState(networkState) {
2186
+ try {
2187
+ const networkClient = this.messenger.call('NetworkController:getNetworkClientById', networkState.selectedNetworkClientId);
2188
+ const hexChainId = networkClient.configuration?.chainId;
2189
+ if (!hexChainId) {
2190
+ return undefined;
2191
+ }
2192
+ return `eip155:${parseInt(hexChainId, 16)}`;
2193
+ }
2194
+ catch {
2195
+ return undefined;
2196
+ }
2197
+ }, _AssetsController_handleNetworkDidChange =
2198
+ /**
2199
+ * Refresh subscriptions and fetch balances when the user switches the
2200
+ * selected EVM network in the UI (`NetworkController:networkDidChange`).
2201
+ *
2202
+ * @param networkState - NetworkController state after the switch.
2203
+ */
2204
+ async function _AssetsController_handleNetworkDidChange(networkState) {
2205
+ if (!__classPrivateFieldGet(this, _AssetsController_uiOpen, "f") || !__classPrivateFieldGet(this, _AssetsController_keyringUnlocked, "f") || !__classPrivateFieldGet(this, _AssetsController_isEnabled, "f").call(this)) {
2206
+ return;
2207
+ }
2208
+ const accounts = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSelectedAccounts).call(this);
2209
+ if (accounts.length === 0) {
2210
+ return;
2211
+ }
2212
+ const selectedChainId = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSelectedEvmChainIdFromNetworkState).call(this, networkState);
2213
+ if (!selectedChainId) {
2214
+ return;
2215
+ }
2216
+ log('Selected EVM network switched', {
2217
+ selectedNetworkClientId: networkState.selectedNetworkClientId,
2218
+ selectedChainId,
2219
+ });
2220
+ const releaseLock = await __classPrivateFieldGet(this, _AssetsController_accountRefreshMutex, "f").acquire();
2221
+ try {
2222
+ await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_refreshActiveChainsOnNetworkSwitch).call(this);
2223
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
2224
+ await this.getAssets(accounts, {
2225
+ chainIds: [selectedChainId],
2226
+ forceUpdate: true,
2227
+ dataTypes: ['balance', 'metadata', 'price'],
2228
+ });
2229
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
2230
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_fetchMissingPricesWithoutCache).call(this, accounts, [selectedChainId]);
2231
+ }
2232
+ finally {
2233
+ releaseLock();
2234
+ }
2004
2235
  }, _AssetsController_refreshAssetsAfterNetworkChange = function _AssetsController_refreshAssetsAfterNetworkChange() {
2005
- this.getAssets(__classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSelectedAccounts).call(this), {
2236
+ const accounts = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSelectedAccounts).call(this);
2237
+ if (accounts.length === 0) {
2238
+ return;
2239
+ }
2240
+ this.getAssets(accounts, {
2006
2241
  forceUpdate: true,
2242
+ dataTypes: ['balance', 'metadata'],
2007
2243
  }).catch((error) => {
2008
2244
  log('Failed to refresh assets after network change', { error });
2009
2245
  });
2246
+ }, _AssetsController_refreshAssetsAfterNetworkAdded =
2247
+ /**
2248
+ * Refresh balances and fetch missing prices after a network is added.
2249
+ *
2250
+ * @param hexChainId - Hex chain id of the newly-added network.
2251
+ */
2252
+ async function _AssetsController_refreshAssetsAfterNetworkAdded(hexChainId) {
2253
+ const accounts = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSelectedAccounts).call(this);
2254
+ if (accounts.length === 0) {
2255
+ return;
2256
+ }
2257
+ let caipChainId;
2258
+ try {
2259
+ caipChainId = `eip155:${parseInt(hexChainId, 16)}`;
2260
+ }
2261
+ catch {
2262
+ return;
2263
+ }
2264
+ await this.getAssets(accounts, {
2265
+ chainIds: [caipChainId],
2266
+ forceUpdate: true,
2267
+ dataTypes: ['balance', 'metadata', 'price'],
2268
+ });
2269
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
2270
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_fetchMissingPricesWithoutCache).call(this, accounts, [caipChainId]);
2010
2271
  };
2011
2272
  //# sourceMappingURL=AssetsController.cjs.map