@metamask-previews/assets-controller 2.0.2-preview-09603a559 → 2.1.0-preview-b0148922a

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 (40) hide show
  1. package/CHANGELOG.md +8 -1
  2. package/dist/AssetsController.cjs +120 -68
  3. package/dist/AssetsController.cjs.map +1 -1
  4. package/dist/AssetsController.d.cts +7 -12
  5. package/dist/AssetsController.d.cts.map +1 -1
  6. package/dist/AssetsController.d.mts +7 -12
  7. package/dist/AssetsController.d.mts.map +1 -1
  8. package/dist/AssetsController.mjs +120 -68
  9. package/dist/AssetsController.mjs.map +1 -1
  10. package/dist/data-sources/AbstractDataSource.cjs.map +1 -1
  11. package/dist/data-sources/AbstractDataSource.d.cts +3 -3
  12. package/dist/data-sources/AbstractDataSource.d.cts.map +1 -1
  13. package/dist/data-sources/AbstractDataSource.d.mts +3 -3
  14. package/dist/data-sources/AbstractDataSource.d.mts.map +1 -1
  15. package/dist/data-sources/AbstractDataSource.mjs.map +1 -1
  16. package/dist/data-sources/BackendWebsocketDataSource.cjs +17 -5
  17. package/dist/data-sources/BackendWebsocketDataSource.cjs.map +1 -1
  18. package/dist/data-sources/BackendWebsocketDataSource.d.cts +2 -2
  19. package/dist/data-sources/BackendWebsocketDataSource.d.cts.map +1 -1
  20. package/dist/data-sources/BackendWebsocketDataSource.d.mts +2 -2
  21. package/dist/data-sources/BackendWebsocketDataSource.d.mts.map +1 -1
  22. package/dist/data-sources/BackendWebsocketDataSource.mjs +14 -5
  23. package/dist/data-sources/BackendWebsocketDataSource.mjs.map +1 -1
  24. package/dist/data-sources/RpcDataSource.cjs +23 -6
  25. package/dist/data-sources/RpcDataSource.cjs.map +1 -1
  26. package/dist/data-sources/RpcDataSource.d.cts.map +1 -1
  27. package/dist/data-sources/RpcDataSource.d.mts.map +1 -1
  28. package/dist/data-sources/RpcDataSource.mjs +23 -6
  29. package/dist/data-sources/RpcDataSource.mjs.map +1 -1
  30. package/dist/data-sources/StakedBalanceDataSource.cjs +41 -3
  31. package/dist/data-sources/StakedBalanceDataSource.cjs.map +1 -1
  32. package/dist/data-sources/StakedBalanceDataSource.d.cts.map +1 -1
  33. package/dist/data-sources/StakedBalanceDataSource.d.mts.map +1 -1
  34. package/dist/data-sources/StakedBalanceDataSource.mjs +41 -3
  35. package/dist/data-sources/StakedBalanceDataSource.mjs.map +1 -1
  36. package/dist/data-sources/evm-rpc-services/services/TokenDetector.cjs.map +1 -1
  37. package/dist/data-sources/evm-rpc-services/services/TokenDetector.d.cts.map +1 -1
  38. package/dist/data-sources/evm-rpc-services/services/TokenDetector.d.mts.map +1 -1
  39. package/dist/data-sources/evm-rpc-services/services/TokenDetector.mjs.map +1 -1
  40. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.1.0]
11
+
10
12
  ### Added
11
13
 
12
14
  - Add `PendingTokenMetadata` type and optional `pendingMetadata` parameter to `addCustomAsset(accountId, assetId, pendingMetadata?)`. When provided (e.g. from the extension's pending-tokens flow), token metadata is written to `assetsInfo` immediately so the UI can render the token without waiting for the pipeline ([#8021](https://github.com/MetaMask/core/pull/8021))
@@ -24,6 +26,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
24
26
 
25
27
  ### Fixed
26
28
 
29
+ - Avoid unnecessary price and token API requests when data sources report balance-only updates. The controller now forwards the optional `request` from the subscribe callback into `handleAssetsUpdate`; when a data source calls `onAssetsUpdate(response, request)` with `request.dataTypes: ['balance']` (e.g. RpcDataSource balance polling, StakedBalanceDataSource), the controller skips Token and Price enrichment so the price API is not called. Previously every update triggered enrichment and could reset or overwrite existing state ([#8043](https://github.com/MetaMask/core/pull/8043))
30
+ - Default `assetsBalance` to `0` for native tokens of each account's supported chains using `NetworkEnablementController.nativeAssetIdentifiers`, so native entries are always present in state even before data sources respond ([#8036](https://github.com/MetaMask/core/pull/8036))
31
+ - Auto-select `'merge'` update mode in `getAssets` when `chainIds` is a subset of enabled chains, preventing partial-chain fetches (e.g. after a swap, adding a custom asset, or data-source chain changes) from wiping balances of other chains ([#8036](https://github.com/MetaMask/core/pull/8036))
32
+ - Convert WebSocket balance updates in `BackendWebsocketDataSource` from raw smallest-units to human-readable amounts using asset decimals (same behavior as RPC/Accounts API), so `assetsBalance` remains consistent across data sources ([#8032](https://github.com/MetaMask/core/pull/8032))
27
33
  - Include all assets from balance and each account's custom assets from state in `detectedAssets`, so prices and metadata are fetched for existing assets and custom tokens (previously only assets without metadata were included, so existing assets did not get prices) ([#8021](https://github.com/MetaMask/core/pull/8021))
28
34
  - Request `includeAggregators: true` when fetching token metadata from the v3 assets API so aggregator data is returned and stored in `assetsInfo` ([#8021](https://github.com/MetaMask/core/pull/8021))
29
35
 
@@ -122,7 +128,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
122
128
  - Refactor `RpcDataSource` to delegate polling to `BalanceFetcher` and `TokenDetector` services ([#7709](https://github.com/MetaMask/core/pull/7709))
123
129
  - Refactor `BalanceFetcher` and `TokenDetector` to extend `StaticIntervalPollingControllerOnly` for independent polling management ([#7709](https://github.com/MetaMask/core/pull/7709))
124
130
 
125
- [Unreleased]: https://github.com/MetaMask/core/compare/@metamask/assets-controller@2.0.2...HEAD
131
+ [Unreleased]: https://github.com/MetaMask/core/compare/@metamask/assets-controller@2.1.0...HEAD
132
+ [2.1.0]: https://github.com/MetaMask/core/compare/@metamask/assets-controller@2.0.2...@metamask/assets-controller@2.1.0
126
133
  [2.0.2]: https://github.com/MetaMask/core/compare/@metamask/assets-controller@2.0.1...@metamask/assets-controller@2.0.2
127
134
  [2.0.1]: https://github.com/MetaMask/core/compare/@metamask/assets-controller@2.0.0...@metamask/assets-controller@2.0.1
128
135
  [2.0.0]: https://github.com/MetaMask/core/compare/@metamask/assets-controller@1.0.0...@metamask/assets-controller@2.0.0
@@ -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_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_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;
16
+ 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_onActiveChainsUpdated, _AssetsController_initializeState, _AssetsController_extractEnabledChains, _AssetsController_normalizeChainReference, _AssetsController_subscribeToEvents, _AssetsController_updateActive, _AssetsController_registerActionHandlers, _AssetsController_handleActiveChainsUpdate, _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;
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");
@@ -241,36 +241,37 @@ class AssetsController extends base_controller_1.BaseController {
241
241
  _AssetsController_detectionMiddleware.set(this, void 0);
242
242
  _AssetsController_tokenDataSource.set(this, void 0);
243
243
  _AssetsController_unsubscribeBasicFunctionality.set(this, null);
244
+ _AssetsController_onActiveChainsUpdated.set(this, void 0);
244
245
  __classPrivateFieldSet(this, _AssetsController_isEnabled, isEnabled(), "f");
245
246
  __classPrivateFieldSet(this, _AssetsController_isBasicFunctionality, isBasicFunctionality ?? (() => true), "f");
246
247
  __classPrivateFieldSet(this, _AssetsController_defaultUpdateInterval, defaultUpdateInterval, "f");
247
248
  __classPrivateFieldSet(this, _AssetsController_trackMetaMetricsEvent, trackMetaMetricsEvent, "f");
248
249
  const rpcConfig = rpcDataSourceConfig ?? {};
249
- const onActiveChainsUpdated = (dataSourceName, chains, previousChains) => {
250
- this.handleActiveChainsUpdate(dataSourceName, chains, previousChains);
251
- };
250
+ __classPrivateFieldSet(this, _AssetsController_onActiveChainsUpdated, (dataSourceName, chains, previousChains) => {
251
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_handleActiveChainsUpdate).call(this, dataSourceName, chains, previousChains);
252
+ }, "f");
252
253
  __classPrivateFieldSet(this, _AssetsController_backendWebsocketDataSource, new BackendWebsocketDataSource_1.BackendWebsocketDataSource({
253
254
  messenger: this.messenger,
254
255
  queryApiClient,
255
- onActiveChainsUpdated,
256
+ onActiveChainsUpdated: __classPrivateFieldGet(this, _AssetsController_onActiveChainsUpdated, "f"),
256
257
  }), "f");
257
258
  __classPrivateFieldSet(this, _AssetsController_accountsApiDataSource, new AccountsApiDataSource_1.AccountsApiDataSource({
258
259
  queryApiClient,
259
- onActiveChainsUpdated,
260
+ onActiveChainsUpdated: __classPrivateFieldGet(this, _AssetsController_onActiveChainsUpdated, "f"),
260
261
  ...accountsApiDataSourceConfig,
261
262
  }), "f");
262
263
  __classPrivateFieldSet(this, _AssetsController_snapDataSource, new SnapDataSource_1.SnapDataSource({
263
264
  messenger: this.messenger,
264
- onActiveChainsUpdated,
265
+ onActiveChainsUpdated: __classPrivateFieldGet(this, _AssetsController_onActiveChainsUpdated, "f"),
265
266
  }), "f");
266
267
  __classPrivateFieldSet(this, _AssetsController_rpcDataSource, new RpcDataSource_1.RpcDataSource({
267
268
  messenger: this.messenger,
268
- onActiveChainsUpdated,
269
+ onActiveChainsUpdated: __classPrivateFieldGet(this, _AssetsController_onActiveChainsUpdated, "f"),
269
270
  ...rpcConfig,
270
271
  }), "f");
271
272
  __classPrivateFieldSet(this, _AssetsController_stakedBalanceDataSource, new StakedBalanceDataSource_1.StakedBalanceDataSource({
272
273
  messenger: this.messenger,
273
- onActiveChainsUpdated,
274
+ onActiveChainsUpdated: __classPrivateFieldGet(this, _AssetsController_onActiveChainsUpdated, "f"),
274
275
  ...stakedBalanceDataSourceConfig,
275
276
  }), "f");
276
277
  __classPrivateFieldSet(this, _AssetsController_tokenDataSource, new TokenDataSource_1.TokenDataSource({
@@ -302,52 +303,14 @@ class AssetsController extends base_controller_1.BaseController {
302
303
  }
303
304
  }
304
305
  }
305
- // ============================================================================
306
- // DATA SOURCE CHAIN MANAGEMENT
307
- // ============================================================================
308
306
  /**
309
- * Handle when a data source's supported chains change.
310
- * Used to refresh balance subscriptions and run a one-time fetch when a new chain is supported.
311
- *
312
- * - On any add/remove: re-subscribes to data sources so chain assignment stays correct.
313
- * - When chains are added: fetches balances for the new chains (for selected accounts on enabled networks).
314
- *
315
- * Controller does not store chains; sources report via this callback. previousChains is required for diff.
307
+ * Returns the callback passed to data sources for reporting active chain updates.
308
+ * Used by tests to simulate a data source reporting chain changes.
316
309
  *
317
- * @param dataSourceId - The identifier of the data source reporting the change.
318
- * @param activeChains - Currently active (supported and available) chain IDs for this source.
319
- * @param previousChains - Previous chains; used to compute added/removed.
310
+ * @returns The onActiveChainsUpdated callback.
320
311
  */
321
- handleActiveChainsUpdate(dataSourceId, activeChains, previousChains) {
322
- if (!__classPrivateFieldGet(this, _AssetsController_isEnabled, "f")) {
323
- return;
324
- }
325
- log('Data source active chains changed', {
326
- dataSourceId,
327
- chainCount: activeChains.length,
328
- chains: activeChains,
329
- });
330
- const previous = previousChains;
331
- const previousSet = new Set(previous);
332
- const addedChains = activeChains.filter((ch) => !previousSet.has(ch));
333
- const removedChains = previous.filter((ch) => !activeChains.includes(ch));
334
- if (addedChains.length > 0 || removedChains.length > 0) {
335
- // Refresh subscriptions to use updated data source availability
336
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
337
- }
338
- // If chains were added and we have selected accounts, do one-time fetch
339
- if (addedChains.length > 0 && __classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get).length > 0) {
340
- const addedEnabledChains = addedChains.filter((chain) => __classPrivateFieldGet(this, _AssetsController_enabledChains, "f").has(chain));
341
- if (addedEnabledChains.length > 0) {
342
- log('Fetching balances for newly added chains', { addedEnabledChains });
343
- this.getAssets(__classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get), {
344
- chainIds: addedEnabledChains,
345
- forceUpdate: true,
346
- }).catch((error) => {
347
- log('Failed to fetch balance for added chains', { error });
348
- });
349
- }
350
- }
312
+ getOnActiveChainsUpdated() {
313
+ return __classPrivateFieldGet(this, _AssetsController_onActiveChainsUpdated, "f");
351
314
  }
352
315
  // ============================================================================
353
316
  // PUBLIC API: QUERY METHODS
@@ -394,7 +357,12 @@ class AssetsController extends base_controller_1.BaseController {
394
357
  __classPrivateFieldGet(this, _AssetsController_detectionMiddleware, "f"),
395
358
  ];
396
359
  const { response, durationByDataSource } = await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, sources, request);
397
- await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, { ...response, updateMode: 'full' });
360
+ // Default to 'merge' when fetching a subset of chains so we don't wipe
361
+ // balances from chains that weren't included in this fetch.
362
+ const isPartialChainFetch = options?.chainIds !== undefined &&
363
+ options.chainIds.length < __classPrivateFieldGet(this, _AssetsController_enabledChains, "f").size;
364
+ const updateMode = options?.updateMode ?? (isPartialChainFetch ? 'merge' : 'full');
365
+ await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, { ...response, updateMode });
398
366
  if (__classPrivateFieldGet(this, _AssetsController_trackMetaMetricsEvent, "f") && !__classPrivateFieldGet(this, _AssetsController_firstInitFetchReported, "f")) {
399
367
  __classPrivateFieldSet(this, _AssetsController_firstInitFetchReported, true, "f");
400
368
  const durationMs = Date.now() - startTime;
@@ -508,13 +476,14 @@ class AssetsController extends base_controller_1.BaseController {
508
476
  assetMetadata;
509
477
  }
510
478
  });
511
- // Fetch data for the newly added custom asset
479
+ // Fetch data for the newly added custom asset (merge to preserve other chains)
512
480
  const account = __classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get).find((a) => a.id === accountId);
513
481
  if (account) {
514
482
  const chainId = extractChainId(normalizedAssetId);
515
483
  await this.getAssets([account], {
516
484
  chainIds: [chainId],
517
485
  forceUpdate: true,
486
+ updateMode: 'merge',
518
487
  });
519
488
  }
520
489
  }
@@ -738,7 +707,7 @@ class AssetsController extends base_controller_1.BaseController {
738
707
  }
739
708
  }
740
709
  exports.AssetsController = AssetsController;
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() {
710
+ _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_onActiveChainsUpdated = new WeakMap(), _AssetsController_instances = new WeakSet(), _AssetsController_selectedAccounts_get = function _AssetsController_selectedAccounts_get() {
742
711
  return this.messenger.call('AccountTreeController:getAccountsFromSelectedAccountGroup');
743
712
  }, _AssetsController_allBalanceDataSources_get = function _AssetsController_allBalanceDataSources_get() {
744
713
  return [
@@ -811,6 +780,37 @@ _AssetsController_isEnabled = new WeakMap(), _AssetsController_isBasicFunctional
811
780
  }
812
781
  }, _AssetsController_registerActionHandlers = function _AssetsController_registerActionHandlers() {
813
782
  this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
783
+ }, _AssetsController_handleActiveChainsUpdate = function _AssetsController_handleActiveChainsUpdate(dataSourceId, activeChains, previousChains) {
784
+ if (!__classPrivateFieldGet(this, _AssetsController_isEnabled, "f")) {
785
+ return;
786
+ }
787
+ log('Data source active chains changed', {
788
+ dataSourceId,
789
+ chainCount: activeChains.length,
790
+ chains: activeChains,
791
+ });
792
+ const previous = previousChains;
793
+ const previousSet = new Set(previous);
794
+ const addedChains = activeChains.filter((ch) => !previousSet.has(ch));
795
+ const removedChains = previous.filter((ch) => !activeChains.includes(ch));
796
+ if (addedChains.length > 0 || removedChains.length > 0) {
797
+ // Refresh subscriptions to use updated data source availability
798
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
799
+ }
800
+ // If chains were added and we have selected accounts, do one-time fetch
801
+ if (addedChains.length > 0 && __classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get).length > 0) {
802
+ const addedEnabledChains = addedChains.filter((chain) => __classPrivateFieldGet(this, _AssetsController_enabledChains, "f").has(chain));
803
+ if (addedEnabledChains.length > 0) {
804
+ log('Fetching balances for newly added chains', { addedEnabledChains });
805
+ this.getAssets(__classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get), {
806
+ chainIds: addedEnabledChains,
807
+ forceUpdate: true,
808
+ updateMode: 'merge',
809
+ }).catch((error) => {
810
+ log('Failed to fetch balance for added chains', { error });
811
+ });
812
+ }
813
+ }
814
814
  }, _AssetsController_executeMiddlewares =
815
815
  // ============================================================================
816
816
  // MIDDLEWARE EXECUTION
@@ -861,11 +861,44 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
861
861
  }
862
862
  }
863
863
  return { response: result.response, durationByDataSource };
864
- }, _AssetsController_updateState =
865
- // ============================================================================
866
- // STATE MANAGEMENT
867
- // ============================================================================
868
- async function _AssetsController_updateState(response) {
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) {
869
902
  const normalizedResponse = normalizeResponse(response);
870
903
  const mode = normalizedResponse.updateMode ?? 'merge';
871
904
  const releaseLock = await __classPrivateFieldGet(this, _AssetsController_controllerMutex, "f").acquire();
@@ -894,9 +927,12 @@ async function _AssetsController_updateState(response) {
894
927
  for (const [accountId, accountBalances] of Object.entries(normalizedResponse.assetsBalance)) {
895
928
  const previousBalances = previousState.assetsBalance[accountId] ?? {};
896
929
  const customAssetIds = state.customAssets[accountId] ?? [];
897
- // Full: response is authoritative; preserve custom assets not in response. Merge: response overlays previous.
898
- const effective = mode === 'full'
899
- ? (() => {
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
+ : (() => {
900
936
  const next = {
901
937
  ...accountBalances,
902
938
  };
@@ -908,13 +944,25 @@ async function _AssetsController_updateState(response) {
908
944
  }
909
945
  }
910
946
  return next;
911
- })()
912
- : { ...previousBalances, ...accountBalances };
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
+ }
957
+ }
913
958
  for (const [assetId, balance] of Object.entries(effective)) {
914
959
  const previousBalance = previousBalances[assetId];
915
960
  const newAmount = balance.amount;
916
961
  const oldAmount = previousBalance?.amount;
917
- if (oldAmount !== newAmount) {
962
+ const isNewDefaultNativeZero = oldAmount === undefined &&
963
+ newAmount === '0' &&
964
+ nativeAssetIdsForAccount.includes(assetId);
965
+ if (oldAmount !== newAmount && !isNewDefaultNativeZero) {
918
966
  changedBalances.push({
919
967
  accountId,
920
968
  assetId,
@@ -1067,6 +1115,7 @@ async function _AssetsController_updateState(response) {
1067
1115
  enabledChainCount: __classPrivateFieldGet(this, _AssetsController_enabledChains, "f").size,
1068
1116
  });
1069
1117
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1118
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
1070
1119
  this.getAssets(__classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get), {
1071
1120
  chainIds: [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")],
1072
1121
  forceUpdate: true,
@@ -1190,7 +1239,7 @@ async function _AssetsController_updateState(response) {
1190
1239
  }),
1191
1240
  subscriptionId: subscriptionKey,
1192
1241
  isUpdate,
1193
- onAssetsUpdate: (response) => this.handleAssetsUpdate(response, sourceId),
1242
+ onAssetsUpdate: (response, request) => this.handleAssetsUpdate(response, sourceId, request),
1194
1243
  getAssetsState: () => this.state,
1195
1244
  };
1196
1245
  source.subscribe(subscribeReq).catch((error) => {
@@ -1274,6 +1323,7 @@ async function _AssetsController_handleAccountGroupChanged() {
1274
1323
  forceUpdate: true,
1275
1324
  });
1276
1325
  }
1326
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
1277
1327
  }, _AssetsController_handleEnabledNetworksChanged = async function _AssetsController_handleEnabledNetworksChanged(enabledNetworkMap) {
1278
1328
  const previousChains = __classPrivateFieldGet(this, _AssetsController_enabledChains, "f");
1279
1329
  __classPrivateFieldSet(this, _AssetsController_enabledChains, __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_extractEnabledChains).call(this, enabledNetworkMap), "f");
@@ -1302,12 +1352,14 @@ async function _AssetsController_handleAccountGroupChanged() {
1302
1352
  // The data will simply not be updated until the network is re-enabled.
1303
1353
  // Refresh subscriptions for new chain set
1304
1354
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1305
- // Do one-time fetch for newly enabled chains
1355
+ // Do one-time fetch for newly enabled chains; merge so we keep existing chain balances
1306
1356
  if (addedChains.length > 0 && __classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get).length > 0) {
1307
1357
  await this.getAssets(__classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get), {
1308
1358
  chainIds: addedChains,
1309
1359
  forceUpdate: true,
1360
+ updateMode: 'merge',
1310
1361
  });
1311
1362
  }
1363
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
1312
1364
  };
1313
1365
  //# sourceMappingURL=AssetsController.cjs.map