@metamask-previews/assets-controller 9.1.0-preview-50e82f088 → 9.1.0-preview-773e4e83d

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
@@ -11,6 +11,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
11
11
 
12
12
  - Bump `@metamask/transaction-controller` from `^68.1.1` to `^68.2.0` ([#9253](https://github.com/MetaMask/core/pull/9253))
13
13
 
14
+ ### Fixed
15
+
16
+ - Fix stale token balances after transactions when switching accounts or when websocket subscriptions reconnect; `AssetsController` now fetches before re-subscribing on account switch, serializes overlapping refresh work, treats `getAssets({ forceUpdate: true })` as authoritative over recent websocket freshness guards, and prevents passive polling from overwriting websocket balances for 120 seconds ([#9265](https://github.com/MetaMask/core/pull/9265))
17
+ - `AccountsApiDataSource` bypasses the TanStack Query balance cache when `forceUpdate` is true so forced refreshes return up-to-date balances instead of 60-second cached values ([#9265](https://github.com/MetaMask/core/pull/9265))
18
+ - `BackendWebsocketDataSource` re-subscribes when subscribed accounts change (case-insensitive EVM address matching), serializes subscribe/unsubscribe to prevent races on account switch, and registers optional channel callbacks for more reliable notification delivery ([#9265](https://github.com/MetaMask/core/pull/9265))
19
+
14
20
  ## [9.1.0]
15
21
 
16
22
  ### Added
@@ -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_wsBalanceFreshUntil, _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_runAccountTreeRefresh, _AssetsController_registerActionHandlers, _AssetsController_handleActiveChainsUpdate, _AssetsController_executeMiddlewares, _AssetsController_getNativeAssetMap, _AssetsController_isNativeAsset, _AssetsController_getAssetType, _AssetsController_resolveNativeAssetIds, _AssetsController_getNativeAssetIdsForEnabledChains, _AssetsController_getNativeAssetIdsForAccount, _AssetsController_ensureNativeBalancesDefaultZero, _AssetsController_ensureDefaultTrackedAssetsSeeded, _AssetsController_filterBalancesRespectingWsFreshness, _AssetsController_markWsBalancesFresh, _AssetsController_clearWsBalanceFreshness, _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;
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");
@@ -62,6 +62,15 @@ const MESSENGER_EXPOSED_METHODS = [
62
62
  ];
63
63
  /** Default polling interval hint for data sources (30 seconds) */
64
64
  const DEFAULT_POLLING_INTERVAL_MS = 30000;
65
+ /** Sources whose passive polling must not overwrite recent websocket balances. */
66
+ const POLLING_BALANCE_SOURCES = new Set([
67
+ 'AccountsApiDataSource',
68
+ 'RpcDataSource',
69
+ 'SnapDataSource',
70
+ 'StakedBalanceDataSource',
71
+ ]);
72
+ /** How long websocket balance updates block stale polling overwrites. */
73
+ const WS_BALANCE_FRESHNESS_MS = 120000;
65
74
  // ============================================================================
66
75
  // TRACE NAMES — used in Sentry spans (search these strings in Discover)
67
76
  // ============================================================================
@@ -192,6 +201,9 @@ function normalizeResponse(response) {
192
201
  if (response.updateMode) {
193
202
  normalized.updateMode = response.updateMode;
194
203
  }
204
+ if (response.sourceId) {
205
+ normalized.sourceId = response.sourceId;
206
+ }
195
207
  return normalized;
196
208
  }
197
209
  // ============================================================================
@@ -255,6 +267,8 @@ class AssetsController extends base_controller_1.BaseController {
255
267
  /** Whether the keyring is unlocked. Combined with #uiOpen for #updateActive. */
256
268
  _AssetsController_keyringUnlocked.set(this, false);
257
269
  _AssetsController_controllerMutex.set(this, new async_mutex_1.Mutex());
270
+ /** Serializes account-switch fetch + subscribe to prevent overlapping races. */
271
+ _AssetsController_accountRefreshMutex.set(this, new async_mutex_1.Mutex());
258
272
  /**
259
273
  * Active balance subscriptions keyed by account ID.
260
274
  * Each account has one logical subscription that may span multiple data sources.
@@ -270,6 +284,11 @@ class AssetsController extends base_controller_1.BaseController {
270
284
  * re-subscriptions when the account set hasn't actually changed.
271
285
  */
272
286
  _AssetsController_lastKnownAccountIds.set(this, new Set());
287
+ /**
288
+ * Per `accountId:assetId`, timestamp until which websocket balance updates
289
+ * should not be overwritten by polling/API fetches.
290
+ */
291
+ _AssetsController_wsBalanceFreshUntil.set(this, new Map());
273
292
  _AssetsController_backendWebsocketDataSource.set(this, void 0);
274
293
  _AssetsController_accountsApiDataSource.set(this, void 0);
275
294
  _AssetsController_snapDataSource.set(this, void 0);
@@ -443,7 +462,11 @@ class AssetsController extends base_controller_1.BaseController {
443
462
  // The fast pipeline only contains a subset of data sources (AccountsApi +
444
463
  // StakedBalance), so it must always merge to avoid wiping Snap/RPC
445
464
  // balances that the background pipeline hasn't yet replaced.
446
- await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, { ...response, updateMode: 'merge' });
465
+ await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, {
466
+ ...response,
467
+ updateMode: 'merge',
468
+ sourceId: 'getAssets:forceUpdate',
469
+ });
447
470
  // Background pipeline: snap and RPC run in parallel after the fast path
448
471
  // commits to state. Their balances are merged together before detection.
449
472
  // Token + price enrichment matches the pre-split behavior: only when basic
@@ -463,7 +486,11 @@ class AssetsController extends base_controller_1.BaseController {
463
486
  ]
464
487
  : []),
465
488
  ], request)
466
- .then(({ response: slowResponse }) => __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, { ...slowResponse, updateMode: 'merge' }))
489
+ .then(({ response: slowResponse }) => __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, {
490
+ ...slowResponse,
491
+ updateMode: 'merge',
492
+ sourceId: 'getAssets:forceUpdate',
493
+ }))
467
494
  .catch((error) => log('Background pipeline failed', { error }));
468
495
  const durationMs = performance.now() - startTime;
469
496
  // Emit trace for every full fetch (Assets Health dashboard)
@@ -870,7 +897,11 @@ class AssetsController extends base_controller_1.BaseController {
870
897
  ]));
871
898
  }
872
899
  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);
900
+ await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, { ...enrichedResponse, sourceId });
901
+ if (sourceId === 'BackendWebsocketDataSource' &&
902
+ enrichedResponse.assetsBalance) {
903
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_markWsBalancesFresh).call(this, enrichedResponse.assetsBalance);
904
+ }
874
905
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_emitTrace).call(this, TRACE_UPDATE_PIPELINE, {
875
906
  source: sourceId,
876
907
  duration_ms: performance.now() - updateStart,
@@ -918,7 +949,7 @@ class AssetsController extends base_controller_1.BaseController {
918
949
  }
919
950
  }
920
951
  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 = {
952
+ _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_wsBalanceFreshUntil = 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
953
  controller: 'AssetsController',
923
954
  }) {
924
955
  if (!__classPrivateFieldGet(this, _AssetsController_trace, "f")) {
@@ -1125,24 +1156,40 @@ _AssetsController_isEnabled = new WeakMap(), _AssetsController_isBasicFunctional
1125
1156
  if (!accountsChanged) {
1126
1157
  return;
1127
1158
  }
1159
+ const hasOverlap = [...currentIds].some((id) => __classPrivateFieldGet(this, _AssetsController_lastKnownAccountIds, "f").has(id));
1160
+ if (!hasOverlap && __classPrivateFieldGet(this, _AssetsController_lastKnownAccountIds, "f").size > 0) {
1161
+ return;
1162
+ }
1128
1163
  log('Account tree changed with new accounts, re-subscribing', {
1129
1164
  previousCount: __classPrivateFieldGet(this, _AssetsController_lastKnownAccountIds, "f").size,
1130
1165
  currentCount: currentIds.size,
1131
1166
  });
1132
1167
  __classPrivateFieldSet(this, _AssetsController_lastKnownAccountIds, currentIds, "f");
1133
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1134
1168
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
1135
1169
  __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);
1170
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_runAccountTreeRefresh).call(this, accounts).catch((error) => {
1171
+ log('Failed to refresh assets after tree change', error);
1141
1172
  });
1142
1173
  }
1143
1174
  else {
1144
1175
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_start).call(this);
1145
1176
  }
1177
+ }, _AssetsController_runAccountTreeRefresh = async function _AssetsController_runAccountTreeRefresh(accounts) {
1178
+ const releaseLock = await __classPrivateFieldGet(this, _AssetsController_accountRefreshMutex, "f").acquire();
1179
+ try {
1180
+ await this.getAssets(accounts, {
1181
+ chainIds: [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")],
1182
+ forceUpdate: true,
1183
+ });
1184
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1185
+ }
1186
+ catch (error) {
1187
+ log('Failed to fetch assets after tree change', error);
1188
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1189
+ }
1190
+ finally {
1191
+ releaseLock();
1192
+ }
1146
1193
  }, _AssetsController_registerActionHandlers = function _AssetsController_registerActionHandlers() {
1147
1194
  this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
1148
1195
  }, _AssetsController_handleActiveChainsUpdate = function _AssetsController_handleActiveChainsUpdate(dataSourceId, activeChains, previousChains) {
@@ -1364,9 +1411,37 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1364
1411
  }
1365
1412
  }
1366
1413
  });
1414
+ }, _AssetsController_filterBalancesRespectingWsFreshness = function _AssetsController_filterBalancesRespectingWsFreshness(accountId, accountBalances, sourceId) {
1415
+ if (!sourceId || !POLLING_BALANCE_SOURCES.has(sourceId)) {
1416
+ return accountBalances;
1417
+ }
1418
+ const now = Date.now();
1419
+ const filtered = {};
1420
+ for (const [assetId, balance] of Object.entries(accountBalances)) {
1421
+ const freshUntil = __classPrivateFieldGet(this, _AssetsController_wsBalanceFreshUntil, "f").get(`${accountId}:${assetId}`);
1422
+ if (freshUntil !== undefined && now < freshUntil) {
1423
+ continue;
1424
+ }
1425
+ filtered[assetId] = balance;
1426
+ }
1427
+ return filtered;
1428
+ }, _AssetsController_markWsBalancesFresh = function _AssetsController_markWsBalancesFresh(assetsBalance) {
1429
+ const freshUntil = Date.now() + WS_BALANCE_FRESHNESS_MS;
1430
+ for (const [accountId, balances] of Object.entries(assetsBalance)) {
1431
+ for (const assetId of Object.keys(balances)) {
1432
+ __classPrivateFieldGet(this, _AssetsController_wsBalanceFreshUntil, "f").set(`${accountId}:${assetId}`, freshUntil);
1433
+ }
1434
+ }
1435
+ }, _AssetsController_clearWsBalanceFreshness = function _AssetsController_clearWsBalanceFreshness(assetsBalance) {
1436
+ for (const [accountId, balances] of Object.entries(assetsBalance)) {
1437
+ for (const assetId of Object.keys(balances)) {
1438
+ __classPrivateFieldGet(this, _AssetsController_wsBalanceFreshUntil, "f").delete(`${accountId}:${assetId}`);
1439
+ }
1440
+ }
1367
1441
  }, _AssetsController_updateState = async function _AssetsController_updateState(response) {
1368
1442
  const normalizedResponse = normalizeResponse(response);
1369
1443
  const mode = normalizedResponse.updateMode ?? 'merge';
1444
+ const assetsBalanceToApply = normalizedResponse.assetsBalance;
1370
1445
  const releaseLock = await __classPrivateFieldGet(this, _AssetsController_controllerMutex, "f").acquire();
1371
1446
  try {
1372
1447
  const previousState = this.state;
@@ -1427,8 +1502,9 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1427
1502
  }
1428
1503
  }
1429
1504
  }
1430
- if (normalizedResponse.assetsBalance) {
1431
- for (const [accountId, accountBalances] of Object.entries(normalizedResponse.assetsBalance)) {
1505
+ if (assetsBalanceToApply) {
1506
+ for (const [accountId, accountBalances] of Object.entries(assetsBalanceToApply)) {
1507
+ const filteredAccountBalances = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_filterBalancesRespectingWsFreshness).call(this, accountId, accountBalances, normalizedResponse.sourceId);
1432
1508
  const previousBalances = previousState.assetsBalance[accountId] ?? {};
1433
1509
  const customAssetIds = state.customAssets[accountId] ?? [];
1434
1510
  // Full: response is authoritative for the chains it covered;
@@ -1437,10 +1513,10 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1437
1513
  // are never inadvertently reset to zero.
1438
1514
  // Merge: response overlays previous balances.
1439
1515
  const effective = mode === 'merge'
1440
- ? { ...previousBalances, ...accountBalances }
1516
+ ? { ...previousBalances, ...filteredAccountBalances }
1441
1517
  : (() => {
1442
1518
  // Determine which chain namespaces this response covers.
1443
- const coveredChains = new Set(Object.keys(accountBalances).map((assetId) => assetId.split('/')[0]));
1519
+ const coveredChains = new Set(Object.keys(filteredAccountBalances).map((assetId) => assetId.split('/')[0]));
1444
1520
  // Start from previous balances, dropping only entries for
1445
1521
  // chains this response is authoritative over.
1446
1522
  const next = {};
@@ -1450,7 +1526,7 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1450
1526
  }
1451
1527
  }
1452
1528
  // Apply the response (authoritative for covered chains).
1453
- Object.assign(next, accountBalances);
1529
+ Object.assign(next, filteredAccountBalances);
1454
1530
  // Preserve custom assets that the response omitted.
1455
1531
  for (const customId of customAssetIds) {
1456
1532
  if (!(customId in next)) {
@@ -1562,6 +1638,12 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1562
1638
  });
1563
1639
  }
1564
1640
  }
1641
+ // Authoritative fetch on account switch — drop WS freshness locks so API
1642
+ // balances (e.g. receiver +1 USDC) replace stale values from a prior send.
1643
+ if (normalizedResponse.sourceId === 'getAssets:forceUpdate' &&
1644
+ assetsBalanceToApply) {
1645
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_clearWsBalanceFreshness).call(this, assetsBalanceToApply);
1646
+ }
1565
1647
  }
1566
1648
  finally {
1567
1649
  releaseLock();
@@ -1932,16 +2014,22 @@ async function _AssetsController_handleAccountGroupChanged() {
1932
2014
  accountIds: accounts.map((a) => a.id),
1933
2015
  });
1934
2016
  __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
- });
2017
+ const releaseLock = await __classPrivateFieldGet(this, _AssetsController_accountRefreshMutex, "f").acquire();
2018
+ try {
2019
+ if (accounts.length > 0) {
2020
+ await this.getAssets(accounts, {
2021
+ chainIds: [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")],
2022
+ forceUpdate: true,
2023
+ });
2024
+ }
2025
+ // Subscribe after fetch so WS notifications can recover state
2026
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
2027
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
2028
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureDefaultTrackedAssetsSeeded).call(this);
2029
+ }
2030
+ finally {
2031
+ releaseLock();
1942
2032
  }
1943
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
1944
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureDefaultTrackedAssetsSeeded).call(this);
1945
2033
  }, _AssetsController_handleEnabledNetworksChanged = async function _AssetsController_handleEnabledNetworksChanged(enabledNetworkMap) {
1946
2034
  const previousChains = __classPrivateFieldGet(this, _AssetsController_enabledChains, "f");
1947
2035
  __classPrivateFieldSet(this, _AssetsController_enabledChains, __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_extractEnabledChains).call(this, enabledNetworkMap), "f");