@metamask-previews/assets-controller 11.2.1-preview-6cd1bb2f5 → 11.3.0-preview-2296abeaf

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,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [11.3.0]
11
+
12
+ ### Added
13
+
14
+ - `SnapDataSource` now preserves optional balance `metadata` from snap keyrings into `assetsBalance` when handling `AccountsController:accountBalancesUpdated` and `getAccountBalances` results ([#9564](https://github.com/MetaMask/core/pull/9564))
15
+
16
+ ### Changed
17
+
18
+ - Bump `@metamask/assets-controllers` from `^110.0.0` to `^110.0.1` ([#9693](https://github.com/MetaMask/core/pull/9693))
19
+ - Bump `@metamask/core-backend` from `^7.0.0` to `^8.0.0` ([#9693](https://github.com/MetaMask/core/pull/9693))
20
+ - Bump `@metamask/transaction-controller` from `^69.2.1` to `^69.3.0` ([#9693](https://github.com/MetaMask/core/pull/9693))
21
+ - 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))
22
+ - Pipeline / data-source / update enrichment spans emit only on the unlock (first-init) fetch per session; later polls, force updates, and subscription enrichment skip tracing.
23
+ - Tracing helpers live in `utils/trace.ts` (`emitTrace` / `withTrace`); omit `trace` to no-op so call sites stay free of gating `if`s. Fast and background fetch lanes are sibling `withTrace` calls (not nested).
24
+ - Dashboard-facing spans (`AssetsFullFetch`, `AssetsUpdatePipeline`, `AggregatedBalanceSelector`, etc.) record `duration_ms` as a Sentry measurement (and backdate `startTime`) so Spans widgets charting `p95(duration_ms)` receive data. Nesting parents use `AssetsFetchPipeline` / `AssetsUpdateEnrichment` / `AssetsBackgroundFetch` / `AggregatedBalance`.
25
+ - `AggregatedBalanceSelector` is nested under an `AggregatedBalance` parent span via `parentContext`.
26
+ - Bump `@metamask/keyring-api` from `^23.5.0` to `^23.7.0` ([#9676](https://github.com/MetaMask/core/pull/9676))
27
+ - Bump `@metamask/keyring-internal-api` from `^11.0.1` to `^11.0.2` ([#9676](https://github.com/MetaMask/core/pull/9676))
28
+ - Bump `@metamask/keyring-snap-client` from `^9.2.0` to `^9.2.1` ([#9676](https://github.com/MetaMask/core/pull/9676))
29
+
30
+ ### Fixed
31
+
32
+ - `withTrace` treats a rejected parent `trace` promise as best-effort (like `emitTrace`), so Sentry/adapter failures cannot fail full fetches or `handleAssetsUpdate` enrichment ([#9672](https://github.com/MetaMask/core/pull/9672))
33
+ - `SnapDataSource` now delivers snap-sourced balance updates directly to `AssetsController` via a constructor-supplied `onAssetsUpdate` callback instead of fanning out to `activeSubscriptions`, so updates (e.g. Tron energy/bandwidth) are no longer dropped when no active subscription is tracked for the chain in the SnapDataSource ([#9656](https://github.com/MetaMask/core/pull/9656))
34
+ - Balance aggregation selectors (`getAggregatedBalanceForAccount`, `getAggregatedBalanceForAccountIds`, `calculateBalanceForAllWallets`, `calculateBalanceChangeForAccountGroup`) no longer rescale balances whose amount is greater than or equal to `10^decimals`. Amounts in `assetsBalance` state are always human-readable, so the removed raw-vs-human magnitude heuristic corrupted legitimately large balances (e.g. 54.06B tokens with 9 decimals were divided by `10^9`), excluding them from aggregated fiat totals ([#9653](https://github.com/MetaMask/core/pull/9653))
35
+
10
36
  ## [11.2.1]
11
37
 
12
38
  ### Fixed
@@ -783,7 +809,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
783
809
  - Refactor `RpcDataSource` to delegate polling to `BalanceFetcher` and `TokenDetector` services ([#7709](https://github.com/MetaMask/core/pull/7709))
784
810
  - Refactor `BalanceFetcher` and `TokenDetector` to extend `StaticIntervalPollingControllerOnly` for independent polling management ([#7709](https://github.com/MetaMask/core/pull/7709))
785
811
 
786
- [Unreleased]: https://github.com/MetaMask/core/compare/@metamask/assets-controller@11.2.1...HEAD
812
+ [Unreleased]: https://github.com/MetaMask/core/compare/@metamask/assets-controller@11.3.0...HEAD
813
+ [11.3.0]: https://github.com/MetaMask/core/compare/@metamask/assets-controller@11.2.1...@metamask/assets-controller@11.3.0
787
814
  [11.2.1]: https://github.com/MetaMask/core/compare/@metamask/assets-controller@11.2.0...@metamask/assets-controller@11.2.1
788
815
  [11.2.0]: https://github.com/MetaMask/core/compare/@metamask/assets-controller@11.1.1...@metamask/assets-controller@11.2.0
789
816
  [11.1.1]: https://github.com/MetaMask/core/compare/@metamask/assets-controller@11.1.0...@metamask/assets-controller@11.1.1
@@ -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_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");
@@ -42,6 +42,7 @@ const constants_js_1 = require("./utils/constants.cjs");
42
42
  const customAssetsRpcSupplement_js_1 = require("./utils/customAssetsRpcSupplement.cjs");
43
43
  const index_js_1 = require("./utils/index.cjs");
44
44
  const processAccountActivityBalanceUpdates_js_1 = require("./utils/processAccountActivityBalanceUpdates.cjs");
45
+ const trace_js_1 = require("./utils/trace.cjs");
45
46
  const NATIVE_ASSETS_QUERY_KEY = ['nativeAssets'];
46
47
  /** Stable empty map so memoized formatters are not busted by `?? {}` identity churn. */
47
48
  const EMPTY_NATIVE_ASSET_MAP = {};
@@ -72,9 +73,14 @@ const DEFAULT_POLLING_INTERVAL_MS = 30000;
72
73
  // ============================================================================
73
74
  const TRACE_FIRST_INIT_FETCH = 'AssetsControllerFirstInitFetch';
74
75
  const TRACE_FULL_FETCH = 'AssetsFullFetch';
76
+ /** Parent span that nests per-source timings; dashboard charts {@link TRACE_FULL_FETCH}. */
77
+ const TRACE_FETCH_PIPELINE = 'AssetsFetchPipeline';
78
+ const TRACE_BACKGROUND_FETCH = 'AssetsBackgroundFetch';
75
79
  const TRACE_DATA_SOURCE_TIMING = 'AssetsDataSourceTiming';
76
80
  const TRACE_DATA_SOURCE_ERROR = 'AssetsDataSourceError';
77
81
  const TRACE_UPDATE_PIPELINE = 'AssetsUpdatePipeline';
82
+ /** Parent span that nests update enrichment; dashboard charts {@link TRACE_UPDATE_PIPELINE}. */
83
+ const TRACE_UPDATE_PARENT = 'AssetsUpdateEnrichment';
78
84
  const TRACE_SUBSCRIPTION_ERROR = 'AssetsSubscriptionError';
79
85
  const TRACE_STATE_SIZE = 'AssetsStateSize';
80
86
  const log = (0, logger_js_1.createModuleLogger)(logger_js_1.projectLogger, CONTROLLER_NAME);
@@ -366,6 +372,7 @@ class AssetsController extends base_controller_1.BaseController {
366
372
  __classPrivateFieldSet(this, _AssetsController_snapDataSource, new SnapDataSource_js_1.SnapDataSource({
367
373
  messenger: this.messenger,
368
374
  onActiveChainsUpdated: __classPrivateFieldGet(this, _AssetsController_onActiveChainsUpdated, "f"),
375
+ onAssetsUpdate: (response) => this.handleAssetsUpdate(response, 'SnapDataSource'),
369
376
  }), "f");
370
377
  __classPrivateFieldSet(this, _AssetsController_rpcDataSource, new RpcDataSource_js_1.RpcDataSource({
371
378
  messenger: this.messenger,
@@ -455,7 +462,11 @@ class AssetsController extends base_controller_1.BaseController {
455
462
  customAssets.push(...accountCustomAssets);
456
463
  }
457
464
  if (options?.forceUpdate) {
458
- const startTime = performance.now();
465
+ // Pipeline spans only on unlock/first-init fetch; later forceUpdates pass
466
+ // `undefined` so `emitTrace` / `withTrace` no-op without call-site if/else.
467
+ const pipelineTrace = __classPrivateFieldGet(this, _AssetsController_firstInitFetchReported, "f")
468
+ ? undefined
469
+ : __classPrivateFieldGet(this, _AssetsController_trace, "f");
459
470
  const request = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_buildDataRequest).call(this, accounts, chainIds, {
460
471
  assetTypes,
461
472
  dataTypes,
@@ -489,59 +500,101 @@ class AssetsController extends base_controller_1.BaseController {
489
500
  ]),
490
501
  ]
491
502
  : [__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,
503
+ const { response } = await (0, trace_js_1.withTrace)({
504
+ name: TRACE_FETCH_PIPELINE,
505
+ trace: pipelineTrace,
506
+ data: {
507
+ chain_count: chainIds.length,
508
+ account_count: accounts.length,
509
+ basic_functionality: __classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this),
510
+ },
511
+ fn: async (parentContext) => {
512
+ const startTime = performance.now();
513
+ const result = await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, {
514
+ sources: fastSources,
515
+ request,
516
+ parentContext,
517
+ trace: pipelineTrace,
518
+ });
519
+ await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, {
520
+ ...result.response,
521
+ updateMode: 'merge',
522
+ replaceCoveredChainBalances: true,
523
+ });
524
+ const durationMs = performance.now() - startTime;
525
+ // Summary fields for Assets Health (nested under the parent span).
526
+ (0, trace_js_1.emitTrace)({
527
+ name: TRACE_FULL_FETCH,
528
+ trace: pipelineTrace,
529
+ data: {
530
+ duration_ms: durationMs,
531
+ chain_count: chainIds.length,
532
+ account_count: accounts.length,
533
+ basic_functionality: __classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this),
534
+ asset_count: result.response.assetsBalance
535
+ ? Object.values(result.response.assetsBalance).reduce((sum, acct) => sum + Object.keys(acct).length, 0)
536
+ : 0,
537
+ price_count: result.response.assetsPrice
538
+ ? Object.keys(result.response.assetsPrice).length
539
+ : 0,
540
+ ...result.durationByDataSource,
541
+ },
542
+ parentContext,
543
+ });
544
+ (0, trace_js_1.emitTrace)({
545
+ name: TRACE_FIRST_INIT_FETCH,
546
+ trace: pipelineTrace,
547
+ data: {
548
+ duration_ms: durationMs,
549
+ chain_ids: JSON.stringify(chainIds),
550
+ ...result.durationByDataSource,
551
+ },
552
+ parentContext,
553
+ });
554
+ return result;
555
+ },
497
556
  });
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).
557
+ // Mark after the unlock/first forceUpdate so later polls skip pipeline spans.
558
+ __classPrivateFieldSet(this, _AssetsController_firstInitFetchReported, true, "f");
559
+ // Background (slow) lane flattened sibling of the fast lane (not nested
560
+ // inside its withTrace callback). Still fire-and-forget so UI is not blocked.
502
561
  const slowPipelineChainIds = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSlowPipelineChainIds).call(this, chainIds, response);
503
562
  if (slowPipelineChainIds.length > 0) {
504
563
  const slowSources = __classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)
505
564
  ? [__classPrivateFieldGet(this, _AssetsController_snapDataSource, "f"), __classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f")]
506
565
  : [__classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f")];
507
566
  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,
527
- chain_count: chainIds.length,
528
- account_count: accounts.length,
529
- 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, {
541
- duration_ms: durationMs,
542
- chain_ids: JSON.stringify(chainIds),
543
- ...durationByDataSource,
544
- });
567
+ (0, trace_js_1.withTrace)({
568
+ name: TRACE_BACKGROUND_FETCH,
569
+ trace: pipelineTrace,
570
+ data: {
571
+ chain_count: slowPipelineChainIds.length,
572
+ account_count: accounts.length,
573
+ },
574
+ fn: async (slowParentContext) => {
575
+ const { response: slowResponse } = await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, {
576
+ sources: [
577
+ (0, ParallelMiddleware_js_1.createParallelBalanceMiddleware)(slowSources),
578
+ __classPrivateFieldGet(this, _AssetsController_detectionMiddleware, "f"),
579
+ ...(__classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)
580
+ ? [
581
+ (0, ParallelMiddleware_js_1.createParallelMiddleware)([
582
+ __classPrivateFieldGet(this, _AssetsController_tokenDataSource, "f"),
583
+ __classPrivateFieldGet(this, _AssetsController_priceDataSource, "f"),
584
+ ]),
585
+ ]
586
+ : []),
587
+ ],
588
+ request: slowRequest,
589
+ parentContext: slowParentContext,
590
+ trace: pipelineTrace,
591
+ });
592
+ await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, {
593
+ ...slowResponse,
594
+ updateMode: 'merge',
595
+ });
596
+ },
597
+ }).catch((error) => log('Background pipeline failed', { error }));
545
598
  }
546
599
  }
547
600
  const result = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getAssetsFromState).call(this, accounts, chainIds, assetTypes);
@@ -921,57 +974,87 @@ class AssetsController extends base_controller_1.BaseController {
921
974
  * @param request - Optional original request for context when enriching
922
975
  */
923
976
  async handleAssetsUpdate(response, sourceId, request) {
924
- const updateStart = performance.now();
925
977
  log('Assets updated from data source', {
926
978
  sourceId,
927
979
  hasBalance: Boolean(response.assetsBalance),
928
980
  hasPrice: Boolean(response.assetsPrice),
929
981
  });
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, {
967
- source: sourceId,
968
- duration_ms: performance.now() - updateStart,
969
- has_balance: Boolean(response.assetsBalance),
970
- has_price: Boolean(response.assetsPrice),
971
- has_metadata: Boolean(enrichedResponse.assetsInfo),
972
- balance_account_count: response.assetsBalance
973
- ? Object.keys(response.assetsBalance).length
974
- : 0,
982
+ // Enrichment spans only before unlock/first-init fetch completes.
983
+ const pipelineTrace = __classPrivateFieldGet(this, _AssetsController_firstInitFetchReported, "f")
984
+ ? undefined
985
+ : __classPrivateFieldGet(this, _AssetsController_trace, "f");
986
+ await (0, trace_js_1.withTrace)({
987
+ name: TRACE_UPDATE_PARENT,
988
+ trace: pipelineTrace,
989
+ data: {
990
+ source: sourceId,
991
+ has_balance: Boolean(response.assetsBalance),
992
+ has_price: Boolean(response.assetsPrice),
993
+ balance_account_count: response.assetsBalance
994
+ ? Object.keys(response.assetsBalance).length
995
+ : 0,
996
+ },
997
+ fn: async (parentContext) => {
998
+ const updateStart = performance.now();
999
+ const resolvedRequest = request ?? {
1000
+ accountsWithSupportedChains: [],
1001
+ chainIds: [],
1002
+ dataTypes: ['balance', 'metadata', 'price'],
1003
+ };
1004
+ // RPC-only mode (basic functionality off): never run token/price APIs. Strip
1005
+ // those data types so downstream middleware cannot treat them as requested.
1006
+ const pipelineRequest = __classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)
1007
+ ? resolvedRequest
1008
+ : {
1009
+ ...resolvedRequest,
1010
+ dataTypes: resolvedRequest.dataTypes.filter((dt) => dt !== 'metadata' && dt !== 'price'),
1011
+ };
1012
+ // Graduate custom assets only when AccountsAPI / Websocket reports them.
1013
+ // RPC already fetches custom assets on purpose, and Snap handles non-EVM
1014
+ // chains the rule does not apply to, so skip the middleware for those.
1015
+ const shouldGraduateCustomAssets = sourceId === 'AccountsApiDataSource' ||
1016
+ sourceId === 'BackendWebsocketDataSource' ||
1017
+ sourceId === 'AccountActivityService';
1018
+ const enrichmentSources = [
1019
+ ...(shouldGraduateCustomAssets
1020
+ ? [__classPrivateFieldGet(this, _AssetsController_customAssetGraduationMiddleware, "f")]
1021
+ : []),
1022
+ __classPrivateFieldGet(this, _AssetsController_detectionMiddleware, "f"),
1023
+ ];
1024
+ if (__classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)) {
1025
+ enrichmentSources.push((0, ParallelMiddleware_js_1.createParallelMiddleware)([
1026
+ __classPrivateFieldGet(this, _AssetsController_tokenDataSource, "f"),
1027
+ __classPrivateFieldGet(this, _AssetsController_priceDataSource, "f"),
1028
+ ]));
1029
+ }
1030
+ const { response: enrichedResponse } = await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, {
1031
+ sources: enrichmentSources,
1032
+ request: pipelineRequest,
1033
+ initialResponse: response,
1034
+ parentContext,
1035
+ trace: pipelineTrace,
1036
+ });
1037
+ await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, {
1038
+ ...enrichedResponse,
1039
+ replaceCoveredChainBalances: response.replaceCoveredChainBalances,
1040
+ });
1041
+ // Summary fields for Assets Health (nested under the parent span).
1042
+ (0, trace_js_1.emitTrace)({
1043
+ name: TRACE_UPDATE_PIPELINE,
1044
+ trace: pipelineTrace,
1045
+ data: {
1046
+ source: sourceId,
1047
+ duration_ms: performance.now() - updateStart,
1048
+ has_balance: Boolean(response.assetsBalance),
1049
+ has_price: Boolean(response.assetsPrice),
1050
+ has_metadata: Boolean(enrichedResponse.assetsInfo),
1051
+ balance_account_count: response.assetsBalance
1052
+ ? Object.keys(response.assetsBalance).length
1053
+ : 0,
1054
+ },
1055
+ parentContext,
1056
+ });
1057
+ },
975
1058
  });
976
1059
  }
977
1060
  // ============================================================================
@@ -1011,16 +1094,7 @@ class AssetsController extends base_controller_1.BaseController {
1011
1094
  }
1012
1095
  }
1013
1096
  exports.AssetsController = AssetsController;
1014
- _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
- controller: 'AssetsController',
1016
- }) {
1017
- if (!__classPrivateFieldGet(this, _AssetsController_trace, "f")) {
1018
- return;
1019
- }
1020
- __classPrivateFieldGet(this, _AssetsController_trace, "f").call(this, { name, data, tags }, () => undefined)?.catch(() => {
1021
- // Telemetry failure must not break.
1022
- });
1023
- }, _AssetsController_emitStateSizeTrace = function _AssetsController_emitStateSizeTrace() {
1097
+ _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_emitStateSizeTrace = function _AssetsController_emitStateSizeTrace() {
1024
1098
  if (!__classPrivateFieldGet(this, _AssetsController_trace, "f") || __classPrivateFieldGet(this, _AssetsController_stateSizeReported, "f")) {
1025
1099
  return;
1026
1100
  }
@@ -1048,14 +1122,20 @@ _AssetsController_isEnabled = new WeakMap(), _AssetsController_isBasicFunctional
1048
1122
  customAssetEntries += ids.length;
1049
1123
  }
1050
1124
  }
1051
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_emitTrace).call(this, TRACE_STATE_SIZE, {
1052
- balance_entries: balanceEntries,
1053
- balance_accounts: Object.keys(balances).length,
1054
- unique_asset_count: uniqueAssets.size,
1055
- network_count: uniqueNetworks.size,
1056
- metadata_entries: Object.keys(assetsInfo).length,
1057
- price_entries: Object.keys(assetsPrice).length,
1058
- custom_asset_entries: customAssetEntries,
1125
+ (0, trace_js_1.emitTrace)({
1126
+ name: TRACE_STATE_SIZE,
1127
+ trace: __classPrivateFieldGet(this, _AssetsController_trace, "f"),
1128
+ data: {
1129
+ balance_entries: balanceEntries,
1130
+ balance_accounts: Object.keys(balances).length,
1131
+ // `asset_count` matches the Assets Health dashboard widget field name.
1132
+ asset_count: uniqueAssets.size,
1133
+ unique_asset_count: uniqueAssets.size,
1134
+ network_count: uniqueNetworks.size,
1135
+ metadata_entries: Object.keys(assetsInfo).length,
1136
+ price_entries: Object.keys(assetsPrice).length,
1137
+ custom_asset_entries: customAssetEntries,
1138
+ },
1059
1139
  });
1060
1140
  }, _AssetsController_getSelectedAccounts = function _AssetsController_getSelectedAccounts() {
1061
1141
  const accounts = this.messenger.call('AccountTreeController:getAccountsFromSelectedAccountGroup');
@@ -1380,12 +1460,16 @@ async function _AssetsController_runStartupRefresh(accounts) {
1380
1460
  * Execute middlewares with request/response context.
1381
1461
  * Returns response and exclusive duration per source (sum ≈ wall time).
1382
1462
  *
1383
- * @param sources - Data sources or middlewares with getName() and assetsMiddleware (executed in order).
1384
- * @param request - The data request.
1385
- * @param initialResponse - Optional initial response (for enriching existing data).
1463
+ * @param params - Middleware execution options.
1464
+ * @param params.sources - Data sources or middlewares with getName() and assetsMiddleware.
1465
+ * @param params.request - The data request.
1466
+ * @param params.initialResponse - Optional initial response (for enriching existing data).
1467
+ * @param params.parentContext - Optional parent Sentry span; per-source timings nest under it.
1468
+ * @param params.trace - Optional trace callback; omit after unlock/first-init to skip spans.
1386
1469
  * @returns Response and durationByDataSource (exclusive ms per source name).
1387
1470
  */
1388
- async function _AssetsController_executeMiddlewares(sources, request, initialResponse = {}) {
1471
+ async function _AssetsController_executeMiddlewares(params) {
1472
+ const { sources, request, initialResponse = {}, parentContext, trace, } = params;
1389
1473
  const names = sources.map((source) => source.getName());
1390
1474
  const middlewares = sources.map((source) => source.assetsMiddleware);
1391
1475
  const inclusive = [];
@@ -1429,13 +1513,24 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1429
1513
  durationByDataSource[key] = ms;
1430
1514
  }
1431
1515
  }
1432
- // Emit per-source timing traces for the Assets Health dashboard
1516
+ // Emit per-source timing as subspans under the parent fetch/update span
1517
+ // (no-op when `trace` is omitted — unlock/first-init only).
1433
1518
  for (const [sourceName, durationMs] of Object.entries(durationByDataSource)) {
1434
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_emitTrace).call(this, TRACE_DATA_SOURCE_TIMING, {
1435
- source: sourceName,
1436
- duration_ms: durationMs,
1437
- chain_count: request.chainIds.length,
1438
- account_count: request.accountsWithSupportedChains.length,
1519
+ (0, trace_js_1.emitTrace)({
1520
+ name: TRACE_DATA_SOURCE_TIMING,
1521
+ trace,
1522
+ data: {
1523
+ source: sourceName,
1524
+ duration_ms: durationMs,
1525
+ chain_count: request.chainIds.length,
1526
+ account_count: request.accountsWithSupportedChains.length,
1527
+ },
1528
+ tags: {
1529
+ controller: 'AssetsController',
1530
+ // String tag so Spans widgets can group by `source`.
1531
+ source: sourceName,
1532
+ },
1533
+ parentContext,
1439
1534
  });
1440
1535
  }
1441
1536
  // Failed middlewares: Issues (optional) + perf/Dashboard spans
@@ -1452,14 +1547,20 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1452
1547
  catch {
1453
1548
  // Never let telemetry throw.
1454
1549
  }
1455
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_emitTrace).call(this, TRACE_DATA_SOURCE_ERROR, {
1456
- failed_sources: failedSources,
1457
- error_count: middlewareErrors.length,
1458
- chain_count: request.chainIds.length,
1459
- }, {
1460
- controller: 'AssetsController',
1461
- severity: 'error',
1462
- error_type: assetsError.name,
1550
+ (0, trace_js_1.emitTrace)({
1551
+ name: TRACE_DATA_SOURCE_ERROR,
1552
+ trace,
1553
+ data: {
1554
+ failed_sources: failedSources,
1555
+ error_count: middlewareErrors.length,
1556
+ chain_count: request.chainIds.length,
1557
+ },
1558
+ tags: {
1559
+ controller: 'AssetsController',
1560
+ severity: 'error',
1561
+ error_type: assetsError.name,
1562
+ },
1563
+ parentContext,
1463
1564
  });
1464
1565
  }
1465
1566
  return { response: result.response, durationByDataSource };
@@ -2081,9 +2182,13 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
2081
2182
  };
2082
2183
  source.subscribe(subscribeReq).catch((error) => {
2083
2184
  console.error(`[AssetsController] Failed to subscribe to '${sourceId}':`, error);
2084
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_emitTrace).call(this, TRACE_SUBSCRIPTION_ERROR, {
2085
- source: sourceId,
2086
- error_message: String(error),
2185
+ (0, trace_js_1.emitTrace)({
2186
+ name: TRACE_SUBSCRIPTION_ERROR,
2187
+ trace: __classPrivateFieldGet(this, _AssetsController_trace, "f"),
2188
+ data: {
2189
+ source: sourceId,
2190
+ error_message: String(error),
2191
+ },
2087
2192
  });
2088
2193
  });
2089
2194
  // Track subscription