@metamask-previews/assets-controller 9.1.0-preview-18dd0df9c → 9.1.0-preview-bd7b53f49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
21
21
  - `BackendWebsocketDataSource` registers subscription handlers before the subscribe handshake so in-flight account-activity notifications are not dropped, cleans up subscription state on subscribe failure, and resolves balance updates from stored subscription state when notifications arrive with stale subscription IDs ([#9273](https://github.com/MetaMask/core/pull/9273))
22
22
  - `AssetsController` refreshes data-source `activeChains`, re-subscribes, and force-fetches balances for the newly selected EVM chain when the user switches networks via `NetworkController:networkDidChange`
23
23
  - `AssetsController` bypasses the price deduper cache and fetches spot prices for assets on a newly selected or added EVM network only when those assets have no entry in `assetsPrice` yet; `DetectionMiddleware` queues newly detected assets for the same pipeline price fetch so RPC-detected tokens are priced without waiting for the poll interval
24
+ - `AssetsController` merge updates from `getAssets({ forceUpdate: true })` now replace balances on API-covered chains so unlock/startup reflects the fresh Accounts API snapshot instead of preserving stale pre-lock tokens; subscription-driven merges are unchanged
24
25
 
25
26
  ## [9.1.0]
26
27
 
@@ -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_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_shouldHideNativeToken, _AssetsController_tokenStandardToAssetType, _AssetsController_start, _AssetsController_stop, _AssetsController_subscribeAssets, _AssetsController_subscribeAssetsBalance, _AssetsController_subscribeRpcCustomAssetsSupplement, _AssetsController_unsubscribeBySubscriptionKey, _AssetsController_subscribeStakedBalance, _AssetsController_buildChainToAccountsMap, _AssetsController_subscribeDataSource, _AssetsController_unsubscribeDataSource, _AssetsController_buildDataRequest, _AssetsController_getEnabledChainsForAccount, _AssetsController_handleAccountGroupChanged, _AssetsController_handleEnabledNetworksChanged, _AssetsController_handleNetworkAdded, _AssetsController_refreshActiveChainsOnNetworkSwitch, _AssetsController_getSelectedEvmChainIdFromNetworkState, _AssetsController_handleNetworkDidChange, _AssetsController_refreshAssetsAfterNetworkChange, _AssetsController_refreshAssetsAfterNetworkAdded;
16
+ var _AssetsController_instances, _AssetsController_isEnabled, _AssetsController_isBasicFunctionality, _AssetsController_defaultUpdateInterval, _AssetsController_trace, _AssetsController_captureException, _AssetsController_firstInitFetchReported, _AssetsController_stateSizeReported, _AssetsController_emitTrace, _AssetsController_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_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");
@@ -193,8 +193,41 @@ function normalizeResponse(response) {
193
193
  if (response.updateMode) {
194
194
  normalized.updateMode = response.updateMode;
195
195
  }
196
+ if (response.replaceCoveredChainBalances) {
197
+ normalized.replaceCoveredChainBalances = true;
198
+ }
196
199
  return normalized;
197
200
  }
201
+ /**
202
+ * Merge account balances from a data-source response into prior state.
203
+ *
204
+ * @param previousBalances - Balances already in state for this account.
205
+ * @param accountBalances - Balances from the incoming response.
206
+ * @param customAssetIds - Custom assets to preserve when replacing covered chains.
207
+ * @param replaceCoveredChains - When true, drop prior balances on chains present
208
+ * in the response before applying it (authoritative chain slice).
209
+ * @returns The merged balance map for the account.
210
+ */
211
+ function mergeAccountBalances(previousBalances, accountBalances, customAssetIds, replaceCoveredChains) {
212
+ if (!replaceCoveredChains) {
213
+ return { ...previousBalances, ...accountBalances };
214
+ }
215
+ const coveredChains = new Set(Object.keys(accountBalances).map((assetId) => assetId.split('/')[0]));
216
+ const next = {};
217
+ for (const [assetId, balance] of Object.entries(previousBalances)) {
218
+ if (!coveredChains.has(assetId.split('/')[0])) {
219
+ next[assetId] = balance;
220
+ }
221
+ }
222
+ Object.assign(next, accountBalances);
223
+ for (const customId of customAssetIds) {
224
+ if (!Object.prototype.hasOwnProperty.call(next, customId)) {
225
+ const prev = previousBalances[customId];
226
+ next[customId] = prev ?? { amount: '0' };
227
+ }
228
+ }
229
+ return next;
230
+ }
198
231
  // ============================================================================
199
232
  // CONTROLLER IMPLEMENTATION
200
233
  // ============================================================================
@@ -443,7 +476,11 @@ class AssetsController extends base_controller_1.BaseController {
443
476
  ]
444
477
  : [__classPrivateFieldGet(this, _AssetsController_stakedBalanceDataSource, "f"), __classPrivateFieldGet(this, _AssetsController_detectionMiddleware, "f")];
445
478
  const { response, durationByDataSource } = await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, fastSources, request);
446
- await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, { ...response, updateMode: 'merge' });
479
+ await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, {
480
+ ...response,
481
+ updateMode: 'merge',
482
+ replaceCoveredChainBalances: true,
483
+ });
447
484
  // Background pipeline: snap and RPC run in parallel after the fast path
448
485
  // commits to state. Their balances are merged together before detection.
449
486
  // Token + price enrichment matches the pre-split behavior: only when basic
@@ -875,7 +912,10 @@ class AssetsController extends base_controller_1.BaseController {
875
912
  ]));
876
913
  }
877
914
  const { response: enrichedResponse } = await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, enrichmentSources, pipelineRequest, response);
878
- await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, enrichedResponse);
915
+ await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, {
916
+ ...enrichedResponse,
917
+ replaceCoveredChainBalances: response.replaceCoveredChainBalances,
918
+ });
879
919
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_emitTrace).call(this, TRACE_UPDATE_PIPELINE, {
880
920
  source: sourceId,
881
921
  duration_ms: performance.now() - updateStart,
@@ -1223,6 +1263,32 @@ _AssetsController_isEnabled = new WeakMap(), _AssetsController_isBasicFunctional
1223
1263
  finally {
1224
1264
  releaseLock();
1225
1265
  }
1266
+ }, _AssetsController_runStartupRefresh =
1267
+ /**
1268
+ * Force-fetch balances then subscribe. Used on unlock / first startup.
1269
+ *
1270
+ * @param accounts - Selected accounts to refresh.
1271
+ */
1272
+ async function _AssetsController_runStartupRefresh(accounts) {
1273
+ const releaseLock = await __classPrivateFieldGet(this, _AssetsController_accountRefreshMutex, "f").acquire();
1274
+ try {
1275
+ await this.getAssets(accounts, {
1276
+ chainIds: [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")],
1277
+ forceUpdate: true,
1278
+ });
1279
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1280
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
1281
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureDefaultTrackedAssetsSeeded).call(this);
1282
+ }
1283
+ catch (error) {
1284
+ log('Failed to fetch assets on startup', error);
1285
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1286
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
1287
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureDefaultTrackedAssetsSeeded).call(this);
1288
+ }
1289
+ finally {
1290
+ releaseLock();
1291
+ }
1226
1292
  }, _AssetsController_registerActionHandlers = function _AssetsController_registerActionHandlers() {
1227
1293
  this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
1228
1294
  }, _AssetsController_handleActiveChainsUpdate = function _AssetsController_handleActiveChainsUpdate(dataSourceId, activeChains, previousChains) {
@@ -1557,36 +1623,9 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1557
1623
  for (const [accountId, accountBalances] of Object.entries(normalizedResponse.assetsBalance)) {
1558
1624
  const previousBalances = previousState.assetsBalance[accountId] ?? {};
1559
1625
  const customAssetIds = state.customAssets[accountId] ?? [];
1560
- // Full: response is authoritative for the chains it covered;
1561
- // balances for chains not in the response are preserved from
1562
- // previous state so unsupported chains (e.g. Ink on AccountsAPI)
1563
- // are never inadvertently reset to zero.
1564
- // Merge: response overlays previous balances.
1565
- const effective = mode === 'merge'
1566
- ? { ...previousBalances, ...accountBalances }
1567
- : (() => {
1568
- // Determine which chain namespaces this response covers.
1569
- const coveredChains = new Set(Object.keys(accountBalances).map((assetId) => assetId.split('/')[0]));
1570
- // Start from previous balances, dropping only entries for
1571
- // chains this response is authoritative over.
1572
- const next = {};
1573
- for (const [assetId, balance] of Object.entries(previousBalances)) {
1574
- if (!coveredChains.has(assetId.split('/')[0])) {
1575
- next[assetId] = balance;
1576
- }
1577
- }
1578
- // Apply the response (authoritative for covered chains).
1579
- Object.assign(next, accountBalances);
1580
- // Preserve custom assets that the response omitted.
1581
- for (const customId of customAssetIds) {
1582
- if (!Object.prototype.hasOwnProperty.call(next, customId)) {
1583
- const prev = previousBalances[customId];
1584
- next[customId] =
1585
- prev ?? { amount: '0' };
1586
- }
1587
- }
1588
- return next;
1589
- })();
1626
+ const replaceCoveredChains = mode === 'full' ||
1627
+ normalizedResponse.replaceCoveredChainBalances === true;
1628
+ const effective = mergeAccountBalances(previousBalances, accountBalances, customAssetIds, replaceCoveredChains);
1590
1629
  // Ensure native tokens have an entry (0 if missing) for chains this account supports
1591
1630
  const account = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getSelectedAccounts).call(this).find((a) => a.id === accountId);
1592
1631
  const nativeAssetIdsForAccount = account
@@ -1785,14 +1824,8 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
1785
1824
  enabledChainCount: chainIds.length,
1786
1825
  });
1787
1826
  __classPrivateFieldSet(this, _AssetsController_lastKnownAccountIds, new Set(accounts.map((a) => a.id)), "f");
1788
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1789
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
1790
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureDefaultTrackedAssetsSeeded).call(this);
1791
- this.getAssets(accounts, {
1792
- chainIds,
1793
- forceUpdate: true,
1794
- }).catch((error) => {
1795
- log('Failed to fetch assets', error);
1827
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_runStartupRefresh).call(this, accounts).catch((error) => {
1828
+ log('Failed to start asset tracking', error);
1796
1829
  });
1797
1830
  }, _AssetsController_stop = function _AssetsController_stop() {
1798
1831
  log('Stopping asset tracking', {