@metamask-previews/assets-controller 2.0.2-preview-1e855a9f5 → 2.0.2-preview-702bd3940

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 (90) hide show
  1. package/CHANGELOG.md +19 -1
  2. package/dist/AssetsController-method-action-types.cjs.map +1 -1
  3. package/dist/AssetsController-method-action-types.d.cts +5 -0
  4. package/dist/AssetsController-method-action-types.d.cts.map +1 -1
  5. package/dist/AssetsController-method-action-types.d.mts +5 -0
  6. package/dist/AssetsController-method-action-types.d.mts.map +1 -1
  7. package/dist/AssetsController-method-action-types.mjs.map +1 -1
  8. package/dist/AssetsController.cjs +214 -36
  9. package/dist/AssetsController.cjs.map +1 -1
  10. package/dist/AssetsController.d.cts +40 -6
  11. package/dist/AssetsController.d.cts.map +1 -1
  12. package/dist/AssetsController.d.mts +40 -6
  13. package/dist/AssetsController.d.mts.map +1 -1
  14. package/dist/AssetsController.mjs +214 -36
  15. package/dist/AssetsController.mjs.map +1 -1
  16. package/dist/data-sources/AccountsApiDataSource.cjs +1 -0
  17. package/dist/data-sources/AccountsApiDataSource.cjs.map +1 -1
  18. package/dist/data-sources/AccountsApiDataSource.d.cts.map +1 -1
  19. package/dist/data-sources/AccountsApiDataSource.d.mts.map +1 -1
  20. package/dist/data-sources/AccountsApiDataSource.mjs +1 -0
  21. package/dist/data-sources/AccountsApiDataSource.mjs.map +1 -1
  22. package/dist/data-sources/BackendWebsocketDataSource.cjs +16 -4
  23. package/dist/data-sources/BackendWebsocketDataSource.cjs.map +1 -1
  24. package/dist/data-sources/BackendWebsocketDataSource.d.cts.map +1 -1
  25. package/dist/data-sources/BackendWebsocketDataSource.d.mts.map +1 -1
  26. package/dist/data-sources/BackendWebsocketDataSource.mjs +13 -4
  27. package/dist/data-sources/BackendWebsocketDataSource.mjs.map +1 -1
  28. package/dist/data-sources/PriceDataSource.cjs +20 -14
  29. package/dist/data-sources/PriceDataSource.cjs.map +1 -1
  30. package/dist/data-sources/PriceDataSource.d.cts +2 -2
  31. package/dist/data-sources/PriceDataSource.d.cts.map +1 -1
  32. package/dist/data-sources/PriceDataSource.d.mts +2 -2
  33. package/dist/data-sources/PriceDataSource.d.mts.map +1 -1
  34. package/dist/data-sources/PriceDataSource.mjs +20 -14
  35. package/dist/data-sources/PriceDataSource.mjs.map +1 -1
  36. package/dist/data-sources/RpcDataSource.cjs +2 -0
  37. package/dist/data-sources/RpcDataSource.cjs.map +1 -1
  38. package/dist/data-sources/RpcDataSource.d.cts.map +1 -1
  39. package/dist/data-sources/RpcDataSource.d.mts.map +1 -1
  40. package/dist/data-sources/RpcDataSource.mjs +2 -0
  41. package/dist/data-sources/RpcDataSource.mjs.map +1 -1
  42. package/dist/data-sources/SnapDataSource.cjs +3 -2
  43. package/dist/data-sources/SnapDataSource.cjs.map +1 -1
  44. package/dist/data-sources/SnapDataSource.d.cts.map +1 -1
  45. package/dist/data-sources/SnapDataSource.d.mts.map +1 -1
  46. package/dist/data-sources/SnapDataSource.mjs +3 -2
  47. package/dist/data-sources/SnapDataSource.mjs.map +1 -1
  48. package/dist/data-sources/TokenDataSource.cjs +1 -0
  49. package/dist/data-sources/TokenDataSource.cjs.map +1 -1
  50. package/dist/data-sources/TokenDataSource.d.cts.map +1 -1
  51. package/dist/data-sources/TokenDataSource.d.mts.map +1 -1
  52. package/dist/data-sources/TokenDataSource.mjs +1 -0
  53. package/dist/data-sources/TokenDataSource.mjs.map +1 -1
  54. package/dist/index.cjs.map +1 -1
  55. package/dist/index.d.cts +2 -1
  56. package/dist/index.d.cts.map +1 -1
  57. package/dist/index.d.mts +2 -1
  58. package/dist/index.d.mts.map +1 -1
  59. package/dist/index.mjs.map +1 -1
  60. package/dist/middlewares/DetectionMiddleware.cjs +44 -27
  61. package/dist/middlewares/DetectionMiddleware.cjs.map +1 -1
  62. package/dist/middlewares/DetectionMiddleware.d.cts +15 -9
  63. package/dist/middlewares/DetectionMiddleware.d.cts.map +1 -1
  64. package/dist/middlewares/DetectionMiddleware.d.mts +15 -9
  65. package/dist/middlewares/DetectionMiddleware.d.mts.map +1 -1
  66. package/dist/middlewares/DetectionMiddleware.mjs +44 -27
  67. package/dist/middlewares/DetectionMiddleware.mjs.map +1 -1
  68. package/dist/middlewares/ParallelMiddleware.cjs +216 -0
  69. package/dist/middlewares/ParallelMiddleware.cjs.map +1 -0
  70. package/dist/middlewares/ParallelMiddleware.d.cts +45 -0
  71. package/dist/middlewares/ParallelMiddleware.d.cts.map +1 -0
  72. package/dist/middlewares/ParallelMiddleware.d.mts +45 -0
  73. package/dist/middlewares/ParallelMiddleware.d.mts.map +1 -0
  74. package/dist/middlewares/ParallelMiddleware.mjs +214 -0
  75. package/dist/middlewares/ParallelMiddleware.mjs.map +1 -0
  76. package/dist/middlewares/index.cjs +5 -1
  77. package/dist/middlewares/index.cjs.map +1 -1
  78. package/dist/middlewares/index.d.cts +2 -0
  79. package/dist/middlewares/index.d.cts.map +1 -1
  80. package/dist/middlewares/index.d.mts +2 -0
  81. package/dist/middlewares/index.d.mts.map +1 -1
  82. package/dist/middlewares/index.mjs +1 -0
  83. package/dist/middlewares/index.mjs.map +1 -1
  84. package/dist/types.cjs.map +1 -1
  85. package/dist/types.d.cts +16 -0
  86. package/dist/types.d.cts.map +1 -1
  87. package/dist/types.d.mts +16 -0
  88. package/dist/types.d.mts.map +1 -1
  89. package/dist/types.mjs.map +1 -1
  90. package/package.json +6 -5
@@ -9,8 +9,9 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
9
9
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
10
10
  return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
11
11
  };
12
- var _AssetsController_instances, _AssetsController_isEnabled, _AssetsController_isBasicFunctionality, _AssetsController_defaultUpdateInterval, _AssetsController_trackMetaMetricsEvent, _AssetsController_firstInitFetchReported, _AssetsController_controllerMutex, _AssetsController_activeSubscriptions, _AssetsController_enabledChains, _AssetsController_selectedAccounts_get, _AssetsController_backendWebsocketDataSource, _AssetsController_accountsApiDataSource, _AssetsController_snapDataSource, _AssetsController_rpcDataSource, _AssetsController_stakedBalanceDataSource, _AssetsController_allBalanceDataSources_get, _AssetsController_priceDataSource, _AssetsController_detectionMiddleware, _AssetsController_tokenDataSource, _AssetsController_unsubscribeBasicFunctionality, _AssetsController_initializeState, _AssetsController_extractEnabledChains, _AssetsController_normalizeChainReference, _AssetsController_subscribeToEvents, _AssetsController_registerActionHandlers, _AssetsController_executeMiddlewares, _AssetsController_updateState, _AssetsController_getAssetsFromState, _AssetsController_tokenStandardToAssetType, _AssetsController_start, _AssetsController_stop, _AssetsController_subscribeAssets, _AssetsController_subscribeAssetsBalance, _AssetsController_subscribeStakedBalance, _AssetsController_buildChainToAccountsMap, _AssetsController_subscribeDataSource, _AssetsController_unsubscribeDataSource, _AssetsController_buildDataRequest, _AssetsController_getEnabledChainsForAccount, _AssetsController_handleAccountGroupChanged, _AssetsController_handleEnabledNetworksChanged;
12
+ var _AssetsController_instances, _AssetsController_isEnabled, _AssetsController_isBasicFunctionality, _AssetsController_defaultUpdateInterval, _AssetsController_trackMetaMetricsEvent, _AssetsController_firstInitFetchReported, _AssetsController_uiOpen, _AssetsController_keyringUnlocked, _AssetsController_controllerMutex, _AssetsController_activeSubscriptions, _AssetsController_enabledChains, _AssetsController_selectedAccounts_get, _AssetsController_backendWebsocketDataSource, _AssetsController_accountsApiDataSource, _AssetsController_snapDataSource, _AssetsController_rpcDataSource, _AssetsController_stakedBalanceDataSource, _AssetsController_allBalanceDataSources_get, _AssetsController_priceDataSource, _AssetsController_detectionMiddleware, _AssetsController_tokenDataSource, _AssetsController_unsubscribeBasicFunctionality, _AssetsController_initializeState, _AssetsController_extractEnabledChains, _AssetsController_normalizeChainReference, _AssetsController_subscribeToEvents, _AssetsController_updateActive, _AssetsController_registerActionHandlers, _AssetsController_executeMiddlewares, _AssetsController_resolveNativeAssetIds, _AssetsController_getNativeAssetIdsForEnabledChains, _AssetsController_getNativeAssetIdsForAccount, _AssetsController_ensureNativeBalancesDefaultZero, _AssetsController_updateState, _AssetsController_getAssetsFromState, _AssetsController_tokenStandardToAssetType, _AssetsController_start, _AssetsController_stop, _AssetsController_subscribeAssets, _AssetsController_subscribeAssetsBalance, _AssetsController_subscribeStakedBalance, _AssetsController_buildChainToAccountsMap, _AssetsController_subscribeDataSource, _AssetsController_unsubscribeDataSource, _AssetsController_buildDataRequest, _AssetsController_getEnabledChainsForAccount, _AssetsController_handleAccountGroupChanged, _AssetsController_handleEnabledNetworksChanged;
13
13
  import { BaseController } from "@metamask/base-controller";
14
+ import { clientControllerSelectors } from "@metamask/client-controller";
14
15
  import { isCaipChainId, isStrictHexString, parseCaipAssetType, parseCaipChainId } from "@metamask/utils";
15
16
  import { Mutex } from "async-mutex";
16
17
  import BigNumberJS from "bignumber.js";
@@ -25,6 +26,7 @@ import { StakedBalanceDataSource } from "./data-sources/StakedBalanceDataSource.
25
26
  import { TokenDataSource } from "./data-sources/TokenDataSource.mjs";
26
27
  import { projectLogger, createModuleLogger } from "./logger.mjs";
27
28
  import { DetectionMiddleware } from "./middlewares/DetectionMiddleware.mjs";
29
+ import { createParallelBalanceMiddleware, createParallelMiddleware } from "./middlewares/ParallelMiddleware.mjs";
28
30
  import { normalizeAssetId } from "./utils.mjs";
29
31
  // ============================================================================
30
32
  // CONTROLLER CONSTANTS
@@ -57,6 +59,7 @@ export function getDefaultAssetsControllerState() {
57
59
  assetsPrice: {},
58
60
  customAssets: {},
59
61
  assetPreferences: {},
62
+ selectedCurrency: 'usd',
60
63
  };
61
64
  }
62
65
  // ============================================================================
@@ -93,6 +96,12 @@ const stateMetadata = {
93
96
  includeInDebugSnapshot: false,
94
97
  usedInUi: true,
95
98
  },
99
+ selectedCurrency: {
100
+ persist: true,
101
+ includeInStateLogs: false,
102
+ includeInDebugSnapshot: false,
103
+ usedInUi: true,
104
+ },
96
105
  };
97
106
  // ============================================================================
98
107
  // HELPER FUNCTIONS
@@ -146,6 +155,9 @@ function normalizeResponse(response) {
146
155
  if (response.errors) {
147
156
  normalized.errors = { ...response.errors };
148
157
  }
158
+ if (response.updateMode) {
159
+ normalized.updateMode = response.updateMode;
160
+ }
149
161
  return normalized;
150
162
  }
151
163
  // ============================================================================
@@ -167,8 +179,10 @@ function normalizeResponse(response) {
167
179
  * based on which chains they support. When active chains change, the controller
168
180
  * dynamically adjusts subscriptions.
169
181
  *
170
- * 4. **Keyring Lifecycle**: Listens to KeyringController unlock/lock events to
171
- * start/stop subscriptions when the wallet is unlocked or locked.
182
+ * 4. **Client + Keyring Lifecycle**: Starts subscriptions only when both the UI is
183
+ * open (ClientController) and the wallet is unlocked (KeyringController).
184
+ * Stops when either the UI closes or the keyring locks. See client-controller
185
+ * README for the combined pattern.
172
186
  *
173
187
  * ## Architecture
174
188
  *
@@ -198,6 +212,10 @@ export class AssetsController extends BaseController {
198
212
  _AssetsController_trackMetaMetricsEvent.set(this, void 0);
199
213
  /** Whether we have already reported first init fetch for this session (reset on #stop). */
200
214
  _AssetsController_firstInitFetchReported.set(this, false);
215
+ /** Whether the client (UI) is open. Combined with #keyringUnlocked for #updateActive. */
216
+ _AssetsController_uiOpen.set(this, false);
217
+ /** Whether the keyring is unlocked. Combined with #uiOpen for #updateActive. */
218
+ _AssetsController_keyringUnlocked.set(this, false);
201
219
  _AssetsController_controllerMutex.set(this, new Mutex());
202
220
  /**
203
221
  * Active balance subscriptions keyed by account ID.
@@ -254,6 +272,7 @@ export class AssetsController extends BaseController {
254
272
  }), "f");
255
273
  __classPrivateFieldSet(this, _AssetsController_priceDataSource, new PriceDataSource({
256
274
  queryApiClient,
275
+ getSelectedCurrency: () => this.state.selectedCurrency,
257
276
  ...priceDataSourceConfig,
258
277
  }), "f");
259
278
  __classPrivateFieldSet(this, _AssetsController_detectionMiddleware, new DetectionMiddleware(), "f");
@@ -267,7 +286,7 @@ export class AssetsController extends BaseController {
267
286
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_initializeState).call(this);
268
287
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeToEvents).call(this);
269
288
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_registerActionHandlers).call(this);
270
- // Subscriptions start only on KeyringController:unlock -> #start(), not here.
289
+ // Subscriptions start only when both UI is open and keyring unlocked -> #updateActive().
271
290
  // Subscribe to basic-functionality changes after construction so a synchronous
272
291
  // onChange during subscribe cannot run before data sources are initialized.
273
292
  if (subscribeToBasicFunctionalityChange) {
@@ -318,6 +337,7 @@ export class AssetsController extends BaseController {
318
337
  this.getAssets(__classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get), {
319
338
  chainIds: addedEnabledChains,
320
339
  forceUpdate: true,
340
+ updateMode: 'merge',
321
341
  }).catch((error) => {
322
342
  log('Failed to fetch balance for added chains', { error });
323
343
  });
@@ -331,6 +351,9 @@ export class AssetsController extends BaseController {
331
351
  const chainIds = options?.chainIds ?? [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")];
332
352
  const assetTypes = options?.assetTypes ?? ['fungible'];
333
353
  const dataTypes = options?.dataTypes ?? ['balance', 'metadata', 'price'];
354
+ if (accounts.length === 0 || chainIds.length === 0) {
355
+ return __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getAssetsFromState).call(this, accounts, chainIds, assetTypes);
356
+ }
334
357
  // Collect custom assets for all requested accounts
335
358
  const customAssets = [];
336
359
  for (const account of accounts) {
@@ -344,16 +367,21 @@ export class AssetsController extends BaseController {
344
367
  dataTypes,
345
368
  customAssets: customAssets.length > 0 ? customAssets : undefined,
346
369
  forceUpdate: true,
370
+ assetsForPriceUpdate: options?.assetsForPriceUpdate,
347
371
  });
348
372
  const sources = __classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)
349
373
  ? [
350
- __classPrivateFieldGet(this, _AssetsController_accountsApiDataSource, "f"),
351
- __classPrivateFieldGet(this, _AssetsController_snapDataSource, "f"),
352
- __classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f"),
353
- __classPrivateFieldGet(this, _AssetsController_stakedBalanceDataSource, "f"),
374
+ createParallelBalanceMiddleware([
375
+ __classPrivateFieldGet(this, _AssetsController_accountsApiDataSource, "f"),
376
+ __classPrivateFieldGet(this, _AssetsController_snapDataSource, "f"),
377
+ __classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f"),
378
+ __classPrivateFieldGet(this, _AssetsController_stakedBalanceDataSource, "f"),
379
+ ]),
354
380
  __classPrivateFieldGet(this, _AssetsController_detectionMiddleware, "f"),
355
- __classPrivateFieldGet(this, _AssetsController_tokenDataSource, "f"),
356
- __classPrivateFieldGet(this, _AssetsController_priceDataSource, "f"),
381
+ createParallelMiddleware([
382
+ __classPrivateFieldGet(this, _AssetsController_tokenDataSource, "f"),
383
+ __classPrivateFieldGet(this, _AssetsController_priceDataSource, "f"),
384
+ ]),
357
385
  ]
358
386
  : [
359
387
  __classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f"),
@@ -361,7 +389,12 @@ export class AssetsController extends BaseController {
361
389
  __classPrivateFieldGet(this, _AssetsController_detectionMiddleware, "f"),
362
390
  ];
363
391
  const { response, durationByDataSource } = await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, sources, request);
364
- await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, response);
392
+ // Default to 'merge' when fetching a subset of chains so we don't wipe
393
+ // balances from chains that weren't included in this fetch.
394
+ const isPartialChainFetch = options?.chainIds !== undefined &&
395
+ options.chainIds.length < __classPrivateFieldGet(this, _AssetsController_enabledChains, "f").size;
396
+ const updateMode = options?.updateMode ?? (isPartialChainFetch ? 'merge' : 'full');
397
+ await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, { ...response, updateMode });
365
398
  if (__classPrivateFieldGet(this, _AssetsController_trackMetaMetricsEvent, "f") && !__classPrivateFieldGet(this, _AssetsController_firstInitFetchReported, "f")) {
366
399
  __classPrivateFieldSet(this, _AssetsController_firstInitFetchReported, true, "f");
367
400
  const durationMs = Date.now() - startTime;
@@ -372,7 +405,8 @@ export class AssetsController extends BaseController {
372
405
  });
373
406
  }
374
407
  }
375
- return __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getAssetsFromState).call(this, accounts, chainIds, assetTypes);
408
+ const result = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getAssetsFromState).call(this, accounts, chainIds, assetTypes);
409
+ return result;
376
410
  }
377
411
  async getAssetsBalance(accounts, options) {
378
412
  // Reuse getAssets with dataTypes: ['balance'] only
@@ -423,10 +457,15 @@ export class AssetsController extends BaseController {
423
457
  * Custom assets are included in subscription and fetch operations.
424
458
  * Adding a custom asset also unhides it if it was previously hidden.
425
459
  *
460
+ * When `pendingMetadata` is provided (e.g. from the extension's pending-tokens
461
+ * flow), the token metadata is persisted immediately into `assetsInfo` so the
462
+ * UI can render it without waiting for the next pipeline fetch.
463
+ *
426
464
  * @param accountId - The account ID to add the custom asset for.
427
465
  * @param assetId - The CAIP-19 asset ID to add.
466
+ * @param pendingMetadata - Optional token metadata from the UI (pendingTokens format).
428
467
  */
429
- async addCustomAsset(accountId, assetId) {
468
+ async addCustomAsset(accountId, assetId, pendingMetadata) {
430
469
  const normalizedAssetId = normalizeAssetId(assetId);
431
470
  log('Adding custom asset', { accountId, assetId: normalizedAssetId });
432
471
  this.update((state) => {
@@ -446,14 +485,37 @@ export class AssetsController extends BaseController {
446
485
  delete state.assetPreferences[normalizedAssetId];
447
486
  }
448
487
  }
488
+ // Persist metadata from the UI so the token is immediately renderable
489
+ if (pendingMetadata) {
490
+ const parsed = parseCaipAssetType(normalizedAssetId);
491
+ let tokenType = 'erc20';
492
+ if (parsed.assetNamespace === 'slip44') {
493
+ tokenType = 'native';
494
+ }
495
+ else if (parsed.assetNamespace === 'spl') {
496
+ tokenType = 'spl';
497
+ }
498
+ const assetMetadata = {
499
+ type: tokenType,
500
+ symbol: pendingMetadata.symbol,
501
+ name: pendingMetadata.name,
502
+ decimals: pendingMetadata.decimals,
503
+ image: pendingMetadata.iconUrl,
504
+ aggregators: pendingMetadata.aggregators,
505
+ occurrences: pendingMetadata.occurrences,
506
+ };
507
+ state.assetsInfo[normalizedAssetId] =
508
+ assetMetadata;
509
+ }
449
510
  });
450
- // Fetch data for the newly added custom asset
511
+ // Fetch data for the newly added custom asset (merge to preserve other chains)
451
512
  const account = __classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get).find((a) => a.id === accountId);
452
513
  if (account) {
453
514
  const chainId = extractChainId(normalizedAssetId);
454
515
  await this.getAssets([account], {
455
516
  chainIds: [chainId],
456
517
  forceUpdate: true,
518
+ updateMode: 'merge',
457
519
  });
458
520
  }
459
521
  }
@@ -524,6 +586,34 @@ export class AssetsController extends BaseController {
524
586
  });
525
587
  }
526
588
  // ============================================================================
589
+ // CURRENT CURRENCY MANAGEMENT
590
+ // ============================================================================
591
+ /**
592
+ * Set the current currency.
593
+ *
594
+ * @param selectedCurrency - The ISO 4217 currency code to set.
595
+ */
596
+ setSelectedCurrency(selectedCurrency) {
597
+ const previousCurrency = this.state.selectedCurrency;
598
+ if (previousCurrency === selectedCurrency) {
599
+ return;
600
+ }
601
+ this.update((state) => {
602
+ state.selectedCurrency = selectedCurrency;
603
+ });
604
+ log('Current currency changed', {
605
+ previousCurrency,
606
+ selectedCurrency,
607
+ });
608
+ this.getAssets(__classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get), {
609
+ forceUpdate: true,
610
+ dataTypes: ['price'],
611
+ assetsForPriceUpdate: Object.values(this.state.assetsBalance).flatMap((balances) => Object.keys(balances)),
612
+ }).catch((error) => {
613
+ log('Failed to fetch asset prices after current currency change', error);
614
+ });
615
+ }
616
+ // ============================================================================
527
617
  // SUBSCRIPTIONS
528
618
  // ============================================================================
529
619
  /**
@@ -601,9 +691,15 @@ export class AssetsController extends BaseController {
601
691
  hasBalance: Boolean(response.assetsBalance),
602
692
  hasPrice: Boolean(response.assetsPrice),
603
693
  });
604
- // Run through enrichment middlewares (Event Stack: Detection Token Price)
694
+ // Run through enrichment middlewares (Detection, then Token + Price in parallel)
605
695
  // Include 'metadata' in dataTypes so TokenDataSource runs to enrich detected assets
606
- const { response: enrichedResponse } = await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, [__classPrivateFieldGet(this, _AssetsController_detectionMiddleware, "f"), __classPrivateFieldGet(this, _AssetsController_tokenDataSource, "f"), __classPrivateFieldGet(this, _AssetsController_priceDataSource, "f")], request ?? {
696
+ const { response: enrichedResponse } = await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, [
697
+ __classPrivateFieldGet(this, _AssetsController_detectionMiddleware, "f"),
698
+ createParallelMiddleware([
699
+ __classPrivateFieldGet(this, _AssetsController_tokenDataSource, "f"),
700
+ __classPrivateFieldGet(this, _AssetsController_priceDataSource, "f"),
701
+ ]),
702
+ ], request ?? {
607
703
  accountsWithSupportedChains: [],
608
704
  chainIds: [],
609
705
  dataTypes: ['balance', 'metadata', 'price'],
@@ -642,7 +738,7 @@ export class AssetsController extends BaseController {
642
738
  this.messenger.unregisterActionHandler('AssetsController:unhideAsset');
643
739
  }
644
740
  }
645
- _AssetsController_isEnabled = new WeakMap(), _AssetsController_isBasicFunctionality = new WeakMap(), _AssetsController_defaultUpdateInterval = new WeakMap(), _AssetsController_trackMetaMetricsEvent = new WeakMap(), _AssetsController_firstInitFetchReported = new WeakMap(), _AssetsController_controllerMutex = new WeakMap(), _AssetsController_activeSubscriptions = new WeakMap(), _AssetsController_enabledChains = 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_tokenDataSource = new WeakMap(), _AssetsController_unsubscribeBasicFunctionality = new WeakMap(), _AssetsController_instances = new WeakSet(), _AssetsController_selectedAccounts_get = function _AssetsController_selectedAccounts_get() {
741
+ _AssetsController_isEnabled = new WeakMap(), _AssetsController_isBasicFunctionality = new WeakMap(), _AssetsController_defaultUpdateInterval = new WeakMap(), _AssetsController_trackMetaMetricsEvent = new WeakMap(), _AssetsController_firstInitFetchReported = new WeakMap(), _AssetsController_uiOpen = new WeakMap(), _AssetsController_keyringUnlocked = new WeakMap(), _AssetsController_controllerMutex = new WeakMap(), _AssetsController_activeSubscriptions = new WeakMap(), _AssetsController_enabledChains = 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_tokenDataSource = new WeakMap(), _AssetsController_unsubscribeBasicFunctionality = new WeakMap(), _AssetsController_instances = new WeakSet(), _AssetsController_selectedAccounts_get = function _AssetsController_selectedAccounts_get() {
646
742
  return this.messenger.call('AccountTreeController:getAccountsFromSelectedAccountGroup');
647
743
  }, _AssetsController_allBalanceDataSources_get = function _AssetsController_allBalanceDataSources_get() {
648
744
  return [
@@ -692,9 +788,27 @@ _AssetsController_isEnabled = new WeakMap(), _AssetsController_isBasicFunctional
692
788
  this.messenger.subscribe('NetworkEnablementController:stateChange', ({ enabledNetworkMap }) => {
693
789
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_handleEnabledNetworksChanged).call(this, enabledNetworkMap).catch(console.error);
694
790
  });
695
- // Keyring lifecycle: start when unlocked, stop when locked
696
- this.messenger.subscribe('KeyringController:unlock', () => __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_start).call(this));
697
- this.messenger.subscribe('KeyringController:lock', () => __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_stop).call(this));
791
+ // Client + Keyring lifecycle: only run when UI is open AND keyring is unlocked
792
+ this.messenger.subscribe('ClientController:stateChange', (isUiOpen) => {
793
+ __classPrivateFieldSet(this, _AssetsController_uiOpen, isUiOpen, "f");
794
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateActive).call(this);
795
+ }, clientControllerSelectors.selectIsUiOpen);
796
+ this.messenger.subscribe('KeyringController:unlock', () => {
797
+ __classPrivateFieldSet(this, _AssetsController_keyringUnlocked, true, "f");
798
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateActive).call(this);
799
+ });
800
+ this.messenger.subscribe('KeyringController:lock', () => {
801
+ __classPrivateFieldSet(this, _AssetsController_keyringUnlocked, false, "f");
802
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateActive).call(this);
803
+ });
804
+ }, _AssetsController_updateActive = function _AssetsController_updateActive() {
805
+ const shouldRun = __classPrivateFieldGet(this, _AssetsController_uiOpen, "f") && __classPrivateFieldGet(this, _AssetsController_keyringUnlocked, "f");
806
+ if (shouldRun) {
807
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_start).call(this);
808
+ }
809
+ else {
810
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_stop).call(this);
811
+ }
698
812
  }, _AssetsController_registerActionHandlers = function _AssetsController_registerActionHandlers() {
699
813
  this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
700
814
  }, _AssetsController_executeMiddlewares =
@@ -747,13 +861,46 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
747
861
  }
748
862
  }
749
863
  return { response: result.response, durationByDataSource };
750
- }, _AssetsController_updateState =
751
- // ============================================================================
752
- // STATE MANAGEMENT
753
- // ============================================================================
754
- async function _AssetsController_updateState(response) {
755
- // Normalize asset IDs (checksum EVM addresses) before storing in state
864
+ }, _AssetsController_resolveNativeAssetIds = function _AssetsController_resolveNativeAssetIds(chains) {
865
+ const { nativeAssetIdentifiers } = this.messenger.call('NetworkEnablementController:getState');
866
+ const ids = [];
867
+ for (const chainId of chains) {
868
+ const nativeId = nativeAssetIdentifiers?.[chainId];
869
+ if (nativeId) {
870
+ ids.push(nativeId);
871
+ }
872
+ }
873
+ return ids;
874
+ }, _AssetsController_getNativeAssetIdsForEnabledChains = function _AssetsController_getNativeAssetIdsForEnabledChains() {
875
+ return __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_resolveNativeAssetIds).call(this, __classPrivateFieldGet(this, _AssetsController_enabledChains, "f"));
876
+ }, _AssetsController_getNativeAssetIdsForAccount = function _AssetsController_getNativeAssetIdsForAccount(account) {
877
+ return __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_resolveNativeAssetIds).call(this, __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getEnabledChainsForAccount).call(this, account));
878
+ }, _AssetsController_ensureNativeBalancesDefaultZero = function _AssetsController_ensureNativeBalancesDefaultZero() {
879
+ const accounts = __classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get);
880
+ if (accounts.length === 0) {
881
+ return;
882
+ }
883
+ this.update((state) => {
884
+ const balances = state.assetsBalance;
885
+ for (const account of accounts) {
886
+ const accountId = account.id;
887
+ const nativeAssetIds = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getNativeAssetIdsForAccount).call(this, account);
888
+ if (nativeAssetIds.length === 0) {
889
+ continue;
890
+ }
891
+ if (!balances[accountId]) {
892
+ balances[accountId] = {};
893
+ }
894
+ for (const nativeAssetId of nativeAssetIds) {
895
+ if (!(nativeAssetId in balances[accountId])) {
896
+ balances[accountId][nativeAssetId] = { amount: '0' };
897
+ }
898
+ }
899
+ }
900
+ });
901
+ }, _AssetsController_updateState = async function _AssetsController_updateState(response) {
756
902
  const normalizedResponse = normalizeResponse(response);
903
+ const mode = normalizedResponse.updateMode ?? 'merge';
757
904
  const releaseLock = await __classPrivateFieldGet(this, _AssetsController_controllerMutex, "f").acquire();
758
905
  try {
759
906
  const previousState = this.state;
@@ -779,16 +926,43 @@ async function _AssetsController_updateState(response) {
779
926
  if (normalizedResponse.assetsBalance) {
780
927
  for (const [accountId, accountBalances] of Object.entries(normalizedResponse.assetsBalance)) {
781
928
  const previousBalances = previousState.assetsBalance[accountId] ?? {};
782
- if (!balances[accountId]) {
783
- balances[accountId] = {};
929
+ const customAssetIds = state.customAssets[accountId] ?? [];
930
+ // Full: response is authoritative; preserve custom assets not in response.
931
+ // Merge: response overlays previous balances.
932
+ // Callers that fetch partial data (e.g. newly added chains) must set updateMode: 'merge'.
933
+ const effective = mode === 'merge'
934
+ ? { ...previousBalances, ...accountBalances }
935
+ : (() => {
936
+ const next = {
937
+ ...accountBalances,
938
+ };
939
+ for (const customId of customAssetIds) {
940
+ if (!(customId in next)) {
941
+ const prev = previousBalances[customId];
942
+ next[customId] =
943
+ prev ?? { amount: '0' };
944
+ }
945
+ }
946
+ return next;
947
+ })();
948
+ // Ensure native tokens have an entry (0 if missing) for chains this account supports
949
+ const account = __classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get).find((a) => a.id === accountId);
950
+ const nativeAssetIdsForAccount = account
951
+ ? __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getNativeAssetIdsForAccount).call(this, account)
952
+ : __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getNativeAssetIdsForEnabledChains).call(this);
953
+ for (const nativeAssetId of nativeAssetIdsForAccount) {
954
+ if (!(nativeAssetId in effective)) {
955
+ effective[nativeAssetId] = { amount: '0' };
956
+ }
784
957
  }
785
- for (const [assetId, balance] of Object.entries(accountBalances)) {
958
+ for (const [assetId, balance] of Object.entries(effective)) {
786
959
  const previousBalance = previousBalances[assetId];
787
- const balanceData = balance;
788
- const newAmount = balanceData.amount;
960
+ const newAmount = balance.amount;
789
961
  const oldAmount = previousBalance?.amount;
790
- // Track if balance actually changed
791
- if (oldAmount !== newAmount) {
962
+ const isNewDefaultNativeZero = oldAmount === undefined &&
963
+ newAmount === '0' &&
964
+ nativeAssetIdsForAccount.includes(assetId);
965
+ if (oldAmount !== newAmount && !isNewDefaultNativeZero) {
792
966
  changedBalances.push({
793
967
  accountId,
794
968
  assetId,
@@ -797,7 +971,7 @@ async function _AssetsController_updateState(response) {
797
971
  });
798
972
  }
799
973
  }
800
- Object.assign(balances[accountId], accountBalances);
974
+ balances[accountId] = effective;
801
975
  }
802
976
  }
803
977
  // Update prices in state
@@ -941,6 +1115,7 @@ async function _AssetsController_updateState(response) {
941
1115
  enabledChainCount: __classPrivateFieldGet(this, _AssetsController_enabledChains, "f").size,
942
1116
  });
943
1117
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1118
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
944
1119
  this.getAssets(__classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get), {
945
1120
  chainIds: [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")],
946
1121
  forceUpdate: true,
@@ -975,7 +1150,7 @@ async function _AssetsController_updateState(response) {
975
1150
  }
976
1151
  __classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").clear();
977
1152
  }, _AssetsController_subscribeAssets = function _AssetsController_subscribeAssets() {
978
- if (__classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get).length === 0) {
1153
+ if (__classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get).length === 0 || __classPrivateFieldGet(this, _AssetsController_enabledChains, "f").size === 0) {
979
1154
  return;
980
1155
  }
981
1156
  // Subscribe to balance updates (batched by data source)
@@ -1148,6 +1323,7 @@ async function _AssetsController_handleAccountGroupChanged() {
1148
1323
  forceUpdate: true,
1149
1324
  });
1150
1325
  }
1326
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
1151
1327
  }, _AssetsController_handleEnabledNetworksChanged = async function _AssetsController_handleEnabledNetworksChanged(enabledNetworkMap) {
1152
1328
  const previousChains = __classPrivateFieldGet(this, _AssetsController_enabledChains, "f");
1153
1329
  __classPrivateFieldSet(this, _AssetsController_enabledChains, __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_extractEnabledChains).call(this, enabledNetworkMap), "f");
@@ -1176,12 +1352,14 @@ async function _AssetsController_handleAccountGroupChanged() {
1176
1352
  // The data will simply not be updated until the network is re-enabled.
1177
1353
  // Refresh subscriptions for new chain set
1178
1354
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1179
- // Do one-time fetch for newly enabled chains
1355
+ // Do one-time fetch for newly enabled chains; merge so we keep existing chain balances
1180
1356
  if (addedChains.length > 0 && __classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get).length > 0) {
1181
1357
  await this.getAssets(__classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get), {
1182
1358
  chainIds: addedChains,
1183
1359
  forceUpdate: true,
1360
+ updateMode: 'merge',
1184
1361
  });
1185
1362
  }
1363
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
1186
1364
  };
1187
1365
  //# sourceMappingURL=AssetsController.mjs.map