@metamask-previews/assets-controller 11.2.1-preview-6316943cb → 11.2.1-preview-0b2e3c9b4

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 (52) hide show
  1. package/CHANGELOG.md +6 -3
  2. package/dist/AssetsController.cjs +237 -133
  3. package/dist/AssetsController.cjs.map +1 -1
  4. package/dist/AssetsController.d.cts.map +1 -1
  5. package/dist/AssetsController.d.mts.map +1 -1
  6. package/dist/AssetsController.mjs +237 -133
  7. package/dist/AssetsController.mjs.map +1 -1
  8. package/dist/data-sources/AccountsApiDataSource.cjs +11 -2
  9. package/dist/data-sources/AccountsApiDataSource.cjs.map +1 -1
  10. package/dist/data-sources/AccountsApiDataSource.d.cts.map +1 -1
  11. package/dist/data-sources/AccountsApiDataSource.d.mts.map +1 -1
  12. package/dist/data-sources/AccountsApiDataSource.mjs +10 -1
  13. package/dist/data-sources/AccountsApiDataSource.mjs.map +1 -1
  14. package/dist/data-sources/BackendWebsocketDataSource.cjs +20 -12
  15. package/dist/data-sources/BackendWebsocketDataSource.cjs.map +1 -1
  16. package/dist/data-sources/BackendWebsocketDataSource.d.cts +1 -2
  17. package/dist/data-sources/BackendWebsocketDataSource.d.cts.map +1 -1
  18. package/dist/data-sources/BackendWebsocketDataSource.d.mts +1 -2
  19. package/dist/data-sources/BackendWebsocketDataSource.d.mts.map +1 -1
  20. package/dist/data-sources/BackendWebsocketDataSource.mjs +20 -12
  21. package/dist/data-sources/BackendWebsocketDataSource.mjs.map +1 -1
  22. package/dist/selectors/balance.cjs +52 -12
  23. package/dist/selectors/balance.cjs.map +1 -1
  24. package/dist/selectors/balance.d.cts.map +1 -1
  25. package/dist/selectors/balance.d.mts.map +1 -1
  26. package/dist/selectors/balance.mjs +52 -12
  27. package/dist/selectors/balance.mjs.map +1 -1
  28. package/dist/utils/index.cjs +1 -3
  29. package/dist/utils/index.cjs.map +1 -1
  30. package/dist/utils/index.d.cts +0 -1
  31. package/dist/utils/index.d.cts.map +1 -1
  32. package/dist/utils/index.d.mts +0 -1
  33. package/dist/utils/index.d.mts.map +1 -1
  34. package/dist/utils/index.mjs +0 -1
  35. package/dist/utils/index.mjs.map +1 -1
  36. package/dist/utils/trace.cjs +120 -0
  37. package/dist/utils/trace.cjs.map +1 -0
  38. package/dist/utils/trace.d.cts +52 -0
  39. package/dist/utils/trace.d.cts.map +1 -0
  40. package/dist/utils/trace.d.mts +52 -0
  41. package/dist/utils/trace.d.mts.map +1 -0
  42. package/dist/utils/trace.mjs +115 -0
  43. package/dist/utils/trace.mjs.map +1 -0
  44. package/package.json +1 -1
  45. package/dist/utils/caip.cjs +0 -24
  46. package/dist/utils/caip.cjs.map +0 -1
  47. package/dist/utils/caip.d.cts +0 -11
  48. package/dist/utils/caip.d.cts.map +0 -1
  49. package/dist/utils/caip.d.mts +0 -11
  50. package/dist/utils/caip.d.mts.map +0 -1
  51. package/dist/utils/caip.mjs +0 -20
  52. package/dist/utils/caip.mjs.map +0 -1
package/CHANGELOG.md CHANGED
@@ -9,19 +9,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ### Added
11
11
 
12
- - Add stage-gated ingestion of the Snaps → AssetsController migration networks (Solana, Stellar, Tron) ([#9647](https://github.com/MetaMask/core/pull/9647))
13
- - `AssetsController` and `BackendWebsocketDataSource` now resolve a per-network migration stage from `RemoteFeatureFlagController` state (via `RemoteFeatureFlagController:getState`) using the `networkAssetsSnapsMigrationSolana`, `networkAssetsSnapsMigrationStellar`, and `networkAssetsSnapsMigrationTron` flags. Migration networks are only ingested and surfaced as active chains from `SnapsAssetsMigrationStage.ReadAssetsControllerWithFallback` onward, and left to the Snap when the stage is `Off` (the fail-safe when the flag is missing). Non-migration namespaces (e.g. `eip155`) are never gated.
14
-
15
12
  - `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))
16
13
 
17
14
  ### Changed
18
15
 
16
+ - 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))
17
+ - 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.
18
+ - 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).
19
+ - 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`.
20
+ - `AggregatedBalanceSelector` is nested under an `AggregatedBalance` parent span via `parentContext`.
19
21
  - Bump `@metamask/keyring-api` from `^23.5.0` to `^23.7.0` ([#9676](https://github.com/MetaMask/core/pull/9676))
20
22
  - Bump `@metamask/keyring-internal-api` from `^11.0.1` to `^11.0.2` ([#9676](https://github.com/MetaMask/core/pull/9676))
21
23
  - Bump `@metamask/keyring-snap-client` from `^9.2.0` to `^9.2.1` ([#9676](https://github.com/MetaMask/core/pull/9676))
22
24
 
23
25
  ### Fixed
24
26
 
27
+ - `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))
25
28
  - `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))
26
29
 
27
30
  ## [11.2.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);
@@ -456,7 +462,11 @@ class AssetsController extends base_controller_1.BaseController {
456
462
  customAssets.push(...accountCustomAssets);
457
463
  }
458
464
  if (options?.forceUpdate) {
459
- 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");
460
470
  const request = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_buildDataRequest).call(this, accounts, chainIds, {
461
471
  assetTypes,
462
472
  dataTypes,
@@ -490,59 +500,101 @@ class AssetsController extends base_controller_1.BaseController {
490
500
  ]),
491
501
  ]
492
502
  : [__classPrivateFieldGet(this, _AssetsController_stakedBalanceDataSource, "f"), __classPrivateFieldGet(this, _AssetsController_detectionMiddleware, "f")];
493
- const { response, durationByDataSource } = await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, fastSources, request);
494
- await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, {
495
- ...response,
496
- updateMode: 'merge',
497
- 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
+ },
498
556
  });
499
- // Background pipeline: snap and RPC run in parallel after the fast path
500
- // commits to state. Their balances are merged together before detection.
501
- // Token + price enrichment matches the pre-split behavior: only when basic
502
- // 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.
503
561
  const slowPipelineChainIds = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSlowPipelineChainIds).call(this, chainIds, response);
504
562
  if (slowPipelineChainIds.length > 0) {
505
563
  const slowSources = __classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)
506
564
  ? [__classPrivateFieldGet(this, _AssetsController_snapDataSource, "f"), __classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f")]
507
565
  : [__classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f")];
508
566
  const slowRequest = { ...request, chainIds: slowPipelineChainIds };
509
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, [
510
- (0, ParallelMiddleware_js_1.createParallelBalanceMiddleware)(slowSources),
511
- __classPrivateFieldGet(this, _AssetsController_detectionMiddleware, "f"),
512
- ...(__classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)
513
- ? [
514
- (0, ParallelMiddleware_js_1.createParallelMiddleware)([
515
- __classPrivateFieldGet(this, _AssetsController_tokenDataSource, "f"),
516
- __classPrivateFieldGet(this, _AssetsController_priceDataSource, "f"),
517
- ]),
518
- ]
519
- : []),
520
- ], slowRequest)
521
- .then(({ response: slowResponse }) => __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, { ...slowResponse, updateMode: 'merge' }))
522
- .catch((error) => log('Background pipeline failed', { error }));
523
- }
524
- const durationMs = performance.now() - startTime;
525
- // Emit trace for every full fetch (Assets Health dashboard)
526
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_emitTrace).call(this, TRACE_FULL_FETCH, {
527
- duration_ms: durationMs,
528
- chain_count: chainIds.length,
529
- account_count: accounts.length,
530
- basic_functionality: __classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this),
531
- asset_count: response.assetsBalance
532
- ? Object.values(response.assetsBalance).reduce((sum, acct) => sum + Object.keys(acct).length, 0)
533
- : 0,
534
- price_count: response.assetsPrice
535
- ? Object.keys(response.assetsPrice).length
536
- : 0,
537
- ...durationByDataSource,
538
- });
539
- if (!__classPrivateFieldGet(this, _AssetsController_firstInitFetchReported, "f")) {
540
- __classPrivateFieldSet(this, _AssetsController_firstInitFetchReported, true, "f");
541
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_emitTrace).call(this, TRACE_FIRST_INIT_FETCH, {
542
- duration_ms: durationMs,
543
- chain_ids: JSON.stringify(chainIds),
544
- ...durationByDataSource,
545
- });
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 }));
546
598
  }
547
599
  }
548
600
  const result = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getAssetsFromState).call(this, accounts, chainIds, assetTypes);
@@ -922,57 +974,87 @@ class AssetsController extends base_controller_1.BaseController {
922
974
  * @param request - Optional original request for context when enriching
923
975
  */
924
976
  async handleAssetsUpdate(response, sourceId, request) {
925
- const updateStart = performance.now();
926
977
  log('Assets updated from data source', {
927
978
  sourceId,
928
979
  hasBalance: Boolean(response.assetsBalance),
929
980
  hasPrice: Boolean(response.assetsPrice),
930
981
  });
931
- const resolvedRequest = request ?? {
932
- accountsWithSupportedChains: [],
933
- chainIds: [],
934
- dataTypes: ['balance', 'metadata', 'price'],
935
- };
936
- // RPC-only mode (basic functionality off): never run token/price APIs. Strip
937
- // those data types so downstream middleware cannot treat them as requested.
938
- const pipelineRequest = __classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)
939
- ? resolvedRequest
940
- : {
941
- ...resolvedRequest,
942
- dataTypes: resolvedRequest.dataTypes.filter((dt) => dt !== 'metadata' && dt !== 'price'),
943
- };
944
- // Graduate custom assets only when AccountsAPI / Websocket reports them.
945
- // RPC already fetches custom assets on purpose, and Snap handles non-EVM
946
- // chains the rule does not apply to, so skip the middleware for those.
947
- const shouldGraduateCustomAssets = sourceId === 'AccountsApiDataSource' ||
948
- sourceId === 'BackendWebsocketDataSource' ||
949
- sourceId === 'AccountActivityService';
950
- const enrichmentSources = [
951
- ...(shouldGraduateCustomAssets
952
- ? [__classPrivateFieldGet(this, _AssetsController_customAssetGraduationMiddleware, "f")]
953
- : []),
954
- __classPrivateFieldGet(this, _AssetsController_detectionMiddleware, "f"),
955
- ];
956
- if (__classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)) {
957
- enrichmentSources.push((0, ParallelMiddleware_js_1.createParallelMiddleware)([
958
- __classPrivateFieldGet(this, _AssetsController_tokenDataSource, "f"),
959
- __classPrivateFieldGet(this, _AssetsController_priceDataSource, "f"),
960
- ]));
961
- }
962
- const { response: enrichedResponse } = await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, enrichmentSources, pipelineRequest, response);
963
- await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, {
964
- ...enrichedResponse,
965
- replaceCoveredChainBalances: response.replaceCoveredChainBalances,
966
- });
967
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_emitTrace).call(this, TRACE_UPDATE_PIPELINE, {
968
- source: sourceId,
969
- duration_ms: performance.now() - updateStart,
970
- has_balance: Boolean(response.assetsBalance),
971
- has_price: Boolean(response.assetsPrice),
972
- has_metadata: Boolean(enrichedResponse.assetsInfo),
973
- balance_account_count: response.assetsBalance
974
- ? Object.keys(response.assetsBalance).length
975
- : 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
+ },
976
1058
  });
977
1059
  }
978
1060
  // ============================================================================
@@ -1012,16 +1094,7 @@ class AssetsController extends base_controller_1.BaseController {
1012
1094
  }
1013
1095
  }
1014
1096
  exports.AssetsController = AssetsController;
1015
- _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 = {
1016
- controller: 'AssetsController',
1017
- }) {
1018
- if (!__classPrivateFieldGet(this, _AssetsController_trace, "f")) {
1019
- return;
1020
- }
1021
- __classPrivateFieldGet(this, _AssetsController_trace, "f").call(this, { name, data, tags }, () => undefined)?.catch(() => {
1022
- // Telemetry failure must not break.
1023
- });
1024
- }, _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() {
1025
1098
  if (!__classPrivateFieldGet(this, _AssetsController_trace, "f") || __classPrivateFieldGet(this, _AssetsController_stateSizeReported, "f")) {
1026
1099
  return;
1027
1100
  }
@@ -1049,14 +1122,20 @@ _AssetsController_isEnabled = new WeakMap(), _AssetsController_isBasicFunctional
1049
1122
  customAssetEntries += ids.length;
1050
1123
  }
1051
1124
  }
1052
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_emitTrace).call(this, TRACE_STATE_SIZE, {
1053
- balance_entries: balanceEntries,
1054
- balance_accounts: Object.keys(balances).length,
1055
- unique_asset_count: uniqueAssets.size,
1056
- network_count: uniqueNetworks.size,
1057
- metadata_entries: Object.keys(assetsInfo).length,
1058
- price_entries: Object.keys(assetsPrice).length,
1059
- 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
+ },
1060
1139
  });
1061
1140
  }, _AssetsController_getSelectedAccounts = function _AssetsController_getSelectedAccounts() {
1062
1141
  const accounts = this.messenger.call('AccountTreeController:getAccountsFromSelectedAccountGroup');
@@ -1381,12 +1460,16 @@ async function _AssetsController_runStartupRefresh(accounts) {
1381
1460
  * Execute middlewares with request/response context.
1382
1461
  * Returns response and exclusive duration per source (sum ≈ wall time).
1383
1462
  *
1384
- * @param sources - Data sources or middlewares with getName() and assetsMiddleware (executed in order).
1385
- * @param request - The data request.
1386
- * @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.
1387
1469
  * @returns Response and durationByDataSource (exclusive ms per source name).
1388
1470
  */
1389
- async function _AssetsController_executeMiddlewares(sources, request, initialResponse = {}) {
1471
+ async function _AssetsController_executeMiddlewares(params) {
1472
+ const { sources, request, initialResponse = {}, parentContext, trace, } = params;
1390
1473
  const names = sources.map((source) => source.getName());
1391
1474
  const middlewares = sources.map((source) => source.assetsMiddleware);
1392
1475
  const inclusive = [];
@@ -1430,13 +1513,24 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1430
1513
  durationByDataSource[key] = ms;
1431
1514
  }
1432
1515
  }
1433
- // 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).
1434
1518
  for (const [sourceName, durationMs] of Object.entries(durationByDataSource)) {
1435
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_emitTrace).call(this, TRACE_DATA_SOURCE_TIMING, {
1436
- source: sourceName,
1437
- duration_ms: durationMs,
1438
- chain_count: request.chainIds.length,
1439
- 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,
1440
1534
  });
1441
1535
  }
1442
1536
  // Failed middlewares: Issues (optional) + perf/Dashboard spans
@@ -1453,14 +1547,20 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1453
1547
  catch {
1454
1548
  // Never let telemetry throw.
1455
1549
  }
1456
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_emitTrace).call(this, TRACE_DATA_SOURCE_ERROR, {
1457
- failed_sources: failedSources,
1458
- error_count: middlewareErrors.length,
1459
- chain_count: request.chainIds.length,
1460
- }, {
1461
- controller: 'AssetsController',
1462
- severity: 'error',
1463
- 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,
1464
1564
  });
1465
1565
  }
1466
1566
  return { response: result.response, durationByDataSource };
@@ -2082,9 +2182,13 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
2082
2182
  };
2083
2183
  source.subscribe(subscribeReq).catch((error) => {
2084
2184
  console.error(`[AssetsController] Failed to subscribe to '${sourceId}':`, error);
2085
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_emitTrace).call(this, TRACE_SUBSCRIPTION_ERROR, {
2086
- source: sourceId,
2087
- 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
+ },
2088
2192
  });
2089
2193
  });
2090
2194
  // Track subscription