@metamask-previews/assets-controller 13.1.4-preview-c9c49e0 → 13.1.4-preview-57cd9b4fb

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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Changed
11
+
12
+ - **BREAKING:** Subscribe to `AccountTreeController:initialized` / `:uninitialized` (typed as `AccountTreeControllerInitializedEvent` / `AccountTreeControllerUninitializedEvent`) so asset tracking starts only after the account tree is fully built, instead of on intermediate `AccountTreeController:stateChange` events during `init()` or on unlock before the tree is ready ([#9892](https://github.com/MetaMask/core/pull/9892))
13
+ - Hosts that restrict which events flow through the `AssetsController` messenger must now also delegate `AccountTreeController:initialized` and `AccountTreeController:uninitialized`
14
+ - Reduce Accounts API calls on startup and refresh:
15
+ - Skip `AccountsApiDataSource` subscribe-time initial poll after a forced `getAssets` balance fetch
16
+ - Ignore init-time `AccountTreeController:selectedAccountGroupChange` (including same-group re-emits and events before tracking starts) so `:initialized` owns first start; only refresh on real group switches while tracking
17
+ - Skip Accounts API middleware when `dataTypes` does not include `balance` (e.g. price-only refreshes)
18
+
10
19
  ## [13.1.4]
11
20
 
12
21
  ### Changed
@@ -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_emitStateSizeTrace, _AssetsController_uiOpen, _AssetsController_keyringUnlocked, _AssetsController_controllerMutex, _AssetsController_accountRefreshMutex, _AssetsController_activeSubscriptions, _AssetsController_enabledChains, _AssetsController_lastKnownAccountIds, _AssetsController_getSelectedAccounts, _AssetsController_accountActivityDataSource, _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_updateActive, _AssetsController_handleAccountTreeStateChange, _AssetsController_runAccountTreeRefresh, _AssetsController_runStartupRefresh, _AssetsController_registerActionHandlers, _AssetsController_handleActiveChainsUpdate, _AssetsController_executeMiddlewares, _AssetsController_assertNonEmptyAccountId, _AssetsController_normalizeAssetIdOrThrow, _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_getAssetFromState, _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;
16
+ var _AssetsController_instances, _AssetsController_isEnabled, _AssetsController_isBasicFunctionality, _AssetsController_defaultUpdateInterval, _AssetsController_trace, _AssetsController_captureException, _AssetsController_firstInitFetchReported, _AssetsController_stateSizeReported, _AssetsController_emitStateSizeTrace, _AssetsController_uiOpen, _AssetsController_keyringUnlocked, _AssetsController_accountTreeInitialized, _AssetsController_controllerMutex, _AssetsController_accountRefreshMutex, _AssetsController_activeSubscriptions, _AssetsController_enabledChains, _AssetsController_lastKnownAccountIds, _AssetsController_getSelectedAccounts, _AssetsController_accountActivityDataSource, _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_updateActive, _AssetsController_handleAccountTreeStateChange, _AssetsController_runAccountTreeRefresh, _AssetsController_runStartupRefresh, _AssetsController_registerActionHandlers, _AssetsController_handleActiveChainsUpdate, _AssetsController_executeMiddlewares, _AssetsController_assertNonEmptyAccountId, _AssetsController_normalizeAssetIdOrThrow, _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_getAssetFromState, _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");
@@ -309,6 +309,11 @@ class AssetsController extends base_controller_1.BaseController {
309
309
  _AssetsController_uiOpen.set(this, false);
310
310
  /** Whether the keyring is unlocked. Combined with #uiOpen for #updateActive. */
311
311
  _AssetsController_keyringUnlocked.set(this, false);
312
+ /**
313
+ * Whether `AccountTreeController` has finished `init()`. Unlock / UI-open
314
+ * alone must not start fetches — the tree can still be mid-build.
315
+ */
316
+ _AssetsController_accountTreeInitialized.set(this, false);
312
317
  _AssetsController_controllerMutex.set(this, new async_mutex_1.Mutex());
313
318
  /** Serializes account-switch fetch + subscribe to prevent overlapping races. */
314
319
  _AssetsController_accountRefreshMutex.set(this, new async_mutex_1.Mutex());
@@ -1180,7 +1185,7 @@ class AssetsController extends base_controller_1.BaseController {
1180
1185
  }
1181
1186
  }
1182
1187
  exports.AssetsController = AssetsController;
1183
- _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_accountActivityDataSource = 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_emitStateSizeTrace = function _AssetsController_emitStateSizeTrace() {
1188
+ _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_accountTreeInitialized = new WeakMap(), _AssetsController_controllerMutex = new WeakMap(), _AssetsController_accountRefreshMutex = new WeakMap(), _AssetsController_activeSubscriptions = new WeakMap(), _AssetsController_enabledChains = new WeakMap(), _AssetsController_lastKnownAccountIds = new WeakMap(), _AssetsController_accountActivityDataSource = 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_emitStateSizeTrace = function _AssetsController_emitStateSizeTrace() {
1184
1189
  if (!__classPrivateFieldGet(this, _AssetsController_trace, "f") || __classPrivateFieldGet(this, _AssetsController_stateSizeReported, "f")) {
1185
1190
  return;
1186
1191
  }
@@ -1293,16 +1298,13 @@ _AssetsController_isEnabled = new WeakMap(), _AssetsController_isBasicFunctional
1293
1298
  return reference;
1294
1299
  }, _AssetsController_subscribeToEvents = function _AssetsController_subscribeToEvents() {
1295
1300
  // Subscribe to account group changes (when user switches between account groups like Account 1 -> Account 2)
1296
- this.messenger.subscribe('AccountTreeController:selectedAccountGroupChange', (groupId) => {
1297
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_handleAccountGroupChanged).call(this, groupId).catch(console.error);
1301
+ this.messenger.subscribe('AccountTreeController:selectedAccountGroupChange', (groupId, previousGroupId) => {
1302
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_handleAccountGroupChanged).call(this, groupId, previousGroupId).catch(console.error);
1298
1303
  });
1299
- // Catch the initial tree build. On returning users,
1300
- // `selectedAccountGroupChange` does NOT fire when the persisted group
1301
- // is unchanged, and `accountTreeChange` doesn't fire either (init()
1302
- // rebuilds from persisted accounts without publishing it).
1303
- // The base-controller `:stateChange` event is guaranteed to fire
1304
- // when init() calls this.update(). #start() is idempotent so
1305
- // repeated fires are safe.
1304
+ // Catch post-init tree mutations that change the selected account set
1305
+ // (e.g. a snap account added to the current group) without changing
1306
+ // the selected group id. Intermediate `:stateChange` events during
1307
+ // `AccountTreeController.init()` are ignored until `:initialized`.
1306
1308
  this.messenger.subscribe('AccountTreeController:stateChange', () => {
1307
1309
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_handleAccountTreeStateChange).call(this);
1308
1310
  });
@@ -1354,6 +1356,17 @@ _AssetsController_isEnabled = new WeakMap(), _AssetsController_isBasicFunctional
1354
1356
  this.messenger.subscribe('TransactionController:transactionConfirmed', (transactionMeta) => {
1355
1357
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_onTransactionConfirmed).call(this, transactionMeta);
1356
1358
  });
1359
+ // Start tracking only after the account tree is fully built. Unlock can
1360
+ // happen before `AccountTreeController.init()`, and `:stateChange` fires
1361
+ // for intermediate mutations during that build.
1362
+ this.messenger.subscribe('AccountTreeController:initialized', () => {
1363
+ __classPrivateFieldSet(this, _AssetsController_accountTreeInitialized, true, "f");
1364
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateActive).call(this);
1365
+ });
1366
+ this.messenger.subscribe('AccountTreeController:uninitialized', () => {
1367
+ __classPrivateFieldSet(this, _AssetsController_accountTreeInitialized, false, "f");
1368
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateActive).call(this);
1369
+ });
1357
1370
  }, _AssetsController_onUnapprovedTransactionAdded = function _AssetsController_onUnapprovedTransactionAdded(transactionMeta) {
1358
1371
  const hexChainId = transactionMeta.chainId;
1359
1372
  if (!hexChainId) {
@@ -1397,7 +1410,7 @@ _AssetsController_isEnabled = new WeakMap(), _AssetsController_isBasicFunctional
1397
1410
  log('Failed to refresh assets after transaction confirmed', { error });
1398
1411
  });
1399
1412
  }, _AssetsController_updateActive = function _AssetsController_updateActive() {
1400
- const shouldRun = __classPrivateFieldGet(this, _AssetsController_uiOpen, "f") && __classPrivateFieldGet(this, _AssetsController_keyringUnlocked, "f");
1413
+ const shouldRun = __classPrivateFieldGet(this, _AssetsController_uiOpen, "f") && __classPrivateFieldGet(this, _AssetsController_keyringUnlocked, "f") && __classPrivateFieldGet(this, _AssetsController_accountTreeInitialized, "f");
1401
1414
  if (shouldRun) {
1402
1415
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_start).call(this);
1403
1416
  }
@@ -1405,37 +1418,35 @@ _AssetsController_isEnabled = new WeakMap(), _AssetsController_isBasicFunctional
1405
1418
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_stop).call(this);
1406
1419
  }
1407
1420
  }, _AssetsController_handleAccountTreeStateChange = function _AssetsController_handleAccountTreeStateChange() {
1408
- const shouldRun = __classPrivateFieldGet(this, _AssetsController_uiOpen, "f") && __classPrivateFieldGet(this, _AssetsController_keyringUnlocked, "f");
1421
+ const shouldRun = __classPrivateFieldGet(this, _AssetsController_uiOpen, "f") &&
1422
+ __classPrivateFieldGet(this, _AssetsController_keyringUnlocked, "f") &&
1423
+ __classPrivateFieldGet(this, _AssetsController_accountTreeInitialized, "f") &&
1424
+ __classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").size > 0;
1409
1425
  if (!shouldRun) {
1410
1426
  return;
1411
1427
  }
1412
- if (__classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").size > 0) {
1413
- const accounts = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSelectedAccounts).call(this);
1414
- const currentIds = new Set(accounts.map((a) => a.id));
1415
- const accountsChanged = currentIds.size !== __classPrivateFieldGet(this, _AssetsController_lastKnownAccountIds, "f").size ||
1416
- [...currentIds].some((id) => !__classPrivateFieldGet(this, _AssetsController_lastKnownAccountIds, "f").has(id));
1417
- if (!accountsChanged) {
1418
- return;
1419
- }
1420
- const hasOverlap = [...currentIds].some((id) => __classPrivateFieldGet(this, _AssetsController_lastKnownAccountIds, "f").has(id));
1421
- if (!hasOverlap && __classPrivateFieldGet(this, _AssetsController_lastKnownAccountIds, "f").size > 0) {
1422
- return;
1423
- }
1424
- log('Account tree changed with new accounts, re-subscribing', {
1425
- previousCount: __classPrivateFieldGet(this, _AssetsController_lastKnownAccountIds, "f").size,
1426
- currentCount: currentIds.size,
1427
- });
1428
- const newAccounts = accounts.filter((account) => !__classPrivateFieldGet(this, _AssetsController_lastKnownAccountIds, "f").has(account.id));
1429
- __classPrivateFieldSet(this, _AssetsController_lastKnownAccountIds, currentIds, "f");
1430
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
1431
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureDefaultTrackedAssetsSeeded).call(this);
1432
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_runAccountTreeRefresh).call(this, accounts, newAccounts).catch((error) => {
1433
- log('Failed to refresh assets after tree change', error);
1434
- });
1428
+ const accounts = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSelectedAccounts).call(this);
1429
+ const currentIds = new Set(accounts.map((a) => a.id));
1430
+ const accountsChanged = currentIds.size !== __classPrivateFieldGet(this, _AssetsController_lastKnownAccountIds, "f").size ||
1431
+ [...currentIds].some((id) => !__classPrivateFieldGet(this, _AssetsController_lastKnownAccountIds, "f").has(id));
1432
+ if (!accountsChanged) {
1433
+ return;
1435
1434
  }
1436
- else {
1437
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_start).call(this);
1435
+ const hasOverlap = [...currentIds].some((id) => __classPrivateFieldGet(this, _AssetsController_lastKnownAccountIds, "f").has(id));
1436
+ if (!hasOverlap && __classPrivateFieldGet(this, _AssetsController_lastKnownAccountIds, "f").size > 0) {
1437
+ return;
1438
1438
  }
1439
+ log('Account tree changed with new accounts, re-subscribing', {
1440
+ previousCount: __classPrivateFieldGet(this, _AssetsController_lastKnownAccountIds, "f").size,
1441
+ currentCount: currentIds.size,
1442
+ });
1443
+ const newAccounts = accounts.filter((account) => !__classPrivateFieldGet(this, _AssetsController_lastKnownAccountIds, "f").has(account.id));
1444
+ __classPrivateFieldSet(this, _AssetsController_lastKnownAccountIds, currentIds, "f");
1445
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
1446
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureDefaultTrackedAssetsSeeded).call(this);
1447
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_runAccountTreeRefresh).call(this, accounts, newAccounts).catch((error) => {
1448
+ log('Failed to refresh assets after tree change', error);
1449
+ });
1439
1450
  }, _AssetsController_runAccountTreeRefresh = async function _AssetsController_runAccountTreeRefresh(accounts, newAccounts = []) {
1440
1451
  const releaseLock = await __classPrivateFieldGet(this, _AssetsController_accountRefreshMutex, "f").acquire();
1441
1452
  try {
@@ -1443,7 +1454,7 @@ _AssetsController_isEnabled = new WeakMap(), _AssetsController_isBasicFunctional
1443
1454
  chainIds: [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")],
1444
1455
  forceUpdate: true,
1445
1456
  });
1446
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1457
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this, { skipInitialFetch: true });
1447
1458
  if (newAccounts.length > 0) {
1448
1459
  await this.getAssets(newAccounts, {
1449
1460
  chainIds: [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")],
@@ -1454,7 +1465,7 @@ _AssetsController_isEnabled = new WeakMap(), _AssetsController_isBasicFunctional
1454
1465
  }
1455
1466
  catch (error) {
1456
1467
  log('Failed to fetch assets after tree change', error);
1457
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1468
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this, { skipInitialFetch: true });
1458
1469
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_fetchMissingPricesWithoutCache).call(this, accounts, [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")]);
1459
1470
  }
1460
1471
  finally {
@@ -1477,14 +1488,15 @@ async function _AssetsController_runStartupRefresh(accounts) {
1477
1488
  // and default tracked assets that were never returned by balance APIs.
1478
1489
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
1479
1490
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureDefaultTrackedAssetsSeeded).call(this);
1480
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1491
+ // Balances were just force-fetched — skip AccountsApi's subscribe-time poll.
1492
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this, { skipInitialFetch: true });
1481
1493
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_fetchMissingPricesWithoutCache).call(this, accounts, [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")]);
1482
1494
  }
1483
1495
  catch (error) {
1484
1496
  log('Failed to fetch assets on startup', error);
1485
1497
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
1486
1498
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureDefaultTrackedAssetsSeeded).call(this);
1487
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1499
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this, { skipInitialFetch: true });
1488
1500
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_fetchMissingPricesWithoutCache).call(this, accounts, [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")]);
1489
1501
  }
1490
1502
  finally {
@@ -2107,19 +2119,19 @@ async function _AssetsController_executeMiddlewares(params) {
2107
2119
  }
2108
2120
  }
2109
2121
  __classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").clear();
2110
- }, _AssetsController_subscribeAssets = function _AssetsController_subscribeAssets() {
2122
+ }, _AssetsController_subscribeAssets = function _AssetsController_subscribeAssets(options) {
2111
2123
  const accounts = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSelectedAccounts).call(this);
2112
2124
  const enabledChains = [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")];
2113
2125
  if (accounts.length === 0 || enabledChains.length === 0) {
2114
2126
  return;
2115
2127
  }
2116
2128
  // Subscribe to balance updates (batched by data source)
2117
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssetsBalance).call(this, accounts, enabledChains);
2129
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssetsBalance).call(this, accounts, enabledChains, options);
2118
2130
  // Subscribe to staked balance updates (separate from regular balance chain-claiming)
2119
2131
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeStakedBalance).call(this, accounts, enabledChains);
2120
2132
  // Subscribe to price updates for all assets held by selected accounts
2121
2133
  this.subscribeAssetsPrice(accounts, enabledChains);
2122
- }, _AssetsController_subscribeAssetsBalance = function _AssetsController_subscribeAssetsBalance(accounts, chainIds) {
2134
+ }, _AssetsController_subscribeAssetsBalance = function _AssetsController_subscribeAssetsBalance(accounts, chainIds, options) {
2123
2135
  const chainToAccounts = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_buildChainToAccountsMap).call(this, accounts, new Set(chainIds));
2124
2136
  const remainingChains = new Set(chainToAccounts.keys());
2125
2137
  // When basic functionality is on, use all balance data sources; when off,
@@ -2156,7 +2168,12 @@ async function _AssetsController_executeMiddlewares(params) {
2156
2168
  return true;
2157
2169
  });
2158
2170
  if (accountsForSource.length > 0) {
2159
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeDataSource).call(this, source, accountsForSource, assignedChains);
2171
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeDataSource).call(this, source, accountsForSource, assignedChains, {
2172
+ ...(options?.skipInitialFetch &&
2173
+ source === __classPrivateFieldGet(this, _AssetsController_accountsApiDataSource, "f")
2174
+ ? { skipInitialFetch: true }
2175
+ : {}),
2176
+ });
2160
2177
  }
2161
2178
  }
2162
2179
  // Supplemental RPC subscription for customAssets on chains another data
@@ -2235,6 +2252,7 @@ async function _AssetsController_executeMiddlewares(params) {
2235
2252
  accountCount: accounts.length,
2236
2253
  chainCount: chains.length,
2237
2254
  customAssetsOnly: options.customAssetsOnly === true,
2255
+ skipInitialFetch: options.skipInitialFetch === true,
2238
2256
  });
2239
2257
  const subscribeReq = {
2240
2258
  request: __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_buildDataRequest).call(this, accounts, chains, {
@@ -2249,6 +2267,7 @@ async function _AssetsController_executeMiddlewares(params) {
2249
2267
  isUpdate,
2250
2268
  onAssetsUpdate: (response, request) => this.handleAssetsUpdate(response, sourceId, request),
2251
2269
  getAssetsState: () => this.state,
2270
+ ...(options.skipInitialFetch === true ? { skipInitialFetch: true } : {}),
2252
2271
  };
2253
2272
  source.subscribe(subscribeReq).catch((error) => {
2254
2273
  console.error(`[AssetsController] Failed to subscribe to '${sourceId}':`, error);
@@ -2338,15 +2357,27 @@ async function _AssetsController_executeMiddlewares(params) {
2338
2357
  // ============================================================================
2339
2358
  // EVENT HANDLERS
2340
2359
  // ============================================================================
2341
- async function _AssetsController_handleAccountGroupChanged(groupId) {
2360
+ async function _AssetsController_handleAccountGroupChanged(groupId, previousGroupId = '') {
2342
2361
  // The selected account group can be empty during onboarding or wallet reset.
2343
2362
  if (!groupId) {
2344
2363
  return;
2345
2364
  }
2365
+ // First start is owned by `AccountTreeController:initialized` / `#start`.
2366
+ // ATC also re-publishes `selectedAccountGroupChange` on every `init()`
2367
+ // (including when the group did not change) so late subscribers can catch
2368
+ // up — ignore those until we are already tracking.
2369
+ if (__classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").size === 0) {
2370
+ return;
2371
+ }
2372
+ if (groupId === previousGroupId) {
2373
+ return;
2374
+ }
2346
2375
  const accounts = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSelectedAccounts).call(this);
2347
2376
  log('Account group changed', {
2348
2377
  accountCount: accounts.length,
2349
2378
  accountIds: accounts.map((a) => a.id),
2379
+ groupId,
2380
+ previousGroupId,
2350
2381
  });
2351
2382
  __classPrivateFieldSet(this, _AssetsController_lastKnownAccountIds, new Set(accounts.map((a) => a.id)), "f");
2352
2383
  const releaseLock = await __classPrivateFieldGet(this, _AssetsController_accountRefreshMutex, "f").acquire();
@@ -2360,7 +2391,8 @@ async function _AssetsController_handleAccountGroupChanged(groupId) {
2360
2391
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
2361
2392
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureDefaultTrackedAssetsSeeded).call(this);
2362
2393
  // Subscribe after seed so the price poll sees natives / defaults.
2363
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
2394
+ // Balances were just force-fetched — skip AccountsApi's subscribe-time poll.
2395
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this, { skipInitialFetch: true });
2364
2396
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_fetchMissingPricesWithoutCache).call(this, accounts, [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")]);
2365
2397
  }
2366
2398
  finally {