@metamask-previews/assets-controller 11.2.1-preview-3d3ef9d3a → 11.2.1-preview-e0f702919

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,11 @@ 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
+ - Nest AssetsController Sentry spans (`AssetsDataSourceTiming`, `AssetsDataSourceError`, `AssetsControllerFirstInitFetch`, pipeline summaries) as subspans under parent `AssetsFullFetch` / `AssetsUpdatePipeline` / `AssetsBackgroundFetch` traces, and emit a single `AggregatedBalanceSelector` span for `calculateBalanceForAllWallets` instead of one root span per account group, to avoid Sentry rate limits ([#9672](https://github.com/MetaMask/core/pull/9672))
13
+ - Pipeline traces (`AssetsFullFetch`, `AssetsUpdatePipeline`, per-source timings, etc.) run on cold start only (first `#start` fetch per unlock session); later fetches/updates skip Sentry wrapping. Subscription errors and the one-shot state-size trace remain enabled.
14
+
10
15
  ## [11.2.1]
11
16
 
12
17
  ### Fixed
@@ -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_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_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_emitTrace, _AssetsController_withTrace, _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_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");
@@ -72,6 +72,7 @@ const DEFAULT_POLLING_INTERVAL_MS = 30000;
72
72
  // ============================================================================
73
73
  const TRACE_FIRST_INIT_FETCH = 'AssetsControllerFirstInitFetch';
74
74
  const TRACE_FULL_FETCH = 'AssetsFullFetch';
75
+ const TRACE_BACKGROUND_FETCH = 'AssetsBackgroundFetch';
75
76
  const TRACE_DATA_SOURCE_TIMING = 'AssetsDataSourceTiming';
76
77
  const TRACE_DATA_SOURCE_ERROR = 'AssetsDataSourceError';
77
78
  const TRACE_UPDATE_PIPELINE = 'AssetsUpdatePipeline';
@@ -455,94 +456,111 @@ class AssetsController extends base_controller_1.BaseController {
455
456
  customAssets.push(...accountCustomAssets);
456
457
  }
457
458
  if (options?.forceUpdate) {
458
- const startTime = performance.now();
459
- const request = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_buildDataRequest).call(this, accounts, chainIds, {
460
- assetTypes,
461
- dataTypes,
462
- customAssets: customAssets.length > 0 ? customAssets : undefined,
463
- forceUpdate: true,
464
- assetsForPriceUpdate: options?.assetsForPriceUpdate,
465
- });
466
- // Fast pipeline: accountsApi + stakedBalance → detection → token + price.
467
- // Snap and RPC are excluded here due to their latency (snap triggers account
468
- // creation, RPC is slow on many chains). Results are committed to state
469
- // immediately so the UI can display balances without waiting for them.
470
- //
471
- // Fast/slow pipelines use merge so partial API snapshots cannot wipe
472
- // tokens missing from the response (e.g. USDC when only native balance
473
- // is returned). Balances present in the response are still refreshed.
474
- const fastSources = __classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)
475
- ? [
476
- (0, ParallelMiddleware_js_1.createParallelBalanceMiddleware)([
477
- __classPrivateFieldGet(this, _AssetsController_accountsApiDataSource, "f"),
478
- __classPrivateFieldGet(this, _AssetsController_stakedBalanceDataSource, "f"),
479
- ]),
480
- // Graduation must run BEFORE the RPC fallback so it only sees
481
- // AccountsApi/Websocket balances. RPC intentionally carries
482
- // custom assets and must never trigger graduation.
483
- __classPrivateFieldGet(this, _AssetsController_customAssetGraduationMiddleware, "f"),
484
- __classPrivateFieldGet(this, _AssetsController_rpcFallbackMiddleware, "f"),
485
- __classPrivateFieldGet(this, _AssetsController_detectionMiddleware, "f"),
486
- (0, ParallelMiddleware_js_1.createParallelMiddleware)([
487
- __classPrivateFieldGet(this, _AssetsController_tokenDataSource, "f"),
488
- __classPrivateFieldGet(this, _AssetsController_priceDataSource, "f"),
489
- ]),
490
- ]
491
- : [__classPrivateFieldGet(this, _AssetsController_stakedBalanceDataSource, "f"), __classPrivateFieldGet(this, _AssetsController_detectionMiddleware, "f")];
492
- const { response, durationByDataSource } = await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, fastSources, request);
493
- await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, {
494
- ...response,
495
- updateMode: 'merge',
496
- replaceCoveredChainBalances: true,
497
- });
498
- // Background pipeline: snap and RPC run in parallel after the fast path
499
- // commits to state. Their balances are merged together before detection.
500
- // Token + price enrichment matches the pre-split behavior: only when basic
501
- // functionality is on (RPC-only mode must not call token/price APIs).
502
- const slowPipelineChainIds = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSlowPipelineChainIds).call(this, chainIds, response);
503
- if (slowPipelineChainIds.length > 0) {
504
- const slowSources = __classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)
505
- ? [__classPrivateFieldGet(this, _AssetsController_snapDataSource, "f"), __classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f")]
506
- : [__classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f")];
507
- const slowRequest = { ...request, chainIds: slowPipelineChainIds };
508
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, [
509
- (0, ParallelMiddleware_js_1.createParallelBalanceMiddleware)(slowSources),
510
- __classPrivateFieldGet(this, _AssetsController_detectionMiddleware, "f"),
511
- ...(__classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)
512
- ? [
513
- (0, ParallelMiddleware_js_1.createParallelMiddleware)([
514
- __classPrivateFieldGet(this, _AssetsController_tokenDataSource, "f"),
515
- __classPrivateFieldGet(this, _AssetsController_priceDataSource, "f"),
516
- ]),
517
- ]
518
- : []),
519
- ], slowRequest)
520
- .then(({ response: slowResponse }) => __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, { ...slowResponse, updateMode: 'merge' }))
521
- .catch((error) => log('Background pipeline failed', { error }));
522
- }
523
- const durationMs = performance.now() - startTime;
524
- // Emit trace for every full fetch (Assets Health dashboard)
525
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_emitTrace).call(this, TRACE_FULL_FETCH, {
526
- duration_ms: durationMs,
459
+ await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_withTrace).call(this, TRACE_FULL_FETCH, {
527
460
  chain_count: chainIds.length,
528
461
  account_count: accounts.length,
529
462
  basic_functionality: __classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this),
530
- asset_count: response.assetsBalance
531
- ? Object.values(response.assetsBalance).reduce((sum, acct) => sum + Object.keys(acct).length, 0)
532
- : 0,
533
- price_count: response.assetsPrice
534
- ? Object.keys(response.assetsPrice).length
535
- : 0,
536
- ...durationByDataSource,
537
- });
538
- if (!__classPrivateFieldGet(this, _AssetsController_firstInitFetchReported, "f")) {
539
- __classPrivateFieldSet(this, _AssetsController_firstInitFetchReported, true, "f");
540
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_emitTrace).call(this, TRACE_FIRST_INIT_FETCH, {
463
+ }, async (parentContext) => {
464
+ const startTime = performance.now();
465
+ const request = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_buildDataRequest).call(this, accounts, chainIds, {
466
+ assetTypes,
467
+ dataTypes,
468
+ customAssets: customAssets.length > 0 ? customAssets : undefined,
469
+ forceUpdate: true,
470
+ assetsForPriceUpdate: options?.assetsForPriceUpdate,
471
+ });
472
+ // Fast pipeline: accountsApi + stakedBalance → detection → token + price.
473
+ // Snap and RPC are excluded here due to their latency (snap triggers account
474
+ // creation, RPC is slow on many chains). Results are committed to state
475
+ // immediately so the UI can display balances without waiting for them.
476
+ //
477
+ // Fast/slow pipelines use merge so partial API snapshots cannot wipe
478
+ // tokens missing from the response (e.g. USDC when only native balance
479
+ // is returned). Balances present in the response are still refreshed.
480
+ const fastSources = __classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)
481
+ ? [
482
+ (0, ParallelMiddleware_js_1.createParallelBalanceMiddleware)([
483
+ __classPrivateFieldGet(this, _AssetsController_accountsApiDataSource, "f"),
484
+ __classPrivateFieldGet(this, _AssetsController_stakedBalanceDataSource, "f"),
485
+ ]),
486
+ // Graduation must run BEFORE the RPC fallback so it only sees
487
+ // AccountsApi/Websocket balances. RPC intentionally carries
488
+ // custom assets and must never trigger graduation.
489
+ __classPrivateFieldGet(this, _AssetsController_customAssetGraduationMiddleware, "f"),
490
+ __classPrivateFieldGet(this, _AssetsController_rpcFallbackMiddleware, "f"),
491
+ __classPrivateFieldGet(this, _AssetsController_detectionMiddleware, "f"),
492
+ (0, ParallelMiddleware_js_1.createParallelMiddleware)([
493
+ __classPrivateFieldGet(this, _AssetsController_tokenDataSource, "f"),
494
+ __classPrivateFieldGet(this, _AssetsController_priceDataSource, "f"),
495
+ ]),
496
+ ]
497
+ : [__classPrivateFieldGet(this, _AssetsController_stakedBalanceDataSource, "f"), __classPrivateFieldGet(this, _AssetsController_detectionMiddleware, "f")];
498
+ const { response, durationByDataSource } = await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, fastSources, request, {}, parentContext);
499
+ await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, {
500
+ ...response,
501
+ updateMode: 'merge',
502
+ replaceCoveredChainBalances: true,
503
+ });
504
+ // Background pipeline: snap and RPC run in parallel after the fast path
505
+ // commits to state. Their balances are merged together before detection.
506
+ // Token + price enrichment matches the pre-split behavior: only when basic
507
+ // functionality is on (RPC-only mode must not call token/price APIs).
508
+ // Own parent span so timings nest instead of becoming extra root traces
509
+ // after the fast-path parent has ended.
510
+ const slowPipelineChainIds = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSlowPipelineChainIds).call(this, chainIds, response);
511
+ if (slowPipelineChainIds.length > 0) {
512
+ const slowSources = __classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)
513
+ ? [__classPrivateFieldGet(this, _AssetsController_snapDataSource, "f"), __classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f")]
514
+ : [__classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f")];
515
+ const slowRequest = { ...request, chainIds: slowPipelineChainIds };
516
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_withTrace).call(this, TRACE_BACKGROUND_FETCH, {
517
+ chain_count: slowPipelineChainIds.length,
518
+ account_count: accounts.length,
519
+ }, async (slowParentContext) => {
520
+ const { response: slowResponse } = await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, [
521
+ (0, ParallelMiddleware_js_1.createParallelBalanceMiddleware)(slowSources),
522
+ __classPrivateFieldGet(this, _AssetsController_detectionMiddleware, "f"),
523
+ ...(__classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)
524
+ ? [
525
+ (0, ParallelMiddleware_js_1.createParallelMiddleware)([
526
+ __classPrivateFieldGet(this, _AssetsController_tokenDataSource, "f"),
527
+ __classPrivateFieldGet(this, _AssetsController_priceDataSource, "f"),
528
+ ]),
529
+ ]
530
+ : []),
531
+ ], slowRequest, {}, slowParentContext);
532
+ await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, {
533
+ ...slowResponse,
534
+ updateMode: 'merge',
535
+ });
536
+ }).catch((error) => log('Background pipeline failed', { error }));
537
+ }
538
+ const durationMs = performance.now() - startTime;
539
+ // Summary fields for Assets Health (nested under the parent span so
540
+ // they do not create additional root transactions).
541
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_emitTrace).call(this, TRACE_FULL_FETCH, {
541
542
  duration_ms: durationMs,
542
- chain_ids: JSON.stringify(chainIds),
543
+ chain_count: chainIds.length,
544
+ account_count: accounts.length,
545
+ basic_functionality: __classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this),
546
+ asset_count: response.assetsBalance
547
+ ? Object.values(response.assetsBalance).reduce((sum, acct) => sum + Object.keys(acct).length, 0)
548
+ : 0,
549
+ price_count: response.assetsPrice
550
+ ? Object.keys(response.assetsPrice).length
551
+ : 0,
543
552
  ...durationByDataSource,
544
- });
545
- }
553
+ }, { controller: 'AssetsController' }, parentContext);
554
+ if (!__classPrivateFieldGet(this, _AssetsController_firstInitFetchReported, "f")) {
555
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_emitTrace).call(this, TRACE_FIRST_INIT_FETCH, {
556
+ duration_ms: durationMs,
557
+ chain_ids: JSON.stringify(chainIds),
558
+ ...durationByDataSource,
559
+ }, { controller: 'AssetsController' }, parentContext);
560
+ // Mark after emitting so the first-init span itself is not gated.
561
+ __classPrivateFieldSet(this, _AssetsController_firstInitFetchReported, true, "f");
562
+ }
563
+ });
546
564
  }
547
565
  const result = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getAssetsFromState).call(this, accounts, chainIds, assetTypes);
548
566
  return result;
@@ -921,57 +939,67 @@ class AssetsController extends base_controller_1.BaseController {
921
939
  * @param request - Optional original request for context when enriching
922
940
  */
923
941
  async handleAssetsUpdate(response, sourceId, request) {
924
- const updateStart = performance.now();
925
942
  log('Assets updated from data source', {
926
943
  sourceId,
927
944
  hasBalance: Boolean(response.assetsBalance),
928
945
  hasPrice: Boolean(response.assetsPrice),
929
946
  });
930
- const resolvedRequest = request ?? {
931
- accountsWithSupportedChains: [],
932
- chainIds: [],
933
- dataTypes: ['balance', 'metadata', 'price'],
934
- };
935
- // RPC-only mode (basic functionality off): never run token/price APIs. Strip
936
- // those data types so downstream middleware cannot treat them as requested.
937
- const pipelineRequest = __classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)
938
- ? resolvedRequest
939
- : {
940
- ...resolvedRequest,
941
- dataTypes: resolvedRequest.dataTypes.filter((dt) => dt !== 'metadata' && dt !== 'price'),
942
- };
943
- // Graduate custom assets only when AccountsAPI / Websocket reports them.
944
- // RPC already fetches custom assets on purpose, and Snap handles non-EVM
945
- // chains the rule does not apply to, so skip the middleware for those.
946
- const shouldGraduateCustomAssets = sourceId === 'AccountsApiDataSource' ||
947
- sourceId === 'BackendWebsocketDataSource' ||
948
- sourceId === 'AccountActivityService';
949
- const enrichmentSources = [
950
- ...(shouldGraduateCustomAssets
951
- ? [__classPrivateFieldGet(this, _AssetsController_customAssetGraduationMiddleware, "f")]
952
- : []),
953
- __classPrivateFieldGet(this, _AssetsController_detectionMiddleware, "f"),
954
- ];
955
- if (__classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)) {
956
- enrichmentSources.push((0, ParallelMiddleware_js_1.createParallelMiddleware)([
957
- __classPrivateFieldGet(this, _AssetsController_tokenDataSource, "f"),
958
- __classPrivateFieldGet(this, _AssetsController_priceDataSource, "f"),
959
- ]));
960
- }
961
- const { response: enrichedResponse } = await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, enrichmentSources, pipelineRequest, response);
962
- await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, {
963
- ...enrichedResponse,
964
- replaceCoveredChainBalances: response.replaceCoveredChainBalances,
965
- });
966
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_emitTrace).call(this, TRACE_UPDATE_PIPELINE, {
947
+ await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_withTrace).call(this, TRACE_UPDATE_PIPELINE, {
967
948
  source: sourceId,
968
- duration_ms: performance.now() - updateStart,
969
949
  has_balance: Boolean(response.assetsBalance),
970
950
  has_price: Boolean(response.assetsPrice),
971
- has_metadata: Boolean(enrichedResponse.assetsInfo),
972
951
  balance_account_count: response.assetsBalance
973
952
  ? Object.keys(response.assetsBalance).length
974
953
  : 0,
954
+ }, async (parentContext) => {
955
+ const updateStart = performance.now();
956
+ const resolvedRequest = request ?? {
957
+ accountsWithSupportedChains: [],
958
+ chainIds: [],
959
+ dataTypes: ['balance', 'metadata', 'price'],
960
+ };
961
+ // RPC-only mode (basic functionality off): never run token/price APIs. Strip
962
+ // those data types so downstream middleware cannot treat them as requested.
963
+ const pipelineRequest = __classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)
964
+ ? resolvedRequest
965
+ : {
966
+ ...resolvedRequest,
967
+ dataTypes: resolvedRequest.dataTypes.filter((dt) => dt !== 'metadata' && dt !== 'price'),
968
+ };
969
+ // Graduate custom assets only when AccountsAPI / Websocket reports them.
970
+ // RPC already fetches custom assets on purpose, and Snap handles non-EVM
971
+ // chains the rule does not apply to, so skip the middleware for those.
972
+ const shouldGraduateCustomAssets = sourceId === 'AccountsApiDataSource' ||
973
+ sourceId === 'BackendWebsocketDataSource' ||
974
+ sourceId === 'AccountActivityService';
975
+ const enrichmentSources = [
976
+ ...(shouldGraduateCustomAssets
977
+ ? [__classPrivateFieldGet(this, _AssetsController_customAssetGraduationMiddleware, "f")]
978
+ : []),
979
+ __classPrivateFieldGet(this, _AssetsController_detectionMiddleware, "f"),
980
+ ];
981
+ if (__classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)) {
982
+ enrichmentSources.push((0, ParallelMiddleware_js_1.createParallelMiddleware)([
983
+ __classPrivateFieldGet(this, _AssetsController_tokenDataSource, "f"),
984
+ __classPrivateFieldGet(this, _AssetsController_priceDataSource, "f"),
985
+ ]));
986
+ }
987
+ const { response: enrichedResponse } = await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, enrichmentSources, pipelineRequest, response, parentContext);
988
+ await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, {
989
+ ...enrichedResponse,
990
+ replaceCoveredChainBalances: response.replaceCoveredChainBalances,
991
+ });
992
+ // Summary fields for Assets Health (nested under the parent span).
993
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_emitTrace).call(this, TRACE_UPDATE_PIPELINE, {
994
+ source: sourceId,
995
+ duration_ms: performance.now() - updateStart,
996
+ has_balance: Boolean(response.assetsBalance),
997
+ has_price: Boolean(response.assetsPrice),
998
+ has_metadata: Boolean(enrichedResponse.assetsInfo),
999
+ balance_account_count: response.assetsBalance
1000
+ ? Object.keys(response.assetsBalance).length
1001
+ : 0,
1002
+ }, { controller: 'AssetsController' }, parentContext);
975
1003
  });
976
1004
  }
977
1005
  // ============================================================================
@@ -1013,13 +1041,42 @@ class AssetsController extends base_controller_1.BaseController {
1013
1041
  exports.AssetsController = AssetsController;
1014
1042
  _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 = {
1015
1043
  controller: 'AssetsController',
1016
- }) {
1044
+ }, parentContext) {
1017
1045
  if (!__classPrivateFieldGet(this, _AssetsController_trace, "f")) {
1018
1046
  return;
1019
1047
  }
1020
- __classPrivateFieldGet(this, _AssetsController_trace, "f").call(this, { name, data, tags }, () => undefined)?.catch(() => {
1048
+ // Cold-start only: skip root spans after the first init fetch. Nested
1049
+ // subspans (parentContext set) and error/state-size traces still allowed.
1050
+ if (__classPrivateFieldGet(this, _AssetsController_firstInitFetchReported, "f") &&
1051
+ parentContext === undefined &&
1052
+ name !== TRACE_SUBSCRIPTION_ERROR &&
1053
+ name !== TRACE_STATE_SIZE) {
1054
+ return;
1055
+ }
1056
+ __classPrivateFieldGet(this, _AssetsController_trace, "f").call(this, { name, data, tags, parentContext }, () => undefined)?.catch(() => {
1021
1057
  // Telemetry failure must not break.
1022
1058
  });
1059
+ }, _AssetsController_withTrace =
1060
+ /**
1061
+ * Run work inside a parent Sentry span and pass its context so callers can
1062
+ * emit nested subspans via {@link #emitTrace}.
1063
+ *
1064
+ * Only wraps work on cold start (before the first init fetch is reported).
1065
+ * Later calls still run `fn`, but without creating a Sentry parent span.
1066
+ *
1067
+ * @param name - Parent span name.
1068
+ * @param data - Key-value pairs attached as span data.
1069
+ * @param fn - Work to run; receives the parent span context.
1070
+ * @param tags - Key-value pairs used for Sentry filtering.
1071
+ * @returns The result of `fn`.
1072
+ */
1073
+ async function _AssetsController_withTrace(name, data, fn, tags = {
1074
+ controller: 'AssetsController',
1075
+ }) {
1076
+ if (!__classPrivateFieldGet(this, _AssetsController_trace, "f") || __classPrivateFieldGet(this, _AssetsController_firstInitFetchReported, "f")) {
1077
+ return fn(undefined);
1078
+ }
1079
+ return await __classPrivateFieldGet(this, _AssetsController_trace, "f").call(this, { name, data, tags }, (parentContext) => fn(parentContext));
1023
1080
  }, _AssetsController_emitStateSizeTrace = function _AssetsController_emitStateSizeTrace() {
1024
1081
  if (!__classPrivateFieldGet(this, _AssetsController_trace, "f") || __classPrivateFieldGet(this, _AssetsController_stateSizeReported, "f")) {
1025
1082
  return;
@@ -1383,9 +1440,10 @@ async function _AssetsController_runStartupRefresh(accounts) {
1383
1440
  * @param sources - Data sources or middlewares with getName() and assetsMiddleware (executed in order).
1384
1441
  * @param request - The data request.
1385
1442
  * @param initialResponse - Optional initial response (for enriching existing data).
1443
+ * @param parentContext - Optional parent Sentry span; per-source timings nest under it.
1386
1444
  * @returns Response and durationByDataSource (exclusive ms per source name).
1387
1445
  */
1388
- async function _AssetsController_executeMiddlewares(sources, request, initialResponse = {}) {
1446
+ async function _AssetsController_executeMiddlewares(sources, request, initialResponse = {}, parentContext) {
1389
1447
  const names = sources.map((source) => source.getName());
1390
1448
  const middlewares = sources.map((source) => source.assetsMiddleware);
1391
1449
  const inclusive = [];
@@ -1429,14 +1487,14 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1429
1487
  durationByDataSource[key] = ms;
1430
1488
  }
1431
1489
  }
1432
- // Emit per-source timing traces for the Assets Health dashboard
1490
+ // Emit per-source timing as subspans under the parent fetch/update span
1433
1491
  for (const [sourceName, durationMs] of Object.entries(durationByDataSource)) {
1434
1492
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_emitTrace).call(this, TRACE_DATA_SOURCE_TIMING, {
1435
1493
  source: sourceName,
1436
1494
  duration_ms: durationMs,
1437
1495
  chain_count: request.chainIds.length,
1438
1496
  account_count: request.accountsWithSupportedChains.length,
1439
- });
1497
+ }, { controller: 'AssetsController' }, parentContext);
1440
1498
  }
1441
1499
  // Failed middlewares: Issues (optional) + perf/Dashboard spans
1442
1500
  if (middlewareErrors.length > 0) {
@@ -1460,7 +1518,7 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1460
1518
  controller: 'AssetsController',
1461
1519
  severity: 'error',
1462
1520
  error_type: assetsError.name,
1463
- });
1521
+ }, parentContext);
1464
1522
  }
1465
1523
  return { response: result.response, durationByDataSource };
1466
1524
  }, _AssetsController_fetchMissingPricesWithoutCache = function _AssetsController_fetchMissingPricesWithoutCache(accounts, chainIds) {