@metamask-previews/assets-controller 9.1.0-preview-bd7b53f49 → 9.1.0-preview-e0ee14da7

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 (82) hide show
  1. package/CHANGELOG.md +2 -8
  2. package/dist/AssetsController.cjs +136 -309
  3. package/dist/AssetsController.cjs.map +1 -1
  4. package/dist/AssetsController.d.cts +8 -11
  5. package/dist/AssetsController.d.cts.map +1 -1
  6. package/dist/AssetsController.d.mts +8 -11
  7. package/dist/AssetsController.d.mts.map +1 -1
  8. package/dist/AssetsController.mjs +136 -309
  9. package/dist/AssetsController.mjs.map +1 -1
  10. package/dist/data-sources/AccountsApiDataSource.cjs +0 -10
  11. package/dist/data-sources/AccountsApiDataSource.cjs.map +1 -1
  12. package/dist/data-sources/AccountsApiDataSource.d.cts +0 -8
  13. package/dist/data-sources/AccountsApiDataSource.d.cts.map +1 -1
  14. package/dist/data-sources/AccountsApiDataSource.d.mts +0 -8
  15. package/dist/data-sources/AccountsApiDataSource.d.mts.map +1 -1
  16. package/dist/data-sources/AccountsApiDataSource.mjs +0 -10
  17. package/dist/data-sources/AccountsApiDataSource.mjs.map +1 -1
  18. package/dist/data-sources/BackendWebsocketDataSource.cjs +60 -36
  19. package/dist/data-sources/BackendWebsocketDataSource.cjs.map +1 -1
  20. package/dist/data-sources/BackendWebsocketDataSource.d.cts +0 -8
  21. package/dist/data-sources/BackendWebsocketDataSource.d.cts.map +1 -1
  22. package/dist/data-sources/BackendWebsocketDataSource.d.mts +0 -8
  23. package/dist/data-sources/BackendWebsocketDataSource.d.mts.map +1 -1
  24. package/dist/data-sources/BackendWebsocketDataSource.mjs +57 -36
  25. package/dist/data-sources/BackendWebsocketDataSource.mjs.map +1 -1
  26. package/dist/data-sources/PriceDataSource.cjs +9 -18
  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 +10 -19
  31. package/dist/data-sources/PriceDataSource.mjs.map +1 -1
  32. package/dist/data-sources/RpcDataSource.cjs +9 -29
  33. package/dist/data-sources/RpcDataSource.cjs.map +1 -1
  34. package/dist/data-sources/RpcDataSource.d.cts +0 -5
  35. package/dist/data-sources/RpcDataSource.d.cts.map +1 -1
  36. package/dist/data-sources/RpcDataSource.d.mts +0 -5
  37. package/dist/data-sources/RpcDataSource.d.mts.map +1 -1
  38. package/dist/data-sources/RpcDataSource.mjs +9 -29
  39. package/dist/data-sources/RpcDataSource.mjs.map +1 -1
  40. package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.cts +2 -2
  41. package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.mts +2 -2
  42. package/dist/data-sources/evm-rpc-services/services/StakedBalanceFetcher.d.cts +2 -2
  43. package/dist/data-sources/evm-rpc-services/services/StakedBalanceFetcher.d.mts +2 -2
  44. package/dist/data-sources/evm-rpc-services/services/TokenDetector.d.cts +2 -2
  45. package/dist/data-sources/evm-rpc-services/services/TokenDetector.d.mts +2 -2
  46. package/dist/middlewares/DetectionMiddleware.cjs +1 -22
  47. package/dist/middlewares/DetectionMiddleware.cjs.map +1 -1
  48. package/dist/middlewares/DetectionMiddleware.d.cts +0 -3
  49. package/dist/middlewares/DetectionMiddleware.d.cts.map +1 -1
  50. package/dist/middlewares/DetectionMiddleware.d.mts +0 -3
  51. package/dist/middlewares/DetectionMiddleware.d.mts.map +1 -1
  52. package/dist/middlewares/DetectionMiddleware.mjs +1 -22
  53. package/dist/middlewares/DetectionMiddleware.mjs.map +1 -1
  54. package/dist/middlewares/ParallelMiddleware.cjs +0 -7
  55. package/dist/middlewares/ParallelMiddleware.cjs.map +1 -1
  56. package/dist/middlewares/ParallelMiddleware.d.cts.map +1 -1
  57. package/dist/middlewares/ParallelMiddleware.d.mts.map +1 -1
  58. package/dist/middlewares/ParallelMiddleware.mjs +0 -7
  59. package/dist/middlewares/ParallelMiddleware.mjs.map +1 -1
  60. package/dist/types.cjs.map +1 -1
  61. package/dist/types.d.cts +9 -18
  62. package/dist/types.d.cts.map +1 -1
  63. package/dist/types.d.mts +9 -18
  64. package/dist/types.d.mts.map +1 -1
  65. package/dist/types.mjs.map +1 -1
  66. package/dist/utils/dedupingBatchFetcher.cjs +0 -11
  67. package/dist/utils/dedupingBatchFetcher.cjs.map +1 -1
  68. package/dist/utils/dedupingBatchFetcher.d.cts +0 -7
  69. package/dist/utils/dedupingBatchFetcher.d.cts.map +1 -1
  70. package/dist/utils/dedupingBatchFetcher.d.mts +0 -7
  71. package/dist/utils/dedupingBatchFetcher.d.mts.map +1 -1
  72. package/dist/utils/dedupingBatchFetcher.mjs +0 -11
  73. package/dist/utils/dedupingBatchFetcher.mjs.map +1 -1
  74. package/package.json +1 -1
  75. package/dist/utils/processAccountActivityBalanceUpdates.cjs +0 -54
  76. package/dist/utils/processAccountActivityBalanceUpdates.cjs.map +0 -1
  77. package/dist/utils/processAccountActivityBalanceUpdates.d.cts +0 -13
  78. package/dist/utils/processAccountActivityBalanceUpdates.d.cts.map +0 -1
  79. package/dist/utils/processAccountActivityBalanceUpdates.d.mts +0 -13
  80. package/dist/utils/processAccountActivityBalanceUpdates.d.mts.map +0 -1
  81. package/dist/utils/processAccountActivityBalanceUpdates.mjs +0 -47
  82. package/dist/utils/processAccountActivityBalanceUpdates.mjs.map +0 -1
@@ -9,7 +9,7 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
9
9
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
10
10
  return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
11
11
  };
12
- var _AssetsController_instances, _AssetsController_isEnabled, _AssetsController_isBasicFunctionality, _AssetsController_defaultUpdateInterval, _AssetsController_trace, _AssetsController_captureException, _AssetsController_firstInitFetchReported, _AssetsController_stateSizeReported, _AssetsController_emitTrace, _AssetsController_emitStateSizeTrace, _AssetsController_uiOpen, _AssetsController_keyringUnlocked, _AssetsController_controllerMutex, _AssetsController_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;
12
+ var _AssetsController_instances, _AssetsController_isEnabled, _AssetsController_isBasicFunctionality, _AssetsController_defaultUpdateInterval, _AssetsController_trace, _AssetsController_captureException, _AssetsController_firstInitFetchReported, _AssetsController_stateSizeReported, _AssetsController_emitTrace, _AssetsController_emitStateSizeTrace, _AssetsController_uiOpen, _AssetsController_keyringUnlocked, _AssetsController_controllerMutex, _AssetsController_accountRefreshMutex, _AssetsController_activeSubscriptions, _AssetsController_enabledChains, _AssetsController_lastKnownAccountIds, _AssetsController_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;
13
13
  import { BaseController } from "@metamask/base-controller";
14
14
  import { clientControllerSelectors } from "@metamask/client-controller";
15
15
  import { isCaipChainId, isStrictHexString, KnownCaipNamespace, parseCaipAssetType, parseCaipChainId } from "@metamask/utils";
@@ -35,7 +35,6 @@ import { RpcFallbackMiddleware } from "./middlewares/RpcFallbackMiddleware.mjs";
35
35
  import { normalizeAmountString, normalizeAssetId, formatExchangeRatesForBridge, formatStateForTransactionPay, buildNativeAssetsFromConstant, buildNativeAssetsFromApi } from "./utils/index.mjs";
36
36
  import { ZERO_ADDRESS } from "./utils/constants.mjs";
37
37
  import { pickRpcCustomAssetsSupplement } from "./utils/customAssetsRpcSupplement.mjs";
38
- import { processAccountActivityBalanceUpdates } from "./utils/processAccountActivityBalanceUpdates.mjs";
39
38
  const NATIVE_ASSETS_QUERY_KEY = ['nativeAssets'];
40
39
  // ============================================================================
41
40
  // CONTROLLER CONSTANTS
@@ -58,6 +57,15 @@ const MESSENGER_EXPOSED_METHODS = [
58
57
  ];
59
58
  /** Default polling interval hint for data sources (30 seconds) */
60
59
  const DEFAULT_POLLING_INTERVAL_MS = 30000;
60
+ /** Sources whose passive polling must not overwrite recent websocket balances. */
61
+ const POLLING_BALANCE_SOURCES = new Set([
62
+ 'AccountsApiDataSource',
63
+ 'RpcDataSource',
64
+ 'SnapDataSource',
65
+ 'StakedBalanceDataSource',
66
+ ]);
67
+ /** How long websocket balance updates block stale polling overwrites. */
68
+ const WS_BALANCE_FRESHNESS_MS = 120000;
61
69
  // ============================================================================
62
70
  // TRACE NAMES — used in Sentry spans (search these strings in Discover)
63
71
  // ============================================================================
@@ -187,41 +195,11 @@ function normalizeResponse(response) {
187
195
  if (response.updateMode) {
188
196
  normalized.updateMode = response.updateMode;
189
197
  }
190
- if (response.replaceCoveredChainBalances) {
191
- normalized.replaceCoveredChainBalances = true;
198
+ if (response.sourceId) {
199
+ normalized.sourceId = response.sourceId;
192
200
  }
193
201
  return normalized;
194
202
  }
195
- /**
196
- * Merge account balances from a data-source response into prior state.
197
- *
198
- * @param previousBalances - Balances already in state for this account.
199
- * @param accountBalances - Balances from the incoming response.
200
- * @param customAssetIds - Custom assets to preserve when replacing covered chains.
201
- * @param replaceCoveredChains - When true, drop prior balances on chains present
202
- * in the response before applying it (authoritative chain slice).
203
- * @returns The merged balance map for the account.
204
- */
205
- function mergeAccountBalances(previousBalances, accountBalances, customAssetIds, replaceCoveredChains) {
206
- if (!replaceCoveredChains) {
207
- return { ...previousBalances, ...accountBalances };
208
- }
209
- const coveredChains = new Set(Object.keys(accountBalances).map((assetId) => assetId.split('/')[0]));
210
- const next = {};
211
- for (const [assetId, balance] of Object.entries(previousBalances)) {
212
- if (!coveredChains.has(assetId.split('/')[0])) {
213
- next[assetId] = balance;
214
- }
215
- }
216
- Object.assign(next, accountBalances);
217
- for (const customId of customAssetIds) {
218
- if (!Object.prototype.hasOwnProperty.call(next, customId)) {
219
- const prev = previousBalances[customId];
220
- next[customId] = prev ?? { amount: '0' };
221
- }
222
- }
223
- return next;
224
- }
225
203
  // ============================================================================
226
204
  // CONTROLLER IMPLEMENTATION
227
205
  // ============================================================================
@@ -300,6 +278,11 @@ export class AssetsController extends BaseController {
300
278
  * re-subscriptions when the account set hasn't actually changed.
301
279
  */
302
280
  _AssetsController_lastKnownAccountIds.set(this, new Set());
281
+ /**
282
+ * Per `accountId:assetId`, timestamp until which websocket balance updates
283
+ * should not be overwritten by polling/API fetches.
284
+ */
285
+ _AssetsController_wsBalanceFreshUntil.set(this, new Map());
303
286
  _AssetsController_backendWebsocketDataSource.set(this, void 0);
304
287
  _AssetsController_accountsApiDataSource.set(this, void 0);
305
288
  _AssetsController_snapDataSource.set(this, void 0);
@@ -448,9 +431,9 @@ export class AssetsController extends BaseController {
448
431
  // creation, RPC is slow on many chains). Results are committed to state
449
432
  // immediately so the UI can display balances without waiting for them.
450
433
  //
451
- // Fast/slow pipelines use merge so partial API snapshots cannot wipe
452
- // tokens missing from the response (e.g. USDC when only native balance
453
- // is returned). Balances present in the response are still refreshed.
434
+ // Both the fast and background pipelines use 'merge' mode because neither
435
+ // alone represents the full set of data sources. Using 'full' in either
436
+ // would wipe balances from the sources handled by the other pipeline.
454
437
  const fastSources = __classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)
455
438
  ? [
456
439
  createParallelBalanceMiddleware([
@@ -470,36 +453,39 @@ export class AssetsController extends BaseController {
470
453
  ]
471
454
  : [__classPrivateFieldGet(this, _AssetsController_stakedBalanceDataSource, "f"), __classPrivateFieldGet(this, _AssetsController_detectionMiddleware, "f")];
472
455
  const { response, durationByDataSource } = await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, fastSources, request);
456
+ // The fast pipeline only contains a subset of data sources (AccountsApi +
457
+ // StakedBalance), so it must always merge to avoid wiping Snap/RPC
458
+ // balances that the background pipeline hasn't yet replaced.
473
459
  await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, {
474
460
  ...response,
475
461
  updateMode: 'merge',
476
- replaceCoveredChainBalances: true,
462
+ sourceId: 'getAssets:forceUpdate',
477
463
  });
478
464
  // Background pipeline: snap and RPC run in parallel after the fast path
479
465
  // commits to state. Their balances are merged together before detection.
480
466
  // Token + price enrichment matches the pre-split behavior: only when basic
481
467
  // functionality is on (RPC-only mode must not call token/price APIs).
482
- const slowPipelineChainIds = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSlowPipelineChainIds).call(this, chainIds, response);
483
- if (slowPipelineChainIds.length > 0) {
484
- const slowSources = __classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)
485
- ? [__classPrivateFieldGet(this, _AssetsController_snapDataSource, "f"), __classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f")]
486
- : [__classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f")];
487
- const slowRequest = { ...request, chainIds: slowPipelineChainIds };
488
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, [
489
- createParallelBalanceMiddleware(slowSources),
490
- __classPrivateFieldGet(this, _AssetsController_detectionMiddleware, "f"),
491
- ...(__classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)
492
- ? [
493
- createParallelMiddleware([
494
- __classPrivateFieldGet(this, _AssetsController_tokenDataSource, "f"),
495
- __classPrivateFieldGet(this, _AssetsController_priceDataSource, "f"),
496
- ]),
497
- ]
498
- : []),
499
- ], slowRequest)
500
- .then(({ response: slowResponse }) => __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, { ...slowResponse, updateMode: 'merge' }))
501
- .catch((error) => log('Background pipeline failed', { error }));
502
- }
468
+ const slowSources = __classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)
469
+ ? [__classPrivateFieldGet(this, _AssetsController_snapDataSource, "f"), __classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f")]
470
+ : [__classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f")];
471
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, [
472
+ createParallelBalanceMiddleware(slowSources),
473
+ __classPrivateFieldGet(this, _AssetsController_detectionMiddleware, "f"),
474
+ ...(__classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)
475
+ ? [
476
+ createParallelMiddleware([
477
+ __classPrivateFieldGet(this, _AssetsController_tokenDataSource, "f"),
478
+ __classPrivateFieldGet(this, _AssetsController_priceDataSource, "f"),
479
+ ]),
480
+ ]
481
+ : []),
482
+ ], request)
483
+ .then(({ response: slowResponse }) => __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, {
484
+ ...slowResponse,
485
+ updateMode: 'merge',
486
+ sourceId: 'getAssets:forceUpdate',
487
+ }))
488
+ .catch((error) => log('Background pipeline failed', { error }));
503
489
  const durationMs = performance.now() - startTime;
504
490
  // Emit trace for every full fetch (Assets Health dashboard)
505
491
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_emitTrace).call(this, TRACE_FULL_FETCH, {
@@ -891,8 +877,7 @@ export class AssetsController extends BaseController {
891
877
  // RPC already fetches custom assets on purpose, and Snap handles non-EVM
892
878
  // chains the rule does not apply to, so skip the middleware for those.
893
879
  const shouldGraduateCustomAssets = sourceId === 'AccountsApiDataSource' ||
894
- sourceId === 'BackendWebsocketDataSource' ||
895
- sourceId === 'AccountActivityService';
880
+ sourceId === 'BackendWebsocketDataSource';
896
881
  const enrichmentSources = [
897
882
  ...(shouldGraduateCustomAssets
898
883
  ? [__classPrivateFieldGet(this, _AssetsController_customAssetGraduationMiddleware, "f")]
@@ -906,10 +891,11 @@ export class AssetsController extends BaseController {
906
891
  ]));
907
892
  }
908
893
  const { response: enrichedResponse } = await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, enrichmentSources, pipelineRequest, response);
909
- await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, {
910
- ...enrichedResponse,
911
- replaceCoveredChainBalances: response.replaceCoveredChainBalances,
912
- });
894
+ await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, { ...enrichedResponse, sourceId });
895
+ if (sourceId === 'BackendWebsocketDataSource' &&
896
+ enrichedResponse.assetsBalance) {
897
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_markWsBalancesFresh).call(this, enrichedResponse.assetsBalance);
898
+ }
913
899
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_emitTrace).call(this, TRACE_UPDATE_PIPELINE, {
914
900
  source: sourceId,
915
901
  duration_ms: performance.now() - updateStart,
@@ -956,7 +942,7 @@ export class AssetsController extends BaseController {
956
942
  this.messenger.unregisterActionHandler('AssetsController:setSelectedCurrency');
957
943
  }
958
944
  }
959
- _AssetsController_isEnabled = new WeakMap(), _AssetsController_isBasicFunctionality = new WeakMap(), _AssetsController_defaultUpdateInterval = new WeakMap(), _AssetsController_trace = new WeakMap(), _AssetsController_captureException = new WeakMap(), _AssetsController_firstInitFetchReported = new WeakMap(), _AssetsController_stateSizeReported = new WeakMap(), _AssetsController_uiOpen = new WeakMap(), _AssetsController_keyringUnlocked = new WeakMap(), _AssetsController_controllerMutex = new WeakMap(), _AssetsController_accountRefreshMutex = new WeakMap(), _AssetsController_activeSubscriptions = new WeakMap(), _AssetsController_enabledChains = new WeakMap(), _AssetsController_lastKnownAccountIds = new WeakMap(), _AssetsController_backendWebsocketDataSource = new WeakMap(), _AssetsController_accountsApiDataSource = new WeakMap(), _AssetsController_snapDataSource = new WeakMap(), _AssetsController_rpcDataSource = new WeakMap(), _AssetsController_stakedBalanceDataSource = new WeakMap(), _AssetsController_priceDataSource = new WeakMap(), _AssetsController_detectionMiddleware = new WeakMap(), _AssetsController_customAssetGraduationMiddleware = new WeakMap(), _AssetsController_rpcFallbackMiddleware = new WeakMap(), _AssetsController_tokenDataSource = new WeakMap(), _AssetsController_unsubscribeBasicFunctionality = new WeakMap(), _AssetsController_queryApiClient = new WeakMap(), _AssetsController_onActiveChainsUpdated = new WeakMap(), _AssetsController_instances = new WeakSet(), _AssetsController_emitTrace = function _AssetsController_emitTrace(name, data, tags = {
945
+ _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 = {
960
946
  controller: 'AssetsController',
961
947
  }) {
962
948
  if (!__classPrivateFieldGet(this, _AssetsController_trace, "f")) {
@@ -1082,11 +1068,11 @@ _AssetsController_isEnabled = new WeakMap(), _AssetsController_isBasicFunctional
1082
1068
  // The base-controller `:stateChange` event is guaranteed to fire
1083
1069
  // when init() calls this.update(). #start() is idempotent so
1084
1070
  // repeated fires are safe.
1085
- this.messenger.subscribe('AccountTreeController:stateChanged', () => {
1071
+ this.messenger.subscribe('AccountTreeController:stateChange', () => {
1086
1072
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_handleAccountTreeStateChange).call(this);
1087
1073
  });
1088
1074
  // Subscribe to network enablement changes (only enabledNetworkMap)
1089
- this.messenger.subscribe('NetworkEnablementController:stateChanged', ({ enabledNetworkMap }) => {
1075
+ this.messenger.subscribe('NetworkEnablementController:stateChange', ({ enabledNetworkMap }) => {
1090
1076
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_handleEnabledNetworksChanged).call(this, enabledNetworkMap).catch(console.error);
1091
1077
  });
1092
1078
  // When a network is added or removed from NetworkController, refresh
@@ -1097,20 +1083,13 @@ _AssetsController_isEnabled = new WeakMap(), _AssetsController_isBasicFunctional
1097
1083
  // enabled in NetworkEnablementController.
1098
1084
  this.messenger.subscribe('NetworkController:networkAdded', (networkConfiguration) => {
1099
1085
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_handleNetworkAdded).call(this, networkConfiguration.chainId);
1100
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_refreshAssetsAfterNetworkAdded).call(this, networkConfiguration.chainId).catch((error) => {
1101
- log('Failed to refresh assets after network added', { error });
1102
- });
1086
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_refreshAssetsAfterNetworkChange).call(this);
1103
1087
  });
1104
1088
  this.messenger.subscribe('NetworkController:networkRemoved', () => {
1105
1089
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_refreshAssetsAfterNetworkChange).call(this);
1106
1090
  });
1107
- // Selected EVM network switch (network picker). Enablement changes are
1108
- // handled separately via NetworkEnablementController:stateChanged.
1109
- this.messenger.subscribe('NetworkController:networkDidChange', (networkState) => {
1110
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_handleNetworkDidChange).call(this, networkState).catch(console.error);
1111
- });
1112
1091
  // Client + Keyring lifecycle: only run when UI is open AND keyring is unlocked
1113
- this.messenger.subscribe('ClientController:stateChanged', (isUiOpen) => {
1092
+ this.messenger.subscribe('ClientController:stateChange', (isUiOpen) => {
1114
1093
  __classPrivateFieldSet(this, _AssetsController_uiOpen, isUiOpen, "f");
1115
1094
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateActive).call(this);
1116
1095
  }, clientControllerSelectors.selectIsUiOpen);
@@ -1127,18 +1106,6 @@ _AssetsController_isEnabled = new WeakMap(), _AssetsController_isBasicFunctional
1127
1106
  this.messenger.subscribe('TransactionController:unapprovedTransactionAdded', (transactionMeta) => {
1128
1107
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_onUnapprovedTransactionAdded).call(this, transactionMeta);
1129
1108
  });
1130
- // Post-tx refresh via the full fetch pipeline (Accounts API + RPC fallback).
1131
- // RpcDataSource also listens for transactionConfirmed, but only refreshes
1132
- // chains it owns via an active subscription.
1133
- this.messenger.subscribe('TransactionController:transactionConfirmed', (transactionMeta) => {
1134
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_onTransactionConfirmed).call(this, transactionMeta);
1135
- });
1136
- // Real-time post-tx balances from AccountActivityService (same WS payload as
1137
- // TokenBalancesController; BackendWebsocketDataSource may not receive the
1138
- // callback when AccountActivityService owns the server subscription).
1139
- this.messenger.subscribe('AccountActivityService:balanceUpdated', (event) => {
1140
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_onAccountActivityBalanceUpdated).call(this, event);
1141
- });
1142
1109
  }, _AssetsController_onUnapprovedTransactionAdded = function _AssetsController_onUnapprovedTransactionAdded(transactionMeta) {
1143
1110
  const hexChainId = transactionMeta.chainId;
1144
1111
  if (!hexChainId) {
@@ -1161,46 +1128,6 @@ _AssetsController_isEnabled = new WeakMap(), _AssetsController_isBasicFunctional
1161
1128
  error,
1162
1129
  });
1163
1130
  });
1164
- }, _AssetsController_onTransactionConfirmed = function _AssetsController_onTransactionConfirmed(transactionMeta) {
1165
- const hexChainId = transactionMeta.chainId;
1166
- if (!hexChainId) {
1167
- return;
1168
- }
1169
- const caipChainId = `eip155:${parseInt(hexChainId, 16)}`;
1170
- const fromAddress = transactionMeta.txParams.from?.toLowerCase();
1171
- if (!fromAddress) {
1172
- return;
1173
- }
1174
- const matchedAccount = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSelectedAccounts).call(this).find((account) => account.address.toLowerCase() === fromAddress);
1175
- if (!matchedAccount) {
1176
- return;
1177
- }
1178
- this.getAssets([matchedAccount], {
1179
- chainIds: [caipChainId],
1180
- forceUpdate: true,
1181
- }).catch((error) => {
1182
- log('Failed to refresh assets after transaction confirmed', { error });
1183
- });
1184
- }, _AssetsController_onAccountActivityBalanceUpdated = function _AssetsController_onAccountActivityBalanceUpdated({ address, chain, updates, }) {
1185
- const account = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSelectedAccounts).call(this).find((a) => a.address.startsWith('0x')
1186
- ? a.address.toLowerCase() === address.toLowerCase()
1187
- : a.address === address);
1188
- if (!account) {
1189
- return;
1190
- }
1191
- const chainId = chain;
1192
- const response = processAccountActivityBalanceUpdates(updates, account.id, (assetId) => __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getAssetType).call(this, assetId));
1193
- if (!response.assetsBalance) {
1194
- return;
1195
- }
1196
- const request = {
1197
- accountsWithSupportedChains: [{ account, supportedChains: [chainId] }],
1198
- chainIds: [chainId],
1199
- dataTypes: ['balance', 'metadata'],
1200
- };
1201
- this.handleAssetsUpdate(response, 'AccountActivityService', request).catch((error) => {
1202
- log('Failed to apply AccountActivityService balance update', { error });
1203
- });
1204
1131
  }, _AssetsController_updateActive = function _AssetsController_updateActive() {
1205
1132
  const shouldRun = __classPrivateFieldGet(this, _AssetsController_uiOpen, "f") && __classPrivateFieldGet(this, _AssetsController_keyringUnlocked, "f");
1206
1133
  if (shouldRun) {
@@ -1256,32 +1183,6 @@ _AssetsController_isEnabled = new WeakMap(), _AssetsController_isBasicFunctional
1256
1183
  finally {
1257
1184
  releaseLock();
1258
1185
  }
1259
- }, _AssetsController_runStartupRefresh =
1260
- /**
1261
- * Force-fetch balances then subscribe. Used on unlock / first startup.
1262
- *
1263
- * @param accounts - Selected accounts to refresh.
1264
- */
1265
- async function _AssetsController_runStartupRefresh(accounts) {
1266
- const releaseLock = await __classPrivateFieldGet(this, _AssetsController_accountRefreshMutex, "f").acquire();
1267
- try {
1268
- await this.getAssets(accounts, {
1269
- chainIds: [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")],
1270
- forceUpdate: true,
1271
- });
1272
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1273
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
1274
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureDefaultTrackedAssetsSeeded).call(this);
1275
- }
1276
- catch (error) {
1277
- log('Failed to fetch assets on startup', error);
1278
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1279
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
1280
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureDefaultTrackedAssetsSeeded).call(this);
1281
- }
1282
- finally {
1283
- releaseLock();
1284
- }
1285
1186
  }, _AssetsController_registerActionHandlers = function _AssetsController_registerActionHandlers() {
1286
1187
  this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
1287
1188
  }, _AssetsController_handleActiveChainsUpdate = function _AssetsController_handleActiveChainsUpdate(dataSourceId, activeChains, previousChains) {
@@ -1395,41 +1296,6 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1395
1296
  });
1396
1297
  }
1397
1298
  return { response: result.response, durationByDataSource };
1398
- }, _AssetsController_fetchMissingPricesWithoutCache = function _AssetsController_fetchMissingPricesWithoutCache(accounts, chainIds) {
1399
- if (!__classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this) || accounts.length === 0) {
1400
- return;
1401
- }
1402
- const accountIds = new Set(accounts.map((account) => account.id));
1403
- const chainFilter = new Set(chainIds);
1404
- const assetsForPriceUpdate = [];
1405
- const prices = this.state.assetsPrice;
1406
- for (const [accountId, accountBalances] of Object.entries(this.state.assetsBalance)) {
1407
- if (!accountIds.has(accountId)) {
1408
- continue;
1409
- }
1410
- for (const assetId of Object.keys(accountBalances)) {
1411
- if (!chainFilter.has(assetId.split('/')[0])) {
1412
- continue;
1413
- }
1414
- const normalizedAssetId = normalizeAssetId(assetId);
1415
- if (prices[normalizedAssetId] ?? prices[assetId]) {
1416
- continue;
1417
- }
1418
- assetsForPriceUpdate.push(normalizedAssetId);
1419
- }
1420
- }
1421
- if (assetsForPriceUpdate.length === 0) {
1422
- return;
1423
- }
1424
- __classPrivateFieldGet(this, _AssetsController_priceDataSource, "f").invalidatePriceCache();
1425
- this.getAssets(accounts, {
1426
- forceUpdate: true,
1427
- dataTypes: ['price'],
1428
- chainIds,
1429
- assetsForPriceUpdate,
1430
- }).catch((error) => {
1431
- log('Failed to fetch missing prices', { error });
1432
- });
1433
1299
  }, _AssetsController_getNativeAssetMap = function _AssetsController_getNativeAssetMap() {
1434
1300
  return (__classPrivateFieldGet(this, _AssetsController_queryApiClient, "f").getCachedData(NATIVE_ASSETS_QUERY_KEY) ?? {});
1435
1301
  }, _AssetsController_isNativeAsset = function _AssetsController_isNativeAsset(assetId) {
@@ -1471,17 +1337,6 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1471
1337
  return __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_resolveNativeAssetIds).call(this, __classPrivateFieldGet(this, _AssetsController_enabledChains, "f"));
1472
1338
  }, _AssetsController_getNativeAssetIdsForAccount = function _AssetsController_getNativeAssetIdsForAccount(account) {
1473
1339
  return __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_resolveNativeAssetIds).call(this, __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getEnabledChainsForAccount).call(this, account));
1474
- }, _AssetsController_getSlowPipelineChainIds = function _AssetsController_getSlowPipelineChainIds(chainIds, fastResponse) {
1475
- const accountsApiChains = new Set(__classPrivateFieldGet(this, _AssetsController_accountsApiDataSource, "f").getActiveChainsSync());
1476
- return chainIds.filter((chainId) => {
1477
- if (fastResponse.errors?.[chainId]) {
1478
- return true;
1479
- }
1480
- if (!accountsApiChains.has(chainId)) {
1481
- return true;
1482
- }
1483
- return false;
1484
- });
1485
1340
  }, _AssetsController_ensureNativeBalancesDefaultZero = function _AssetsController_ensureNativeBalancesDefaultZero() {
1486
1341
  const accounts = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSelectedAccounts).call(this);
1487
1342
  if (accounts.length === 0) {
@@ -1499,7 +1354,7 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1499
1354
  balances[accountId] = {};
1500
1355
  }
1501
1356
  for (const nativeAssetId of nativeAssetIds) {
1502
- if (!Object.prototype.hasOwnProperty.call(balances[accountId], nativeAssetId)) {
1357
+ if (!(nativeAssetId in balances[accountId])) {
1503
1358
  balances[accountId][nativeAssetId] = { amount: '0' };
1504
1359
  }
1505
1360
  }
@@ -1549,9 +1404,37 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1549
1404
  }
1550
1405
  }
1551
1406
  });
1407
+ }, _AssetsController_filterBalancesRespectingWsFreshness = function _AssetsController_filterBalancesRespectingWsFreshness(accountId, accountBalances, sourceId) {
1408
+ if (!sourceId || !POLLING_BALANCE_SOURCES.has(sourceId)) {
1409
+ return accountBalances;
1410
+ }
1411
+ const now = Date.now();
1412
+ const filtered = {};
1413
+ for (const [assetId, balance] of Object.entries(accountBalances)) {
1414
+ const freshUntil = __classPrivateFieldGet(this, _AssetsController_wsBalanceFreshUntil, "f").get(`${accountId}:${assetId}`);
1415
+ if (freshUntil !== undefined && now < freshUntil) {
1416
+ continue;
1417
+ }
1418
+ filtered[assetId] = balance;
1419
+ }
1420
+ return filtered;
1421
+ }, _AssetsController_markWsBalancesFresh = function _AssetsController_markWsBalancesFresh(assetsBalance) {
1422
+ const freshUntil = Date.now() + WS_BALANCE_FRESHNESS_MS;
1423
+ for (const [accountId, balances] of Object.entries(assetsBalance)) {
1424
+ for (const assetId of Object.keys(balances)) {
1425
+ __classPrivateFieldGet(this, _AssetsController_wsBalanceFreshUntil, "f").set(`${accountId}:${assetId}`, freshUntil);
1426
+ }
1427
+ }
1428
+ }, _AssetsController_clearWsBalanceFreshness = function _AssetsController_clearWsBalanceFreshness(assetsBalance) {
1429
+ for (const [accountId, balances] of Object.entries(assetsBalance)) {
1430
+ for (const assetId of Object.keys(balances)) {
1431
+ __classPrivateFieldGet(this, _AssetsController_wsBalanceFreshUntil, "f").delete(`${accountId}:${assetId}`);
1432
+ }
1433
+ }
1552
1434
  }, _AssetsController_updateState = async function _AssetsController_updateState(response) {
1553
1435
  const normalizedResponse = normalizeResponse(response);
1554
1436
  const mode = normalizedResponse.updateMode ?? 'merge';
1437
+ const assetsBalanceToApply = normalizedResponse.assetsBalance;
1555
1438
  const releaseLock = await __classPrivateFieldGet(this, _AssetsController_controllerMutex, "f").acquire();
1556
1439
  try {
1557
1440
  const previousState = this.state;
@@ -1612,20 +1495,48 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1612
1495
  }
1613
1496
  }
1614
1497
  }
1615
- if (normalizedResponse.assetsBalance) {
1616
- for (const [accountId, accountBalances] of Object.entries(normalizedResponse.assetsBalance)) {
1498
+ if (assetsBalanceToApply) {
1499
+ for (const [accountId, accountBalances] of Object.entries(assetsBalanceToApply)) {
1500
+ const filteredAccountBalances = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_filterBalancesRespectingWsFreshness).call(this, accountId, accountBalances, normalizedResponse.sourceId);
1617
1501
  const previousBalances = previousState.assetsBalance[accountId] ?? {};
1618
1502
  const customAssetIds = state.customAssets[accountId] ?? [];
1619
- const replaceCoveredChains = mode === 'full' ||
1620
- normalizedResponse.replaceCoveredChainBalances === true;
1621
- const effective = mergeAccountBalances(previousBalances, accountBalances, customAssetIds, replaceCoveredChains);
1503
+ // Full: response is authoritative for the chains it covered;
1504
+ // balances for chains not in the response are preserved from
1505
+ // previous state so unsupported chains (e.g. Ink on AccountsAPI)
1506
+ // are never inadvertently reset to zero.
1507
+ // Merge: response overlays previous balances.
1508
+ const effective = mode === 'merge'
1509
+ ? { ...previousBalances, ...filteredAccountBalances }
1510
+ : (() => {
1511
+ // Determine which chain namespaces this response covers.
1512
+ const coveredChains = new Set(Object.keys(filteredAccountBalances).map((assetId) => assetId.split('/')[0]));
1513
+ // Start from previous balances, dropping only entries for
1514
+ // chains this response is authoritative over.
1515
+ const next = {};
1516
+ for (const [assetId, balance] of Object.entries(previousBalances)) {
1517
+ if (!coveredChains.has(assetId.split('/')[0])) {
1518
+ next[assetId] = balance;
1519
+ }
1520
+ }
1521
+ // Apply the response (authoritative for covered chains).
1522
+ Object.assign(next, filteredAccountBalances);
1523
+ // Preserve custom assets that the response omitted.
1524
+ for (const customId of customAssetIds) {
1525
+ if (!(customId in next)) {
1526
+ const prev = previousBalances[customId];
1527
+ next[customId] =
1528
+ prev ?? { amount: '0' };
1529
+ }
1530
+ }
1531
+ return next;
1532
+ })();
1622
1533
  // Ensure native tokens have an entry (0 if missing) for chains this account supports
1623
1534
  const account = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSelectedAccounts).call(this).find((a) => a.id === accountId);
1624
1535
  const nativeAssetIdsForAccount = account
1625
1536
  ? __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getNativeAssetIdsForAccount).call(this, account)
1626
1537
  : __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getNativeAssetIdsForEnabledChains).call(this);
1627
1538
  for (const nativeAssetId of nativeAssetIdsForAccount) {
1628
- if (!Object.prototype.hasOwnProperty.call(effective, nativeAssetId)) {
1539
+ if (!(nativeAssetId in effective)) {
1629
1540
  effective[nativeAssetId] = { amount: '0' };
1630
1541
  }
1631
1542
  }
@@ -1657,6 +1568,7 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1657
1568
  balances[accountId] = effective;
1658
1569
  }
1659
1570
  }
1571
+ // Update prices in state
1660
1572
  if (normalizedResponse.assetsPrice) {
1661
1573
  for (const [key, value] of Object.entries(normalizedResponse.assetsPrice)) {
1662
1574
  prices[key] = value;
@@ -1719,6 +1631,12 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1719
1631
  });
1720
1632
  }
1721
1633
  }
1634
+ // Authoritative fetch on account switch — drop WS freshness locks so API
1635
+ // balances (e.g. receiver +1 USDC) replace stale values from a prior send.
1636
+ if (normalizedResponse.sourceId === 'getAssets:forceUpdate' &&
1637
+ assetsBalanceToApply) {
1638
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_clearWsBalanceFreshness).call(this, assetsBalanceToApply);
1639
+ }
1722
1640
  }
1723
1641
  finally {
1724
1642
  releaseLock();
@@ -1817,8 +1735,14 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1817
1735
  enabledChainCount: chainIds.length,
1818
1736
  });
1819
1737
  __classPrivateFieldSet(this, _AssetsController_lastKnownAccountIds, new Set(accounts.map((a) => a.id)), "f");
1820
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_runStartupRefresh).call(this, accounts).catch((error) => {
1821
- log('Failed to start asset tracking', error);
1738
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1739
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
1740
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureDefaultTrackedAssetsSeeded).call(this);
1741
+ this.getAssets(accounts, {
1742
+ chainIds,
1743
+ forceUpdate: true,
1744
+ }).catch((error) => {
1745
+ log('Failed to fetch assets', error);
1822
1746
  });
1823
1747
  }, _AssetsController_stop = function _AssetsController_stop() {
1824
1748
  log('Stopping asset tracking', {
@@ -1896,13 +1820,7 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1896
1820
  const seenIds = new Set();
1897
1821
  const accountsForSource = assignedChains
1898
1822
  .flatMap((chainId) => chainToAccounts.get(chainId) ?? [])
1899
- .filter((account) => {
1900
- if (seenIds.has(account.id)) {
1901
- return false;
1902
- }
1903
- seenIds.add(account.id);
1904
- return true;
1905
- });
1823
+ .filter((account) => !seenIds.has(account.id) && (seenIds.add(account.id), true));
1906
1824
  if (accountsForSource.length > 0) {
1907
1825
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeDataSource).call(this, source, accountsForSource, assignedChains);
1908
1826
  }
@@ -2164,102 +2082,11 @@ async function _AssetsController_handleAccountGroupChanged() {
2164
2082
  caipChainId,
2165
2083
  });
2166
2084
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureDefaultTrackedAssetsSeeded).call(this, [caipChainId]);
2167
- }, _AssetsController_refreshActiveChainsOnNetworkSwitch =
2168
- /**
2169
- * Refresh data-source `activeChains` after an EVM network switch so API/WS/Rpc
2170
- * chain claiming is not stuck on an empty or stale init-time list.
2171
- */
2172
- async function _AssetsController_refreshActiveChainsOnNetworkSwitch() {
2173
- await Promise.all([
2174
- __classPrivateFieldGet(this, _AssetsController_accountsApiDataSource, "f").refreshActiveChains(),
2175
- __classPrivateFieldGet(this, _AssetsController_backendWebsocketDataSource, "f").refreshActiveChains(),
2176
- ]);
2177
- __classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f").refreshActiveChainsFromNetworkState();
2178
- }, _AssetsController_getSelectedEvmChainIdFromNetworkState = function _AssetsController_getSelectedEvmChainIdFromNetworkState(networkState) {
2179
- try {
2180
- const networkClient = this.messenger.call('NetworkController:getNetworkClientById', networkState.selectedNetworkClientId);
2181
- const hexChainId = networkClient.configuration?.chainId;
2182
- if (!hexChainId) {
2183
- return undefined;
2184
- }
2185
- return `eip155:${parseInt(hexChainId, 16)}`;
2186
- }
2187
- catch {
2188
- return undefined;
2189
- }
2190
- }, _AssetsController_handleNetworkDidChange =
2191
- /**
2192
- * Refresh subscriptions and fetch balances when the user switches the
2193
- * selected EVM network in the UI (`NetworkController:networkDidChange`).
2194
- *
2195
- * @param networkState - NetworkController state after the switch.
2196
- */
2197
- async function _AssetsController_handleNetworkDidChange(networkState) {
2198
- if (!__classPrivateFieldGet(this, _AssetsController_uiOpen, "f") || !__classPrivateFieldGet(this, _AssetsController_keyringUnlocked, "f") || !__classPrivateFieldGet(this, _AssetsController_isEnabled, "f").call(this)) {
2199
- return;
2200
- }
2201
- const accounts = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSelectedAccounts).call(this);
2202
- if (accounts.length === 0) {
2203
- return;
2204
- }
2205
- const selectedChainId = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSelectedEvmChainIdFromNetworkState).call(this, networkState);
2206
- if (!selectedChainId) {
2207
- return;
2208
- }
2209
- log('Selected EVM network switched', {
2210
- selectedNetworkClientId: networkState.selectedNetworkClientId,
2211
- selectedChainId,
2212
- });
2213
- const releaseLock = await __classPrivateFieldGet(this, _AssetsController_accountRefreshMutex, "f").acquire();
2214
- try {
2215
- await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_refreshActiveChainsOnNetworkSwitch).call(this);
2216
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
2217
- await this.getAssets(accounts, {
2218
- chainIds: [selectedChainId],
2219
- forceUpdate: true,
2220
- dataTypes: ['balance', 'metadata', 'price'],
2221
- });
2222
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
2223
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_fetchMissingPricesWithoutCache).call(this, accounts, [selectedChainId]);
2224
- }
2225
- finally {
2226
- releaseLock();
2227
- }
2228
2085
  }, _AssetsController_refreshAssetsAfterNetworkChange = function _AssetsController_refreshAssetsAfterNetworkChange() {
2229
- const accounts = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSelectedAccounts).call(this);
2230
- if (accounts.length === 0) {
2231
- return;
2232
- }
2233
- this.getAssets(accounts, {
2086
+ this.getAssets(__classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSelectedAccounts).call(this), {
2234
2087
  forceUpdate: true,
2235
- dataTypes: ['balance', 'metadata'],
2236
2088
  }).catch((error) => {
2237
2089
  log('Failed to refresh assets after network change', { error });
2238
2090
  });
2239
- }, _AssetsController_refreshAssetsAfterNetworkAdded =
2240
- /**
2241
- * Refresh balances and fetch missing prices after a network is added.
2242
- *
2243
- * @param hexChainId - Hex chain id of the newly-added network.
2244
- */
2245
- async function _AssetsController_refreshAssetsAfterNetworkAdded(hexChainId) {
2246
- const accounts = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSelectedAccounts).call(this);
2247
- if (accounts.length === 0) {
2248
- return;
2249
- }
2250
- let caipChainId;
2251
- try {
2252
- caipChainId = `eip155:${parseInt(hexChainId, 16)}`;
2253
- }
2254
- catch {
2255
- return;
2256
- }
2257
- await this.getAssets(accounts, {
2258
- chainIds: [caipChainId],
2259
- forceUpdate: true,
2260
- dataTypes: ['balance', 'metadata', 'price'],
2261
- });
2262
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
2263
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_fetchMissingPricesWithoutCache).call(this, accounts, [caipChainId]);
2264
2091
  };
2265
2092
  //# sourceMappingURL=AssetsController.mjs.map