@metamask/assets-controller 2.0.2 → 2.2.0

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 (138) hide show
  1. package/CHANGELOG.md +39 -1
  2. package/dist/AssetsController-method-action-types.cjs.map +1 -1
  3. package/dist/AssetsController-method-action-types.d.cts +21 -1
  4. package/dist/AssetsController-method-action-types.d.cts.map +1 -1
  5. package/dist/AssetsController-method-action-types.d.mts +21 -1
  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 +284 -89
  9. package/dist/AssetsController.cjs.map +1 -1
  10. package/dist/AssetsController.d.cts +59 -17
  11. package/dist/AssetsController.d.cts.map +1 -1
  12. package/dist/AssetsController.d.mts +59 -17
  13. package/dist/AssetsController.d.mts.map +1 -1
  14. package/dist/AssetsController.mjs +284 -89
  15. package/dist/AssetsController.mjs.map +1 -1
  16. package/dist/data-sources/AbstractDataSource.cjs.map +1 -1
  17. package/dist/data-sources/AbstractDataSource.d.cts +3 -3
  18. package/dist/data-sources/AbstractDataSource.d.cts.map +1 -1
  19. package/dist/data-sources/AbstractDataSource.d.mts +3 -3
  20. package/dist/data-sources/AbstractDataSource.d.mts.map +1 -1
  21. package/dist/data-sources/AbstractDataSource.mjs.map +1 -1
  22. package/dist/data-sources/AccountsApiDataSource.cjs +2 -1
  23. package/dist/data-sources/AccountsApiDataSource.cjs.map +1 -1
  24. package/dist/data-sources/AccountsApiDataSource.d.cts.map +1 -1
  25. package/dist/data-sources/AccountsApiDataSource.d.mts.map +1 -1
  26. package/dist/data-sources/AccountsApiDataSource.mjs +2 -1
  27. package/dist/data-sources/AccountsApiDataSource.mjs.map +1 -1
  28. package/dist/data-sources/BackendWebsocketDataSource.cjs +18 -6
  29. package/dist/data-sources/BackendWebsocketDataSource.cjs.map +1 -1
  30. package/dist/data-sources/BackendWebsocketDataSource.d.cts +2 -2
  31. package/dist/data-sources/BackendWebsocketDataSource.d.cts.map +1 -1
  32. package/dist/data-sources/BackendWebsocketDataSource.d.mts +2 -2
  33. package/dist/data-sources/BackendWebsocketDataSource.d.mts.map +1 -1
  34. package/dist/data-sources/BackendWebsocketDataSource.mjs +15 -6
  35. package/dist/data-sources/BackendWebsocketDataSource.mjs.map +1 -1
  36. package/dist/data-sources/PriceDataSource.cjs +20 -14
  37. package/dist/data-sources/PriceDataSource.cjs.map +1 -1
  38. package/dist/data-sources/PriceDataSource.d.cts +2 -2
  39. package/dist/data-sources/PriceDataSource.d.cts.map +1 -1
  40. package/dist/data-sources/PriceDataSource.d.mts +2 -2
  41. package/dist/data-sources/PriceDataSource.d.mts.map +1 -1
  42. package/dist/data-sources/PriceDataSource.mjs +20 -14
  43. package/dist/data-sources/PriceDataSource.mjs.map +1 -1
  44. package/dist/data-sources/RpcDataSource.cjs +23 -4
  45. package/dist/data-sources/RpcDataSource.cjs.map +1 -1
  46. package/dist/data-sources/RpcDataSource.d.cts.map +1 -1
  47. package/dist/data-sources/RpcDataSource.d.mts.map +1 -1
  48. package/dist/data-sources/RpcDataSource.mjs +23 -4
  49. package/dist/data-sources/RpcDataSource.mjs.map +1 -1
  50. package/dist/data-sources/SnapDataSource.cjs +3 -2
  51. package/dist/data-sources/SnapDataSource.cjs.map +1 -1
  52. package/dist/data-sources/SnapDataSource.d.cts.map +1 -1
  53. package/dist/data-sources/SnapDataSource.d.mts.map +1 -1
  54. package/dist/data-sources/SnapDataSource.mjs +3 -2
  55. package/dist/data-sources/SnapDataSource.mjs.map +1 -1
  56. package/dist/data-sources/StakedBalanceDataSource.cjs +41 -3
  57. package/dist/data-sources/StakedBalanceDataSource.cjs.map +1 -1
  58. package/dist/data-sources/StakedBalanceDataSource.d.cts.map +1 -1
  59. package/dist/data-sources/StakedBalanceDataSource.d.mts.map +1 -1
  60. package/dist/data-sources/StakedBalanceDataSource.mjs +41 -3
  61. package/dist/data-sources/StakedBalanceDataSource.mjs.map +1 -1
  62. package/dist/data-sources/TokenDataSource.cjs +1 -0
  63. package/dist/data-sources/TokenDataSource.cjs.map +1 -1
  64. package/dist/data-sources/TokenDataSource.d.cts.map +1 -1
  65. package/dist/data-sources/TokenDataSource.d.mts.map +1 -1
  66. package/dist/data-sources/TokenDataSource.mjs +1 -0
  67. package/dist/data-sources/TokenDataSource.mjs.map +1 -1
  68. package/dist/data-sources/evm-rpc-services/services/TokenDetector.cjs.map +1 -1
  69. package/dist/data-sources/evm-rpc-services/services/TokenDetector.d.cts.map +1 -1
  70. package/dist/data-sources/evm-rpc-services/services/TokenDetector.d.mts.map +1 -1
  71. package/dist/data-sources/evm-rpc-services/services/TokenDetector.mjs.map +1 -1
  72. package/dist/index.cjs +3 -2
  73. package/dist/index.cjs.map +1 -1
  74. package/dist/index.d.cts +5 -3
  75. package/dist/index.d.cts.map +1 -1
  76. package/dist/index.d.mts +5 -3
  77. package/dist/index.d.mts.map +1 -1
  78. package/dist/index.mjs +1 -1
  79. package/dist/index.mjs.map +1 -1
  80. package/dist/middlewares/DetectionMiddleware.cjs +44 -27
  81. package/dist/middlewares/DetectionMiddleware.cjs.map +1 -1
  82. package/dist/middlewares/DetectionMiddleware.d.cts +15 -9
  83. package/dist/middlewares/DetectionMiddleware.d.cts.map +1 -1
  84. package/dist/middlewares/DetectionMiddleware.d.mts +15 -9
  85. package/dist/middlewares/DetectionMiddleware.d.mts.map +1 -1
  86. package/dist/middlewares/DetectionMiddleware.mjs +44 -27
  87. package/dist/middlewares/DetectionMiddleware.mjs.map +1 -1
  88. package/dist/middlewares/ParallelMiddleware.cjs +216 -0
  89. package/dist/middlewares/ParallelMiddleware.cjs.map +1 -0
  90. package/dist/middlewares/ParallelMiddleware.d.cts +45 -0
  91. package/dist/middlewares/ParallelMiddleware.d.cts.map +1 -0
  92. package/dist/middlewares/ParallelMiddleware.d.mts +45 -0
  93. package/dist/middlewares/ParallelMiddleware.d.mts.map +1 -0
  94. package/dist/middlewares/ParallelMiddleware.mjs +214 -0
  95. package/dist/middlewares/ParallelMiddleware.mjs.map +1 -0
  96. package/dist/middlewares/index.cjs +5 -1
  97. package/dist/middlewares/index.cjs.map +1 -1
  98. package/dist/middlewares/index.d.cts +2 -0
  99. package/dist/middlewares/index.d.cts.map +1 -1
  100. package/dist/middlewares/index.d.mts +2 -0
  101. package/dist/middlewares/index.d.mts.map +1 -1
  102. package/dist/middlewares/index.mjs +1 -0
  103. package/dist/middlewares/index.mjs.map +1 -1
  104. package/dist/types.cjs.map +1 -1
  105. package/dist/types.d.cts +16 -0
  106. package/dist/types.d.cts.map +1 -1
  107. package/dist/types.d.mts +16 -0
  108. package/dist/types.d.mts.map +1 -1
  109. package/dist/types.mjs.map +1 -1
  110. package/dist/utils/formatExchangeRatesForBridge.cjs +123 -0
  111. package/dist/utils/formatExchangeRatesForBridge.cjs.map +1 -0
  112. package/dist/utils/formatExchangeRatesForBridge.d.cts +65 -0
  113. package/dist/utils/formatExchangeRatesForBridge.d.cts.map +1 -0
  114. package/dist/utils/formatExchangeRatesForBridge.d.mts +65 -0
  115. package/dist/utils/formatExchangeRatesForBridge.d.mts.map +1 -0
  116. package/dist/utils/formatExchangeRatesForBridge.mjs +119 -0
  117. package/dist/utils/formatExchangeRatesForBridge.mjs.map +1 -0
  118. package/dist/utils/index.cjs +8 -0
  119. package/dist/utils/index.cjs.map +1 -0
  120. package/dist/utils/index.d.cts +4 -0
  121. package/dist/utils/index.d.cts.map +1 -0
  122. package/dist/utils/index.d.mts +4 -0
  123. package/dist/utils/index.d.mts.map +1 -0
  124. package/dist/utils/index.mjs +3 -0
  125. package/dist/utils/index.mjs.map +1 -0
  126. package/dist/{utils.cjs → utils/normalizeAssetId.cjs} +1 -1
  127. package/dist/utils/normalizeAssetId.cjs.map +1 -0
  128. package/dist/{utils.d.mts → utils/normalizeAssetId.d.cts} +2 -2
  129. package/dist/utils/normalizeAssetId.d.cts.map +1 -0
  130. package/dist/{utils.d.cts → utils/normalizeAssetId.d.mts} +2 -2
  131. package/dist/utils/normalizeAssetId.d.mts.map +1 -0
  132. package/dist/{utils.mjs → utils/normalizeAssetId.mjs} +1 -1
  133. package/dist/utils/normalizeAssetId.mjs.map +1 -0
  134. package/package.json +6 -5
  135. package/dist/utils.cjs.map +0 -1
  136. package/dist/utils.d.cts.map +0 -1
  137. package/dist/utils.d.mts.map +0 -1
  138. package/dist/utils.mjs.map +0 -1
@@ -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_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;
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,7 +26,8 @@ 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";
28
- import { normalizeAssetId } from "./utils.mjs";
29
+ import { createParallelBalanceMiddleware, createParallelMiddleware } from "./middlewares/ParallelMiddleware.mjs";
30
+ import { normalizeAssetId, formatExchangeRatesForBridge } from "./utils/index.mjs";
29
31
  // ============================================================================
30
32
  // CONTROLLER CONSTANTS
31
33
  // ============================================================================
@@ -36,6 +38,7 @@ const MESSENGER_EXPOSED_METHODS = [
36
38
  'getAssetsBalance',
37
39
  'getAssetMetadata',
38
40
  'getAssetsPrice',
41
+ 'getExchangeRatesForBridge',
39
42
  'addCustomAsset',
40
43
  'removeCustomAsset',
41
44
  'getCustomAssets',
@@ -57,6 +60,7 @@ export function getDefaultAssetsControllerState() {
57
60
  assetsPrice: {},
58
61
  customAssets: {},
59
62
  assetPreferences: {},
63
+ selectedCurrency: 'usd',
60
64
  };
61
65
  }
62
66
  // ============================================================================
@@ -93,6 +97,12 @@ const stateMetadata = {
93
97
  includeInDebugSnapshot: false,
94
98
  usedInUi: true,
95
99
  },
100
+ selectedCurrency: {
101
+ persist: true,
102
+ includeInStateLogs: false,
103
+ includeInDebugSnapshot: false,
104
+ usedInUi: true,
105
+ },
96
106
  };
97
107
  // ============================================================================
98
108
  // HELPER FUNCTIONS
@@ -146,6 +156,9 @@ function normalizeResponse(response) {
146
156
  if (response.errors) {
147
157
  normalized.errors = { ...response.errors };
148
158
  }
159
+ if (response.updateMode) {
160
+ normalized.updateMode = response.updateMode;
161
+ }
149
162
  return normalized;
150
163
  }
151
164
  // ============================================================================
@@ -167,8 +180,10 @@ function normalizeResponse(response) {
167
180
  * based on which chains they support. When active chains change, the controller
168
181
  * dynamically adjusts subscriptions.
169
182
  *
170
- * 4. **Keyring Lifecycle**: Listens to KeyringController unlock/lock events to
171
- * start/stop subscriptions when the wallet is unlocked or locked.
183
+ * 4. **Client + Keyring Lifecycle**: Starts subscriptions only when both the UI is
184
+ * open (ClientController) and the wallet is unlocked (KeyringController).
185
+ * Stops when either the UI closes or the keyring locks. See client-controller
186
+ * README for the combined pattern.
172
187
  *
173
188
  * ## Architecture
174
189
  *
@@ -198,6 +213,10 @@ export class AssetsController extends BaseController {
198
213
  _AssetsController_trackMetaMetricsEvent.set(this, void 0);
199
214
  /** Whether we have already reported first init fetch for this session (reset on #stop). */
200
215
  _AssetsController_firstInitFetchReported.set(this, false);
216
+ /** Whether the client (UI) is open. Combined with #keyringUnlocked for #updateActive. */
217
+ _AssetsController_uiOpen.set(this, false);
218
+ /** Whether the keyring is unlocked. Combined with #uiOpen for #updateActive. */
219
+ _AssetsController_keyringUnlocked.set(this, false);
201
220
  _AssetsController_controllerMutex.set(this, new Mutex());
202
221
  /**
203
222
  * Active balance subscriptions keyed by account ID.
@@ -217,36 +236,37 @@ export class AssetsController extends BaseController {
217
236
  _AssetsController_detectionMiddleware.set(this, void 0);
218
237
  _AssetsController_tokenDataSource.set(this, void 0);
219
238
  _AssetsController_unsubscribeBasicFunctionality.set(this, null);
239
+ _AssetsController_onActiveChainsUpdated.set(this, void 0);
220
240
  __classPrivateFieldSet(this, _AssetsController_isEnabled, isEnabled(), "f");
221
241
  __classPrivateFieldSet(this, _AssetsController_isBasicFunctionality, isBasicFunctionality ?? (() => true), "f");
222
242
  __classPrivateFieldSet(this, _AssetsController_defaultUpdateInterval, defaultUpdateInterval, "f");
223
243
  __classPrivateFieldSet(this, _AssetsController_trackMetaMetricsEvent, trackMetaMetricsEvent, "f");
224
244
  const rpcConfig = rpcDataSourceConfig ?? {};
225
- const onActiveChainsUpdated = (dataSourceName, chains, previousChains) => {
226
- this.handleActiveChainsUpdate(dataSourceName, chains, previousChains);
227
- };
245
+ __classPrivateFieldSet(this, _AssetsController_onActiveChainsUpdated, (dataSourceName, chains, previousChains) => {
246
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_handleActiveChainsUpdate).call(this, dataSourceName, chains, previousChains);
247
+ }, "f");
228
248
  __classPrivateFieldSet(this, _AssetsController_backendWebsocketDataSource, new BackendWebsocketDataSource({
229
249
  messenger: this.messenger,
230
250
  queryApiClient,
231
- onActiveChainsUpdated,
251
+ onActiveChainsUpdated: __classPrivateFieldGet(this, _AssetsController_onActiveChainsUpdated, "f"),
232
252
  }), "f");
233
253
  __classPrivateFieldSet(this, _AssetsController_accountsApiDataSource, new AccountsApiDataSource({
234
254
  queryApiClient,
235
- onActiveChainsUpdated,
255
+ onActiveChainsUpdated: __classPrivateFieldGet(this, _AssetsController_onActiveChainsUpdated, "f"),
236
256
  ...accountsApiDataSourceConfig,
237
257
  }), "f");
238
258
  __classPrivateFieldSet(this, _AssetsController_snapDataSource, new SnapDataSource({
239
259
  messenger: this.messenger,
240
- onActiveChainsUpdated,
260
+ onActiveChainsUpdated: __classPrivateFieldGet(this, _AssetsController_onActiveChainsUpdated, "f"),
241
261
  }), "f");
242
262
  __classPrivateFieldSet(this, _AssetsController_rpcDataSource, new RpcDataSource({
243
263
  messenger: this.messenger,
244
- onActiveChainsUpdated,
264
+ onActiveChainsUpdated: __classPrivateFieldGet(this, _AssetsController_onActiveChainsUpdated, "f"),
245
265
  ...rpcConfig,
246
266
  }), "f");
247
267
  __classPrivateFieldSet(this, _AssetsController_stakedBalanceDataSource, new StakedBalanceDataSource({
248
268
  messenger: this.messenger,
249
- onActiveChainsUpdated,
269
+ onActiveChainsUpdated: __classPrivateFieldGet(this, _AssetsController_onActiveChainsUpdated, "f"),
250
270
  ...stakedBalanceDataSourceConfig,
251
271
  }), "f");
252
272
  __classPrivateFieldSet(this, _AssetsController_tokenDataSource, new TokenDataSource({
@@ -254,6 +274,7 @@ export class AssetsController extends BaseController {
254
274
  }), "f");
255
275
  __classPrivateFieldSet(this, _AssetsController_priceDataSource, new PriceDataSource({
256
276
  queryApiClient,
277
+ getSelectedCurrency: () => this.state.selectedCurrency,
257
278
  ...priceDataSourceConfig,
258
279
  }), "f");
259
280
  __classPrivateFieldSet(this, _AssetsController_detectionMiddleware, new DetectionMiddleware(), "f");
@@ -267,7 +288,7 @@ export class AssetsController extends BaseController {
267
288
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_initializeState).call(this);
268
289
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeToEvents).call(this);
269
290
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_registerActionHandlers).call(this);
270
- // Subscriptions start only on KeyringController:unlock -> #start(), not here.
291
+ // Subscriptions start only when both UI is open and keyring unlocked -> #updateActive().
271
292
  // Subscribe to basic-functionality changes after construction so a synchronous
272
293
  // onChange during subscribe cannot run before data sources are initialized.
273
294
  if (subscribeToBasicFunctionalityChange) {
@@ -277,52 +298,14 @@ export class AssetsController extends BaseController {
277
298
  }
278
299
  }
279
300
  }
280
- // ============================================================================
281
- // DATA SOURCE CHAIN MANAGEMENT
282
- // ============================================================================
283
301
  /**
284
- * Handle when a data source's supported chains change.
285
- * Used to refresh balance subscriptions and run a one-time fetch when a new chain is supported.
286
- *
287
- * - On any add/remove: re-subscribes to data sources so chain assignment stays correct.
288
- * - When chains are added: fetches balances for the new chains (for selected accounts on enabled networks).
302
+ * Returns the callback passed to data sources for reporting active chain updates.
303
+ * Used by tests to simulate a data source reporting chain changes.
289
304
  *
290
- * Controller does not store chains; sources report via this callback. previousChains is required for diff.
291
- *
292
- * @param dataSourceId - The identifier of the data source reporting the change.
293
- * @param activeChains - Currently active (supported and available) chain IDs for this source.
294
- * @param previousChains - Previous chains; used to compute added/removed.
305
+ * @returns The onActiveChainsUpdated callback.
295
306
  */
296
- handleActiveChainsUpdate(dataSourceId, activeChains, previousChains) {
297
- if (!__classPrivateFieldGet(this, _AssetsController_isEnabled, "f")) {
298
- return;
299
- }
300
- log('Data source active chains changed', {
301
- dataSourceId,
302
- chainCount: activeChains.length,
303
- chains: activeChains,
304
- });
305
- const previous = previousChains;
306
- const previousSet = new Set(previous);
307
- const addedChains = activeChains.filter((ch) => !previousSet.has(ch));
308
- const removedChains = previous.filter((ch) => !activeChains.includes(ch));
309
- if (addedChains.length > 0 || removedChains.length > 0) {
310
- // Refresh subscriptions to use updated data source availability
311
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
312
- }
313
- // If chains were added and we have selected accounts, do one-time fetch
314
- if (addedChains.length > 0 && __classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get).length > 0) {
315
- const addedEnabledChains = addedChains.filter((chain) => __classPrivateFieldGet(this, _AssetsController_enabledChains, "f").has(chain));
316
- if (addedEnabledChains.length > 0) {
317
- log('Fetching balances for newly added chains', { addedEnabledChains });
318
- this.getAssets(__classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get), {
319
- chainIds: addedEnabledChains,
320
- forceUpdate: true,
321
- }).catch((error) => {
322
- log('Failed to fetch balance for added chains', { error });
323
- });
324
- }
325
- }
307
+ getOnActiveChainsUpdated() {
308
+ return __classPrivateFieldGet(this, _AssetsController_onActiveChainsUpdated, "f");
326
309
  }
327
310
  // ============================================================================
328
311
  // PUBLIC API: QUERY METHODS
@@ -331,6 +314,9 @@ export class AssetsController extends BaseController {
331
314
  const chainIds = options?.chainIds ?? [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")];
332
315
  const assetTypes = options?.assetTypes ?? ['fungible'];
333
316
  const dataTypes = options?.dataTypes ?? ['balance', 'metadata', 'price'];
317
+ if (accounts.length === 0 || chainIds.length === 0) {
318
+ return __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getAssetsFromState).call(this, accounts, chainIds, assetTypes);
319
+ }
334
320
  // Collect custom assets for all requested accounts
335
321
  const customAssets = [];
336
322
  for (const account of accounts) {
@@ -344,16 +330,21 @@ export class AssetsController extends BaseController {
344
330
  dataTypes,
345
331
  customAssets: customAssets.length > 0 ? customAssets : undefined,
346
332
  forceUpdate: true,
333
+ assetsForPriceUpdate: options?.assetsForPriceUpdate,
347
334
  });
348
335
  const sources = __classPrivateFieldGet(this, _AssetsController_isBasicFunctionality, "f").call(this)
349
336
  ? [
350
- __classPrivateFieldGet(this, _AssetsController_accountsApiDataSource, "f"),
351
- __classPrivateFieldGet(this, _AssetsController_snapDataSource, "f"),
352
- __classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f"),
353
- __classPrivateFieldGet(this, _AssetsController_stakedBalanceDataSource, "f"),
337
+ createParallelBalanceMiddleware([
338
+ __classPrivateFieldGet(this, _AssetsController_accountsApiDataSource, "f"),
339
+ __classPrivateFieldGet(this, _AssetsController_snapDataSource, "f"),
340
+ __classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f"),
341
+ __classPrivateFieldGet(this, _AssetsController_stakedBalanceDataSource, "f"),
342
+ ]),
354
343
  __classPrivateFieldGet(this, _AssetsController_detectionMiddleware, "f"),
355
- __classPrivateFieldGet(this, _AssetsController_tokenDataSource, "f"),
356
- __classPrivateFieldGet(this, _AssetsController_priceDataSource, "f"),
344
+ createParallelMiddleware([
345
+ __classPrivateFieldGet(this, _AssetsController_tokenDataSource, "f"),
346
+ __classPrivateFieldGet(this, _AssetsController_priceDataSource, "f"),
347
+ ]),
357
348
  ]
358
349
  : [
359
350
  __classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f"),
@@ -361,7 +352,12 @@ export class AssetsController extends BaseController {
361
352
  __classPrivateFieldGet(this, _AssetsController_detectionMiddleware, "f"),
362
353
  ];
363
354
  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);
355
+ // Default to 'merge' when fetching a subset of chains so we don't wipe
356
+ // balances from chains that weren't included in this fetch.
357
+ const isPartialChainFetch = options?.chainIds !== undefined &&
358
+ options.chainIds.length < __classPrivateFieldGet(this, _AssetsController_enabledChains, "f").size;
359
+ const updateMode = options?.updateMode ?? (isPartialChainFetch ? 'merge' : 'full');
360
+ await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, { ...response, updateMode });
365
361
  if (__classPrivateFieldGet(this, _AssetsController_trackMetaMetricsEvent, "f") && !__classPrivateFieldGet(this, _AssetsController_firstInitFetchReported, "f")) {
366
362
  __classPrivateFieldSet(this, _AssetsController_firstInitFetchReported, true, "f");
367
363
  const durationMs = Date.now() - startTime;
@@ -372,7 +368,8 @@ export class AssetsController extends BaseController {
372
368
  });
373
369
  }
374
370
  }
375
- return __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getAssetsFromState).call(this, accounts, chainIds, assetTypes);
371
+ const result = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getAssetsFromState).call(this, accounts, chainIds, assetTypes);
372
+ return result;
376
373
  }
377
374
  async getAssetsBalance(accounts, options) {
378
375
  // Reuse getAssets with dataTypes: ['balance'] only
@@ -415,6 +412,28 @@ export class AssetsController extends BaseController {
415
412
  }
416
413
  return result;
417
414
  }
415
+ /**
416
+ * Returns exchange rates in the format expected by the bridge controller
417
+ * (conversionRates, currencyRates, marketData, currentCurrency) so that
418
+ * when useAssetsControllerForRates is true the bridge can use a single
419
+ * action instead of MultichainAssetsRatesController, TokenRatesController,
420
+ * and CurrencyRateController.
421
+ *
422
+ * @param options - Optional options for bridge rate conversion.
423
+ * @param options.usdToSelectedCurrencyRate - When selectedCurrency is not 'usd', pass 1 USD = this many units of selected currency so that currencyRates and conversionRates use correct user-currency vs USD values; otherwise the bridge USD conversion will be wrong for non-USD currencies.
424
+ * @returns Bridge-compatible exchange rate state derived from assetsPrice and selectedCurrency.
425
+ */
426
+ getExchangeRatesForBridge(options) {
427
+ const { nativeAssetIdentifiers } = this.messenger.call('NetworkEnablementController:getState');
428
+ const { networkConfigurationsByChainId } = this.messenger.call('NetworkController:getState');
429
+ return formatExchangeRatesForBridge({
430
+ assetsPrice: this.state.assetsPrice,
431
+ selectedCurrency: this.state.selectedCurrency,
432
+ nativeAssetIdentifiers,
433
+ networkConfigurationsByChainId,
434
+ usdToSelectedCurrencyRate: options?.usdToSelectedCurrencyRate,
435
+ });
436
+ }
418
437
  // ============================================================================
419
438
  // CUSTOM ASSETS MANAGEMENT
420
439
  // ============================================================================
@@ -423,10 +442,15 @@ export class AssetsController extends BaseController {
423
442
  * Custom assets are included in subscription and fetch operations.
424
443
  * Adding a custom asset also unhides it if it was previously hidden.
425
444
  *
445
+ * When `pendingMetadata` is provided (e.g. from the extension's pending-tokens
446
+ * flow), the token metadata is persisted immediately into `assetsInfo` so the
447
+ * UI can render it without waiting for the next pipeline fetch.
448
+ *
426
449
  * @param accountId - The account ID to add the custom asset for.
427
450
  * @param assetId - The CAIP-19 asset ID to add.
451
+ * @param pendingMetadata - Optional token metadata from the UI (pendingTokens format).
428
452
  */
429
- async addCustomAsset(accountId, assetId) {
453
+ async addCustomAsset(accountId, assetId, pendingMetadata) {
430
454
  const normalizedAssetId = normalizeAssetId(assetId);
431
455
  log('Adding custom asset', { accountId, assetId: normalizedAssetId });
432
456
  this.update((state) => {
@@ -446,14 +470,37 @@ export class AssetsController extends BaseController {
446
470
  delete state.assetPreferences[normalizedAssetId];
447
471
  }
448
472
  }
473
+ // Persist metadata from the UI so the token is immediately renderable
474
+ if (pendingMetadata) {
475
+ const parsed = parseCaipAssetType(normalizedAssetId);
476
+ let tokenType = 'erc20';
477
+ if (parsed.assetNamespace === 'slip44') {
478
+ tokenType = 'native';
479
+ }
480
+ else if (parsed.assetNamespace === 'spl') {
481
+ tokenType = 'spl';
482
+ }
483
+ const assetMetadata = {
484
+ type: tokenType,
485
+ symbol: pendingMetadata.symbol,
486
+ name: pendingMetadata.name,
487
+ decimals: pendingMetadata.decimals,
488
+ image: pendingMetadata.iconUrl,
489
+ aggregators: pendingMetadata.aggregators,
490
+ occurrences: pendingMetadata.occurrences,
491
+ };
492
+ state.assetsInfo[normalizedAssetId] =
493
+ assetMetadata;
494
+ }
449
495
  });
450
- // Fetch data for the newly added custom asset
496
+ // Fetch data for the newly added custom asset (merge to preserve other chains)
451
497
  const account = __classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get).find((a) => a.id === accountId);
452
498
  if (account) {
453
499
  const chainId = extractChainId(normalizedAssetId);
454
500
  await this.getAssets([account], {
455
501
  chainIds: [chainId],
456
502
  forceUpdate: true,
503
+ updateMode: 'merge',
457
504
  });
458
505
  }
459
506
  }
@@ -524,6 +571,34 @@ export class AssetsController extends BaseController {
524
571
  });
525
572
  }
526
573
  // ============================================================================
574
+ // CURRENT CURRENCY MANAGEMENT
575
+ // ============================================================================
576
+ /**
577
+ * Set the current currency.
578
+ *
579
+ * @param selectedCurrency - The ISO 4217 currency code to set.
580
+ */
581
+ setSelectedCurrency(selectedCurrency) {
582
+ const previousCurrency = this.state.selectedCurrency;
583
+ if (previousCurrency === selectedCurrency) {
584
+ return;
585
+ }
586
+ this.update((state) => {
587
+ state.selectedCurrency = selectedCurrency;
588
+ });
589
+ log('Current currency changed', {
590
+ previousCurrency,
591
+ selectedCurrency,
592
+ });
593
+ this.getAssets(__classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get), {
594
+ forceUpdate: true,
595
+ dataTypes: ['price'],
596
+ assetsForPriceUpdate: Object.values(this.state.assetsBalance).flatMap((balances) => Object.keys(balances)),
597
+ }).catch((error) => {
598
+ log('Failed to fetch asset prices after current currency change', error);
599
+ });
600
+ }
601
+ // ============================================================================
527
602
  // SUBSCRIPTIONS
528
603
  // ============================================================================
529
604
  /**
@@ -601,9 +676,15 @@ export class AssetsController extends BaseController {
601
676
  hasBalance: Boolean(response.assetsBalance),
602
677
  hasPrice: Boolean(response.assetsPrice),
603
678
  });
604
- // Run through enrichment middlewares (Event Stack: Detection Token Price)
679
+ // Run through enrichment middlewares (Detection, then Token + Price in parallel)
605
680
  // 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 ?? {
681
+ const { response: enrichedResponse } = await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, [
682
+ __classPrivateFieldGet(this, _AssetsController_detectionMiddleware, "f"),
683
+ createParallelMiddleware([
684
+ __classPrivateFieldGet(this, _AssetsController_tokenDataSource, "f"),
685
+ __classPrivateFieldGet(this, _AssetsController_priceDataSource, "f"),
686
+ ]),
687
+ ], request ?? {
607
688
  accountsWithSupportedChains: [],
608
689
  chainIds: [],
609
690
  dataTypes: ['balance', 'metadata', 'price'],
@@ -635,6 +716,7 @@ export class AssetsController extends BaseController {
635
716
  this.messenger.unregisterActionHandler('AssetsController:getAssetsBalance');
636
717
  this.messenger.unregisterActionHandler('AssetsController:getAssetMetadata');
637
718
  this.messenger.unregisterActionHandler('AssetsController:getAssetsPrice');
719
+ this.messenger.unregisterActionHandler('AssetsController:getExchangeRatesForBridge');
638
720
  this.messenger.unregisterActionHandler('AssetsController:addCustomAsset');
639
721
  this.messenger.unregisterActionHandler('AssetsController:removeCustomAsset');
640
722
  this.messenger.unregisterActionHandler('AssetsController:getCustomAssets');
@@ -642,7 +724,7 @@ export class AssetsController extends BaseController {
642
724
  this.messenger.unregisterActionHandler('AssetsController:unhideAsset');
643
725
  }
644
726
  }
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() {
727
+ _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() {
646
728
  return this.messenger.call('AccountTreeController:getAccountsFromSelectedAccountGroup');
647
729
  }, _AssetsController_allBalanceDataSources_get = function _AssetsController_allBalanceDataSources_get() {
648
730
  return [
@@ -692,11 +774,60 @@ _AssetsController_isEnabled = new WeakMap(), _AssetsController_isBasicFunctional
692
774
  this.messenger.subscribe('NetworkEnablementController:stateChange', ({ enabledNetworkMap }) => {
693
775
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_handleEnabledNetworksChanged).call(this, enabledNetworkMap).catch(console.error);
694
776
  });
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));
777
+ // Client + Keyring lifecycle: only run when UI is open AND keyring is unlocked
778
+ this.messenger.subscribe('ClientController:stateChange', (isUiOpen) => {
779
+ __classPrivateFieldSet(this, _AssetsController_uiOpen, isUiOpen, "f");
780
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateActive).call(this);
781
+ }, clientControllerSelectors.selectIsUiOpen);
782
+ this.messenger.subscribe('KeyringController:unlock', () => {
783
+ __classPrivateFieldSet(this, _AssetsController_keyringUnlocked, true, "f");
784
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateActive).call(this);
785
+ });
786
+ this.messenger.subscribe('KeyringController:lock', () => {
787
+ __classPrivateFieldSet(this, _AssetsController_keyringUnlocked, false, "f");
788
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateActive).call(this);
789
+ });
790
+ }, _AssetsController_updateActive = function _AssetsController_updateActive() {
791
+ const shouldRun = __classPrivateFieldGet(this, _AssetsController_uiOpen, "f") && __classPrivateFieldGet(this, _AssetsController_keyringUnlocked, "f");
792
+ if (shouldRun) {
793
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_start).call(this);
794
+ }
795
+ else {
796
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_stop).call(this);
797
+ }
698
798
  }, _AssetsController_registerActionHandlers = function _AssetsController_registerActionHandlers() {
699
799
  this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
800
+ }, _AssetsController_handleActiveChainsUpdate = function _AssetsController_handleActiveChainsUpdate(dataSourceId, activeChains, previousChains) {
801
+ if (!__classPrivateFieldGet(this, _AssetsController_isEnabled, "f")) {
802
+ return;
803
+ }
804
+ log('Data source active chains changed', {
805
+ dataSourceId,
806
+ chainCount: activeChains.length,
807
+ chains: activeChains,
808
+ });
809
+ const previous = previousChains;
810
+ const previousSet = new Set(previous);
811
+ const addedChains = activeChains.filter((ch) => !previousSet.has(ch));
812
+ const removedChains = previous.filter((ch) => !activeChains.includes(ch));
813
+ if (addedChains.length > 0 || removedChains.length > 0) {
814
+ // Refresh subscriptions to use updated data source availability
815
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
816
+ }
817
+ // If chains were added and we have selected accounts, do one-time fetch
818
+ if (addedChains.length > 0 && __classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get).length > 0) {
819
+ const addedEnabledChains = addedChains.filter((chain) => __classPrivateFieldGet(this, _AssetsController_enabledChains, "f").has(chain));
820
+ if (addedEnabledChains.length > 0) {
821
+ log('Fetching balances for newly added chains', { addedEnabledChains });
822
+ this.getAssets(__classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get), {
823
+ chainIds: addedEnabledChains,
824
+ forceUpdate: true,
825
+ updateMode: 'merge',
826
+ }).catch((error) => {
827
+ log('Failed to fetch balance for added chains', { error });
828
+ });
829
+ }
830
+ }
700
831
  }, _AssetsController_executeMiddlewares =
701
832
  // ============================================================================
702
833
  // MIDDLEWARE EXECUTION
@@ -747,13 +878,46 @@ async function _AssetsController_executeMiddlewares(sources, request, initialRes
747
878
  }
748
879
  }
749
880
  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
881
+ }, _AssetsController_resolveNativeAssetIds = function _AssetsController_resolveNativeAssetIds(chains) {
882
+ const { nativeAssetIdentifiers } = this.messenger.call('NetworkEnablementController:getState');
883
+ const ids = [];
884
+ for (const chainId of chains) {
885
+ const nativeId = nativeAssetIdentifiers?.[chainId];
886
+ if (nativeId) {
887
+ ids.push(nativeId);
888
+ }
889
+ }
890
+ return ids;
891
+ }, _AssetsController_getNativeAssetIdsForEnabledChains = function _AssetsController_getNativeAssetIdsForEnabledChains() {
892
+ return __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_resolveNativeAssetIds).call(this, __classPrivateFieldGet(this, _AssetsController_enabledChains, "f"));
893
+ }, _AssetsController_getNativeAssetIdsForAccount = function _AssetsController_getNativeAssetIdsForAccount(account) {
894
+ return __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_resolveNativeAssetIds).call(this, __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getEnabledChainsForAccount).call(this, account));
895
+ }, _AssetsController_ensureNativeBalancesDefaultZero = function _AssetsController_ensureNativeBalancesDefaultZero() {
896
+ const accounts = __classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get);
897
+ if (accounts.length === 0) {
898
+ return;
899
+ }
900
+ this.update((state) => {
901
+ const balances = state.assetsBalance;
902
+ for (const account of accounts) {
903
+ const accountId = account.id;
904
+ const nativeAssetIds = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getNativeAssetIdsForAccount).call(this, account);
905
+ if (nativeAssetIds.length === 0) {
906
+ continue;
907
+ }
908
+ if (!balances[accountId]) {
909
+ balances[accountId] = {};
910
+ }
911
+ for (const nativeAssetId of nativeAssetIds) {
912
+ if (!(nativeAssetId in balances[accountId])) {
913
+ balances[accountId][nativeAssetId] = { amount: '0' };
914
+ }
915
+ }
916
+ }
917
+ });
918
+ }, _AssetsController_updateState = async function _AssetsController_updateState(response) {
756
919
  const normalizedResponse = normalizeResponse(response);
920
+ const mode = normalizedResponse.updateMode ?? 'merge';
757
921
  const releaseLock = await __classPrivateFieldGet(this, _AssetsController_controllerMutex, "f").acquire();
758
922
  try {
759
923
  const previousState = this.state;
@@ -779,16 +943,43 @@ async function _AssetsController_updateState(response) {
779
943
  if (normalizedResponse.assetsBalance) {
780
944
  for (const [accountId, accountBalances] of Object.entries(normalizedResponse.assetsBalance)) {
781
945
  const previousBalances = previousState.assetsBalance[accountId] ?? {};
782
- if (!balances[accountId]) {
783
- balances[accountId] = {};
946
+ const customAssetIds = state.customAssets[accountId] ?? [];
947
+ // Full: response is authoritative; preserve custom assets not in response.
948
+ // Merge: response overlays previous balances.
949
+ // Callers that fetch partial data (e.g. newly added chains) must set updateMode: 'merge'.
950
+ const effective = mode === 'merge'
951
+ ? { ...previousBalances, ...accountBalances }
952
+ : (() => {
953
+ const next = {
954
+ ...accountBalances,
955
+ };
956
+ for (const customId of customAssetIds) {
957
+ if (!(customId in next)) {
958
+ const prev = previousBalances[customId];
959
+ next[customId] =
960
+ prev ?? { amount: '0' };
961
+ }
962
+ }
963
+ return next;
964
+ })();
965
+ // Ensure native tokens have an entry (0 if missing) for chains this account supports
966
+ const account = __classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get).find((a) => a.id === accountId);
967
+ const nativeAssetIdsForAccount = account
968
+ ? __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getNativeAssetIdsForAccount).call(this, account)
969
+ : __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getNativeAssetIdsForEnabledChains).call(this);
970
+ for (const nativeAssetId of nativeAssetIdsForAccount) {
971
+ if (!(nativeAssetId in effective)) {
972
+ effective[nativeAssetId] = { amount: '0' };
973
+ }
784
974
  }
785
- for (const [assetId, balance] of Object.entries(accountBalances)) {
975
+ for (const [assetId, balance] of Object.entries(effective)) {
786
976
  const previousBalance = previousBalances[assetId];
787
- const balanceData = balance;
788
- const newAmount = balanceData.amount;
977
+ const newAmount = balance.amount;
789
978
  const oldAmount = previousBalance?.amount;
790
- // Track if balance actually changed
791
- if (oldAmount !== newAmount) {
979
+ const isNewDefaultNativeZero = oldAmount === undefined &&
980
+ newAmount === '0' &&
981
+ nativeAssetIdsForAccount.includes(assetId);
982
+ if (oldAmount !== newAmount && !isNewDefaultNativeZero) {
792
983
  changedBalances.push({
793
984
  accountId,
794
985
  assetId,
@@ -797,7 +988,7 @@ async function _AssetsController_updateState(response) {
797
988
  });
798
989
  }
799
990
  }
800
- Object.assign(balances[accountId], accountBalances);
991
+ balances[accountId] = effective;
801
992
  }
802
993
  }
803
994
  // Update prices in state
@@ -941,6 +1132,7 @@ async function _AssetsController_updateState(response) {
941
1132
  enabledChainCount: __classPrivateFieldGet(this, _AssetsController_enabledChains, "f").size,
942
1133
  });
943
1134
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1135
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
944
1136
  this.getAssets(__classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get), {
945
1137
  chainIds: [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")],
946
1138
  forceUpdate: true,
@@ -975,7 +1167,7 @@ async function _AssetsController_updateState(response) {
975
1167
  }
976
1168
  __classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").clear();
977
1169
  }, _AssetsController_subscribeAssets = function _AssetsController_subscribeAssets() {
978
- if (__classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get).length === 0) {
1170
+ if (__classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get).length === 0 || __classPrivateFieldGet(this, _AssetsController_enabledChains, "f").size === 0) {
979
1171
  return;
980
1172
  }
981
1173
  // Subscribe to balance updates (batched by data source)
@@ -1064,7 +1256,7 @@ async function _AssetsController_updateState(response) {
1064
1256
  }),
1065
1257
  subscriptionId: subscriptionKey,
1066
1258
  isUpdate,
1067
- onAssetsUpdate: (response) => this.handleAssetsUpdate(response, sourceId),
1259
+ onAssetsUpdate: (response, request) => this.handleAssetsUpdate(response, sourceId, request),
1068
1260
  getAssetsState: () => this.state,
1069
1261
  };
1070
1262
  source.subscribe(subscribeReq).catch((error) => {
@@ -1148,6 +1340,7 @@ async function _AssetsController_handleAccountGroupChanged() {
1148
1340
  forceUpdate: true,
1149
1341
  });
1150
1342
  }
1343
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
1151
1344
  }, _AssetsController_handleEnabledNetworksChanged = async function _AssetsController_handleEnabledNetworksChanged(enabledNetworkMap) {
1152
1345
  const previousChains = __classPrivateFieldGet(this, _AssetsController_enabledChains, "f");
1153
1346
  __classPrivateFieldSet(this, _AssetsController_enabledChains, __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_extractEnabledChains).call(this, enabledNetworkMap), "f");
@@ -1176,12 +1369,14 @@ async function _AssetsController_handleAccountGroupChanged() {
1176
1369
  // The data will simply not be updated until the network is re-enabled.
1177
1370
  // Refresh subscriptions for new chain set
1178
1371
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1179
- // Do one-time fetch for newly enabled chains
1372
+ // Do one-time fetch for newly enabled chains; merge so we keep existing chain balances
1180
1373
  if (addedChains.length > 0 && __classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get).length > 0) {
1181
1374
  await this.getAssets(__classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get), {
1182
1375
  chainIds: addedChains,
1183
1376
  forceUpdate: true,
1377
+ updateMode: 'merge',
1184
1378
  });
1185
1379
  }
1380
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_ensureNativeBalancesDefaultZero).call(this);
1186
1381
  };
1187
1382
  //# sourceMappingURL=AssetsController.mjs.map