@metamask/assets-controller 0.1.0 → 1.0.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 (132) hide show
  1. package/CHANGELOG.md +31 -3
  2. package/dist/AssetsController-method-action-types.cjs +7 -0
  3. package/dist/AssetsController-method-action-types.cjs.map +1 -0
  4. package/dist/AssetsController-method-action-types.d.cts +78 -0
  5. package/dist/AssetsController-method-action-types.d.cts.map +1 -0
  6. package/dist/AssetsController-method-action-types.d.mts +78 -0
  7. package/dist/AssetsController-method-action-types.d.mts.map +1 -0
  8. package/dist/AssetsController-method-action-types.mjs +6 -0
  9. package/dist/AssetsController-method-action-types.mjs.map +1 -0
  10. package/dist/AssetsController.cjs +273 -247
  11. package/dist/AssetsController.cjs.map +1 -1
  12. package/dist/AssetsController.d.cts +55 -93
  13. package/dist/AssetsController.d.cts.map +1 -1
  14. package/dist/AssetsController.d.mts +55 -93
  15. package/dist/AssetsController.d.mts.map +1 -1
  16. package/dist/AssetsController.mjs +273 -247
  17. package/dist/AssetsController.mjs.map +1 -1
  18. package/dist/data-sources/AbstractDataSource.cjs +8 -0
  19. package/dist/data-sources/AbstractDataSource.cjs.map +1 -1
  20. package/dist/data-sources/AbstractDataSource.d.cts +16 -1
  21. package/dist/data-sources/AbstractDataSource.d.cts.map +1 -1
  22. package/dist/data-sources/AbstractDataSource.d.mts +16 -1
  23. package/dist/data-sources/AbstractDataSource.d.mts.map +1 -1
  24. package/dist/data-sources/AbstractDataSource.mjs +8 -0
  25. package/dist/data-sources/AbstractDataSource.mjs.map +1 -1
  26. package/dist/data-sources/AccountsApiDataSource.cjs +25 -99
  27. package/dist/data-sources/AccountsApiDataSource.cjs.map +1 -1
  28. package/dist/data-sources/AccountsApiDataSource.d.cts +5 -67
  29. package/dist/data-sources/AccountsApiDataSource.d.cts.map +1 -1
  30. package/dist/data-sources/AccountsApiDataSource.d.mts +5 -67
  31. package/dist/data-sources/AccountsApiDataSource.d.mts.map +1 -1
  32. package/dist/data-sources/AccountsApiDataSource.mjs +24 -97
  33. package/dist/data-sources/AccountsApiDataSource.mjs.map +1 -1
  34. package/dist/data-sources/BackendWebsocketDataSource.cjs +138 -45
  35. package/dist/data-sources/BackendWebsocketDataSource.cjs.map +1 -1
  36. package/dist/data-sources/BackendWebsocketDataSource.d.cts +19 -66
  37. package/dist/data-sources/BackendWebsocketDataSource.d.cts.map +1 -1
  38. package/dist/data-sources/BackendWebsocketDataSource.d.mts +19 -66
  39. package/dist/data-sources/BackendWebsocketDataSource.d.mts.map +1 -1
  40. package/dist/data-sources/BackendWebsocketDataSource.mjs +138 -45
  41. package/dist/data-sources/BackendWebsocketDataSource.mjs.map +1 -1
  42. package/dist/data-sources/PriceDataSource.cjs +22 -44
  43. package/dist/data-sources/PriceDataSource.cjs.map +1 -1
  44. package/dist/data-sources/PriceDataSource.d.cts +6 -89
  45. package/dist/data-sources/PriceDataSource.d.cts.map +1 -1
  46. package/dist/data-sources/PriceDataSource.d.mts +6 -89
  47. package/dist/data-sources/PriceDataSource.d.mts.map +1 -1
  48. package/dist/data-sources/PriceDataSource.mjs +22 -44
  49. package/dist/data-sources/PriceDataSource.mjs.map +1 -1
  50. package/dist/data-sources/RpcDataSource.cjs +64 -102
  51. package/dist/data-sources/RpcDataSource.cjs.map +1 -1
  52. package/dist/data-sources/RpcDataSource.d.cts +16 -62
  53. package/dist/data-sources/RpcDataSource.d.cts.map +1 -1
  54. package/dist/data-sources/RpcDataSource.d.mts +16 -62
  55. package/dist/data-sources/RpcDataSource.d.mts.map +1 -1
  56. package/dist/data-sources/RpcDataSource.mjs +64 -102
  57. package/dist/data-sources/RpcDataSource.mjs.map +1 -1
  58. package/dist/data-sources/SnapDataSource.cjs +32 -30
  59. package/dist/data-sources/SnapDataSource.cjs.map +1 -1
  60. package/dist/data-sources/SnapDataSource.d.cts +7 -44
  61. package/dist/data-sources/SnapDataSource.d.cts.map +1 -1
  62. package/dist/data-sources/SnapDataSource.d.mts +7 -44
  63. package/dist/data-sources/SnapDataSource.d.mts.map +1 -1
  64. package/dist/data-sources/SnapDataSource.mjs +32 -30
  65. package/dist/data-sources/SnapDataSource.mjs.map +1 -1
  66. package/dist/data-sources/TokenDataSource.cjs +3 -16
  67. package/dist/data-sources/TokenDataSource.cjs.map +1 -1
  68. package/dist/data-sources/TokenDataSource.d.cts +2 -25
  69. package/dist/data-sources/TokenDataSource.d.cts.map +1 -1
  70. package/dist/data-sources/TokenDataSource.d.mts +2 -25
  71. package/dist/data-sources/TokenDataSource.d.mts.map +1 -1
  72. package/dist/data-sources/TokenDataSource.mjs +3 -16
  73. package/dist/data-sources/TokenDataSource.mjs.map +1 -1
  74. package/dist/data-sources/evm-rpc-services/clients/MulticallClient.cjs +27 -35
  75. package/dist/data-sources/evm-rpc-services/clients/MulticallClient.cjs.map +1 -1
  76. package/dist/data-sources/evm-rpc-services/clients/MulticallClient.d.cts.map +1 -1
  77. package/dist/data-sources/evm-rpc-services/clients/MulticallClient.d.mts.map +1 -1
  78. package/dist/data-sources/evm-rpc-services/clients/MulticallClient.mjs +27 -35
  79. package/dist/data-sources/evm-rpc-services/clients/MulticallClient.mjs.map +1 -1
  80. package/dist/data-sources/index.cjs +1 -6
  81. package/dist/data-sources/index.cjs.map +1 -1
  82. package/dist/data-sources/index.d.cts +6 -7
  83. package/dist/data-sources/index.d.cts.map +1 -1
  84. package/dist/data-sources/index.d.mts +6 -7
  85. package/dist/data-sources/index.d.mts.map +1 -1
  86. package/dist/data-sources/index.mjs +1 -3
  87. package/dist/data-sources/index.mjs.map +1 -1
  88. package/dist/index.cjs +6 -6
  89. package/dist/index.cjs.map +1 -1
  90. package/dist/index.d.cts +11 -11
  91. package/dist/index.d.cts.map +1 -1
  92. package/dist/index.d.mts +11 -11
  93. package/dist/index.d.mts.map +1 -1
  94. package/dist/index.mjs +3 -3
  95. package/dist/index.mjs.map +1 -1
  96. package/dist/middlewares/DetectionMiddleware.cjs +4 -27
  97. package/dist/middlewares/DetectionMiddleware.cjs.map +1 -1
  98. package/dist/middlewares/DetectionMiddleware.d.cts +3 -26
  99. package/dist/middlewares/DetectionMiddleware.d.cts.map +1 -1
  100. package/dist/middlewares/DetectionMiddleware.d.mts +3 -26
  101. package/dist/middlewares/DetectionMiddleware.d.mts.map +1 -1
  102. package/dist/middlewares/DetectionMiddleware.mjs +4 -27
  103. package/dist/middlewares/DetectionMiddleware.mjs.map +1 -1
  104. package/dist/middlewares/index.cjs.map +1 -1
  105. package/dist/middlewares/index.d.cts +1 -1
  106. package/dist/middlewares/index.d.cts.map +1 -1
  107. package/dist/middlewares/index.d.mts +1 -1
  108. package/dist/middlewares/index.d.mts.map +1 -1
  109. package/dist/middlewares/index.mjs.map +1 -1
  110. package/dist/selectors/balance.cjs +306 -0
  111. package/dist/selectors/balance.cjs.map +1 -0
  112. package/dist/selectors/balance.d.cts +22 -0
  113. package/dist/selectors/balance.d.cts.map +1 -0
  114. package/dist/selectors/balance.d.mts +22 -0
  115. package/dist/selectors/balance.d.mts.map +1 -0
  116. package/dist/selectors/balance.mjs +297 -0
  117. package/dist/selectors/balance.mjs.map +1 -0
  118. package/dist/types.cjs.map +1 -1
  119. package/dist/types.d.cts +63 -18
  120. package/dist/types.d.cts.map +1 -1
  121. package/dist/types.d.mts +63 -18
  122. package/dist/types.d.mts.map +1 -1
  123. package/dist/types.mjs.map +1 -1
  124. package/package.json +4 -4
  125. package/dist/data-sources/initDataSources.cjs +0 -215
  126. package/dist/data-sources/initDataSources.cjs.map +0 -1
  127. package/dist/data-sources/initDataSources.d.cts +0 -140
  128. package/dist/data-sources/initDataSources.d.cts.map +0 -1
  129. package/dist/data-sources/initDataSources.d.mts +0 -140
  130. package/dist/data-sources/initDataSources.d.mts.map +0 -1
  131. package/dist/data-sources/initDataSources.mjs +0 -210
  132. package/dist/data-sources/initDataSources.mjs.map +0 -1
@@ -1,34 +1,53 @@
1
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
2
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
3
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
4
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5
+ };
1
6
  var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
7
  if (kind === "m") throw new TypeError("Private method is not writable");
3
8
  if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
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");
5
10
  return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
11
  };
7
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
- };
12
- var _AssetsController_instances, _AssetsController_isEnabled, _AssetsController_defaultUpdateInterval, _AssetsController_controllerMutex, _AssetsController_activeSubscriptions, _AssetsController_enabledChains, _AssetsController_selectedAccounts_get, _AssetsController_dataSources, _AssetsController_initializeState, _AssetsController_extractEnabledChains, _AssetsController_normalizeChainReference, _AssetsController_subscribeToEvents, _AssetsController_registerActionHandlers, _AssetsController_executeMiddlewares, _AssetsController_assignChainsToDataSources, _AssetsController_updateState, _AssetsController_getAssetsFromState, _AssetsController_tokenStandardToAssetType, _AssetsController_start, _AssetsController_stop, _AssetsController_subscribeToDataSources, _AssetsController_subscribeAssetsBalance, _AssetsController_buildChainToAccountsMap, _AssetsController_getAccountsForChains, _AssetsController_subscribeToDataSource, _AssetsController_unsubscribeDataSource, _AssetsController_getEnabledChainsForAccount, _AssetsController_handleAccountGroupChanged, _AssetsController_handleEnabledNetworksChanged, _AssetsController_handleSubscriptionUpdate;
12
+ var _AssetsController_instances, _AssetsController_isEnabled, _AssetsController_defaultUpdateInterval, _AssetsController_controllerMutex, _AssetsController_activeSubscriptions, _AssetsController_enabledChains, _AssetsController_selectedAccounts_get, _AssetsController_backendWebsocketDataSource, _AssetsController_accountsApiDataSource, _AssetsController_snapDataSource, _AssetsController_rpcDataSource, _AssetsController_subscriptionBalanceDataSources_get, _AssetsController_priceDataSource, _AssetsController_detectionMiddleware, _AssetsController_tokenDataSource, _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_buildChainToAccountsMap, _AssetsController_subscribeDataSource, _AssetsController_unsubscribeDataSource, _AssetsController_buildDataRequest, _AssetsController_getEnabledChainsForAccount, _AssetsController_handleAccountGroupChanged, _AssetsController_handleEnabledNetworksChanged;
13
13
  import { BaseController } from "@metamask/base-controller";
14
14
  import { parseCaipAssetType } from "@metamask/utils";
15
15
  import { Mutex } from "async-mutex";
16
16
  import BigNumberJS from "bignumber.js";
17
17
  import $lodash from "lodash";
18
18
  const { isEqual } = $lodash;
19
+ import { AccountsApiDataSource } from "./data-sources/AccountsApiDataSource.mjs";
20
+ import { BackendWebsocketDataSource } from "./data-sources/BackendWebsocketDataSource.mjs";
21
+ import { PriceDataSource } from "./data-sources/PriceDataSource.mjs";
22
+ import { RpcDataSource } from "./data-sources/RpcDataSource.mjs";
23
+ import { SnapDataSource } from "./data-sources/SnapDataSource.mjs";
24
+ import { TokenDataSource } from "./data-sources/TokenDataSource.mjs";
19
25
  import { projectLogger, createModuleLogger } from "./logger.mjs";
26
+ import { DetectionMiddleware } from "./middlewares/DetectionMiddleware.mjs";
20
27
  import { normalizeAssetId } from "./utils.mjs";
21
28
  // ============================================================================
22
29
  // CONTROLLER CONSTANTS
23
30
  // ============================================================================
24
31
  const CONTROLLER_NAME = 'AssetsController';
32
+ /** Method names exposed as messenger actions (AssetsController:getAssets, etc.) */
33
+ const MESSENGER_EXPOSED_METHODS = [
34
+ 'getAssets',
35
+ 'getAssetsBalance',
36
+ 'getAssetMetadata',
37
+ 'getAssetsPrice',
38
+ 'addCustomAsset',
39
+ 'removeCustomAsset',
40
+ 'getCustomAssets',
41
+ 'hideAsset',
42
+ 'unhideAsset',
43
+ ];
25
44
  /** Default polling interval hint for data sources (30 seconds) */
26
45
  const DEFAULT_POLLING_INTERVAL_MS = 30000;
27
46
  const log = createModuleLogger(projectLogger, CONTROLLER_NAME);
28
47
  /**
29
48
  * Returns the default state for AssetsController.
30
49
  *
31
- * @returns The default AssetsController state with empty metadata, balance, price, and customAssets maps.
50
+ * @returns The default AssetsController state with empty maps.
32
51
  */
33
52
  export function getDefaultAssetsControllerState() {
34
53
  return {
@@ -36,6 +55,7 @@ export function getDefaultAssetsControllerState() {
36
55
  assetsBalance: {},
37
56
  assetsPrice: {},
38
57
  customAssets: {},
58
+ assetPreferences: {},
39
59
  };
40
60
  }
41
61
  // ============================================================================
@@ -66,6 +86,12 @@ const stateMetadata = {
66
86
  includeInDebugSnapshot: false,
67
87
  usedInUi: true,
68
88
  },
89
+ assetPreferences: {
90
+ persist: true,
91
+ includeInStateLogs: false,
92
+ includeInDebugSnapshot: false,
93
+ usedInUi: true,
94
+ },
69
95
  };
70
96
  // ============================================================================
71
97
  // HELPER FUNCTIONS
@@ -140,13 +166,8 @@ function normalizeResponse(response) {
140
166
  * based on which chains they support. When active chains change, the controller
141
167
  * dynamically adjusts subscriptions.
142
168
  *
143
- * 4. **App Lifecycle Management**: Listens to app open/close events via messenger
144
- * to start/stop subscriptions automatically, conserving resources when app is closed.
145
- *
146
- * ## App Lifecycle
147
- *
148
- * - **App Opened** (`AppStateController:appOpened`): Starts subscriptions, fetches initial data
149
- * - **App Closed** (`AppStateController:appClosed`): Stops all subscriptions to conserve resources
169
+ * 4. **Keyring Lifecycle**: Listens to KeyringController unlock/lock events to
170
+ * start/stop subscriptions when the wallet is unlocked or locked.
150
171
  *
151
172
  * ## Architecture
152
173
  *
@@ -155,7 +176,7 @@ function normalizeResponse(response) {
155
176
  * - The controller does NOT manage polling - it simply receives pushed updates
156
177
  */
157
178
  export class AssetsController extends BaseController {
158
- constructor({ messenger, state = {}, defaultUpdateInterval = DEFAULT_POLLING_INTERVAL_MS, isEnabled = () => true, }) {
179
+ constructor({ messenger, state = {}, defaultUpdateInterval = DEFAULT_POLLING_INTERVAL_MS, isEnabled = () => true, queryApiClient, rpcDataSourceConfig, }) {
159
180
  super({
160
181
  name: CONTROLLER_NAME,
161
182
  messenger,
@@ -180,79 +201,82 @@ export class AssetsController extends BaseController {
180
201
  _AssetsController_activeSubscriptions.set(this, new Map());
181
202
  /** Currently enabled chains from NetworkEnablementController */
182
203
  _AssetsController_enabledChains.set(this, new Set());
183
- /**
184
- * Registered data sources with their available chains.
185
- * Updated continuously and independently from subscription flows.
186
- * Key: sourceId, Value: Set of currently available chainIds
187
- */
188
- _AssetsController_dataSources.set(this, new Map());
204
+ _AssetsController_backendWebsocketDataSource.set(this, void 0);
205
+ _AssetsController_accountsApiDataSource.set(this, void 0);
206
+ _AssetsController_snapDataSource.set(this, void 0);
207
+ _AssetsController_rpcDataSource.set(this, void 0);
208
+ _AssetsController_priceDataSource.set(this, void 0);
209
+ _AssetsController_detectionMiddleware.set(this, void 0);
210
+ _AssetsController_tokenDataSource.set(this, void 0);
189
211
  __classPrivateFieldSet(this, _AssetsController_isEnabled, isEnabled(), "f");
190
212
  __classPrivateFieldSet(this, _AssetsController_defaultUpdateInterval, defaultUpdateInterval, "f");
213
+ const rpcConfig = rpcDataSourceConfig ?? {};
214
+ const onActiveChainsUpdated = (dataSourceName, chains, previousChains) => this.handleActiveChainsUpdate(dataSourceName, chains, previousChains);
215
+ __classPrivateFieldSet(this, _AssetsController_backendWebsocketDataSource, new BackendWebsocketDataSource({
216
+ messenger: this.messenger,
217
+ queryApiClient,
218
+ onActiveChainsUpdated,
219
+ }), "f");
220
+ __classPrivateFieldSet(this, _AssetsController_accountsApiDataSource, new AccountsApiDataSource({
221
+ queryApiClient,
222
+ onActiveChainsUpdated,
223
+ }), "f");
224
+ __classPrivateFieldSet(this, _AssetsController_snapDataSource, new SnapDataSource({
225
+ messenger: this.messenger,
226
+ onActiveChainsUpdated,
227
+ }), "f");
228
+ __classPrivateFieldSet(this, _AssetsController_rpcDataSource, new RpcDataSource({
229
+ messenger: this.messenger,
230
+ onActiveChainsUpdated,
231
+ ...rpcConfig,
232
+ }), "f");
233
+ __classPrivateFieldSet(this, _AssetsController_tokenDataSource, new TokenDataSource({
234
+ queryApiClient,
235
+ }), "f");
236
+ __classPrivateFieldSet(this, _AssetsController_priceDataSource, new PriceDataSource({
237
+ queryApiClient,
238
+ }), "f");
239
+ __classPrivateFieldSet(this, _AssetsController_detectionMiddleware, new DetectionMiddleware(), "f");
191
240
  if (!__classPrivateFieldGet(this, _AssetsController_isEnabled, "f")) {
192
241
  log('AssetsController is disabled, skipping initialization');
193
242
  return;
194
243
  }
195
244
  log('Initializing AssetsController', {
196
- defaultUpdateInterval,
245
+ defaultUpdateInterval: __classPrivateFieldGet(this, _AssetsController_defaultUpdateInterval, "f"),
197
246
  });
198
247
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_initializeState).call(this);
199
248
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeToEvents).call(this);
200
249
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_registerActionHandlers).call(this);
201
- // Register data sources (order = subscription priority)
202
- this.registerDataSources([
203
- 'BackendWebsocketDataSource', // Real-time push updates
204
- 'AccountsApiDataSource', // HTTP polling fallback
205
- 'SnapDataSource', // Solana/Bitcoin/Tron snaps
206
- 'RpcDataSource', // Direct blockchain queries
207
- ]);
208
- }
209
- // ============================================================================
210
- // DATA SOURCE MANAGEMENT
211
- // ============================================================================
212
- /**
213
- * Register data sources with the controller.
214
- * Order of the array determines subscription order.
215
- *
216
- * Data sources report chain changes by calling `AssetsController:activeChainsUpdate` action.
217
- *
218
- * @param dataSourceIds - Array of data source identifiers to register.
219
- */
220
- registerDataSources(dataSourceIds) {
221
- for (const id of dataSourceIds) {
222
- log('Registering data source', { id });
223
- // Initialize available chains tracking for this source
224
- __classPrivateFieldGet(this, _AssetsController_dataSources, "f").set(id, new Set());
225
- }
226
250
  }
227
251
  // ============================================================================
228
252
  // DATA SOURCE CHAIN MANAGEMENT
229
253
  // ============================================================================
230
254
  /**
231
- * Handle when a data source's active chains change.
232
- * Active chains are chains that are both supported AND available.
233
- * Updates centralized chain tracking and triggers re-selection if needed.
255
+ * Handle when a data source's supported chains change.
256
+ * Used to refresh balance subscriptions and run a one-time fetch when a new chain is supported.
257
+ *
258
+ * - On any add/remove: re-subscribes to data sources so chain assignment stays correct.
259
+ * - When chains are added: fetches balances for the new chains (for selected accounts on enabled networks).
234
260
  *
235
- * Data sources should call this via `AssetsController:activeChainsUpdate` action.
261
+ * Controller does not store chains; sources report via this callback. previousChains is required for diff.
236
262
  *
237
263
  * @param dataSourceId - The identifier of the data source reporting the change.
238
- * @param activeChains - Array of currently active chain IDs for this source.
264
+ * @param activeChains - Currently active (supported and available) chain IDs for this source.
265
+ * @param previousChains - Previous chains; used to compute added/removed.
239
266
  */
240
- handleActiveChainsUpdate(dataSourceId, activeChains) {
267
+ handleActiveChainsUpdate(dataSourceId, activeChains, previousChains) {
241
268
  log('Data source active chains changed', {
242
269
  dataSourceId,
243
270
  chainCount: activeChains.length,
244
271
  chains: activeChains,
245
272
  });
246
- const previousChains = __classPrivateFieldGet(this, _AssetsController_dataSources, "f").get(dataSourceId) ?? new Set();
247
- const newChains = new Set(activeChains);
248
- // Update centralized available chains tracking
249
- __classPrivateFieldGet(this, _AssetsController_dataSources, "f").set(dataSourceId, newChains);
250
- // Check for changes
251
- const addedChains = activeChains.filter((chain) => !previousChains.has(chain));
252
- const removedChains = Array.from(previousChains).filter((chain) => !newChains.has(chain));
273
+ const previous = previousChains;
274
+ const previousSet = new Set(previous);
275
+ const addedChains = activeChains.filter((ch) => !previousSet.has(ch));
276
+ const removedChains = previous.filter((ch) => !activeChains.includes(ch));
253
277
  if (addedChains.length > 0 || removedChains.length > 0) {
254
278
  // Refresh subscriptions to use updated data source availability
255
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeToDataSources).call(this);
279
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
256
280
  }
257
281
  // If chains were added and we have selected accounts, do one-time fetch
258
282
  if (addedChains.length > 0 && __classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get).length > 0) {
@@ -282,21 +306,20 @@ export class AssetsController extends BaseController {
282
306
  customAssets.push(...accountCustomAssets);
283
307
  }
284
308
  if (options?.forceUpdate) {
285
- const response = await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, [
286
- this.messenger.call('AccountsApiDataSource:getAssetsMiddleware'),
287
- this.messenger.call('SnapDataSource:getAssetsMiddleware'),
288
- this.messenger.call('RpcDataSource:getAssetsMiddleware'),
289
- this.messenger.call('DetectionMiddleware:getAssetsMiddleware'),
290
- this.messenger.call('TokenDataSource:getAssetsMiddleware'),
291
- this.messenger.call('PriceDataSource:getAssetsMiddleware'),
292
- ], {
293
- accounts,
294
- chainIds,
309
+ const request = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_buildDataRequest).call(this, accounts, chainIds, {
295
310
  assetTypes,
296
311
  dataTypes,
297
312
  customAssets: customAssets.length > 0 ? customAssets : undefined,
298
313
  forceUpdate: true,
299
314
  });
315
+ const response = await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, [
316
+ __classPrivateFieldGet(this, _AssetsController_accountsApiDataSource, "f").assetsMiddleware,
317
+ __classPrivateFieldGet(this, _AssetsController_snapDataSource, "f").assetsMiddleware,
318
+ __classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f").assetsMiddleware,
319
+ __classPrivateFieldGet(this, _AssetsController_detectionMiddleware, "f").assetsMiddleware,
320
+ __classPrivateFieldGet(this, _AssetsController_tokenDataSource, "f").assetsMiddleware,
321
+ __classPrivateFieldGet(this, _AssetsController_priceDataSource, "f").assetsMiddleware,
322
+ ], request);
300
323
  await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, response);
301
324
  }
302
325
  return __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getAssetsFromState).call(this, accounts, chainIds, assetTypes);
@@ -348,6 +371,7 @@ export class AssetsController extends BaseController {
348
371
  /**
349
372
  * Add a custom asset for an account.
350
373
  * Custom assets are included in subscription and fetch operations.
374
+ * Adding a custom asset also unhides it if it was previously hidden.
351
375
  *
352
376
  * @param accountId - The account ID to add the custom asset for.
353
377
  * @param assetId - The CAIP-19 asset ID to add.
@@ -364,6 +388,14 @@ export class AssetsController extends BaseController {
364
388
  if (!customAssets[accountId].includes(normalizedAssetId)) {
365
389
  customAssets[accountId].push(normalizedAssetId);
366
390
  }
391
+ // Unhide the asset if it was hidden (via assetPreferences)
392
+ const prefs = state.assetPreferences[normalizedAssetId];
393
+ if (prefs?.hidden) {
394
+ delete prefs.hidden;
395
+ if (Object.keys(prefs).length === 0) {
396
+ delete state.assetPreferences[normalizedAssetId];
397
+ }
398
+ }
367
399
  });
368
400
  // Fetch data for the newly added custom asset
369
401
  const account = __classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get).find((a) => a.id === accountId);
@@ -385,12 +417,11 @@ export class AssetsController extends BaseController {
385
417
  const normalizedAssetId = normalizeAssetId(assetId);
386
418
  log('Removing custom asset', { accountId, assetId: normalizedAssetId });
387
419
  this.update((state) => {
388
- const customAssets = state.customAssets;
389
- if (customAssets[accountId]) {
390
- customAssets[accountId] = customAssets[accountId].filter((id) => id !== normalizedAssetId);
420
+ if (state.customAssets[accountId]) {
421
+ state.customAssets[accountId] = state.customAssets[accountId].filter((id) => id !== normalizedAssetId);
391
422
  // Clean up empty arrays
392
- if (customAssets[accountId].length === 0) {
393
- delete customAssets[accountId];
423
+ if (state.customAssets[accountId].length === 0) {
424
+ delete state.customAssets[accountId];
394
425
  }
395
426
  }
396
427
  });
@@ -402,8 +433,49 @@ export class AssetsController extends BaseController {
402
433
  * @returns Array of CAIP-19 asset IDs for the account's custom assets.
403
434
  */
404
435
  getCustomAssets(accountId) {
405
- return (this.state.customAssets[accountId] ?? []);
436
+ return this.state.customAssets[accountId] ?? [];
406
437
  }
438
+ // ============================================================================
439
+ // HIDDEN ASSETS MANAGEMENT
440
+ // ============================================================================
441
+ /**
442
+ * Hide an asset globally.
443
+ * Hidden assets are excluded from the asset list returned by getAssets.
444
+ * The hidden state is stored in assetPreferences.
445
+ *
446
+ * @param assetId - The CAIP-19 asset ID to hide.
447
+ */
448
+ hideAsset(assetId) {
449
+ const normalizedAssetId = normalizeAssetId(assetId);
450
+ log('Hiding asset', { assetId: normalizedAssetId });
451
+ this.update((state) => {
452
+ if (!state.assetPreferences[normalizedAssetId]) {
453
+ state.assetPreferences[normalizedAssetId] = {};
454
+ }
455
+ state.assetPreferences[normalizedAssetId].hidden = true;
456
+ });
457
+ }
458
+ /**
459
+ * Unhide an asset globally.
460
+ *
461
+ * @param assetId - The CAIP-19 asset ID to unhide.
462
+ */
463
+ unhideAsset(assetId) {
464
+ const normalizedAssetId = normalizeAssetId(assetId);
465
+ log('Unhiding asset', { assetId: normalizedAssetId });
466
+ this.update((state) => {
467
+ const prefs = state.assetPreferences[normalizedAssetId];
468
+ if (prefs) {
469
+ delete prefs.hidden;
470
+ if (Object.keys(prefs).length === 0) {
471
+ delete state.assetPreferences[normalizedAssetId];
472
+ }
473
+ }
474
+ });
475
+ }
476
+ // ============================================================================
477
+ // SUBSCRIPTIONS
478
+ // ============================================================================
407
479
  /**
408
480
  * Subscribe to price updates for all assets held by the given accounts.
409
481
  * Polls PriceDataSource which fetches prices from balance state.
@@ -418,19 +490,17 @@ export class AssetsController extends BaseController {
418
490
  const subscriptionKey = 'ds:PriceDataSource';
419
491
  const existingSubscription = __classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").get(subscriptionKey);
420
492
  const isUpdate = existingSubscription !== undefined;
421
- // Fire-and-forget - errors are handled internally by PriceDataSource
422
- this.messenger
423
- .call('PriceDataSource:subscribe', {
424
- request: {
425
- accounts,
426
- chainIds,
493
+ const subscribeReq = {
494
+ request: __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_buildDataRequest).call(this, accounts, chainIds, {
427
495
  dataTypes: ['price'],
428
496
  updateInterval,
429
- },
497
+ }),
430
498
  subscriptionId: subscriptionKey,
431
499
  isUpdate,
432
- })
433
- .catch(console.error);
500
+ onAssetsUpdate: (response) => this.handleAssetsUpdate(response, 'PriceDataSource'),
501
+ getAssetsState: () => this.state,
502
+ };
503
+ __classPrivateFieldGet(this, _AssetsController_priceDataSource, "f").subscribe(subscribeReq).catch(console.error);
434
504
  // Track subscription
435
505
  const subscription = {
436
506
  chains: chainIds,
@@ -452,37 +522,55 @@ export class AssetsController extends BaseController {
452
522
  if (!existingSubscription) {
453
523
  return;
454
524
  }
455
- // Fire-and-forget - errors are handled internally by PriceDataSource
456
- this.messenger
457
- .call('PriceDataSource:unsubscribe', subscriptionKey)
458
- .catch(console.error);
525
+ __classPrivateFieldGet(this, _AssetsController_priceDataSource, "f").unsubscribe(subscriptionKey).catch(console.error);
459
526
  existingSubscription.unsubscribe();
460
527
  }
461
528
  /**
462
529
  * Handle assets updated from a data source.
463
- * Called via `AssetsController:assetsUpdate` action by data sources.
530
+ * Called via the onAssetsUpdate callback passed in SubscriptionRequest when the controller subscribes to a data source.
531
+ * Enriches the response with token metadata (via middlewares) before updating state.
464
532
  *
465
533
  * @param response - The data response with updated assets
466
534
  * @param sourceId - The data source ID reporting the update
535
+ * @param request - Optional original request for context when enriching
467
536
  */
468
- async handleAssetsUpdate(response, sourceId) {
537
+ async handleAssetsUpdate(response, sourceId, request) {
469
538
  log('Assets updated from data source', {
470
539
  sourceId,
471
540
  hasBalance: Boolean(response.assetsBalance),
472
541
  hasPrice: Boolean(response.assetsPrice),
473
542
  });
474
- await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_handleSubscriptionUpdate).call(this, response, sourceId);
543
+ // Run through enrichment middlewares (Event Stack: Detection → Token → Price)
544
+ // Include 'metadata' in dataTypes so TokenDataSource runs to enrich detected assets
545
+ const enrichedResponse = await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, [
546
+ __classPrivateFieldGet(this, _AssetsController_detectionMiddleware, "f").assetsMiddleware,
547
+ __classPrivateFieldGet(this, _AssetsController_tokenDataSource, "f").assetsMiddleware,
548
+ __classPrivateFieldGet(this, _AssetsController_priceDataSource, "f").assetsMiddleware,
549
+ ], request ?? {
550
+ accountsWithSupportedChains: [],
551
+ chainIds: [],
552
+ dataTypes: ['balance', 'metadata', 'price'],
553
+ }, response);
554
+ await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, enrichedResponse);
475
555
  }
476
556
  // ============================================================================
477
557
  // CLEANUP
478
558
  // ============================================================================
479
559
  destroy() {
480
560
  log('Destroying AssetsController', {
481
- dataSourceCount: __classPrivateFieldGet(this, _AssetsController_dataSources, "f").size,
561
+ dataSourceCount: __classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_subscriptionBalanceDataSources_get).length,
482
562
  subscriptionCount: __classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").size,
483
563
  });
484
- // Clear data sources
485
- __classPrivateFieldGet(this, _AssetsController_dataSources, "f").clear();
564
+ // Destroy instantiated data sources
565
+ __classPrivateFieldGet(this, _AssetsController_backendWebsocketDataSource, "f")?.destroy?.();
566
+ __classPrivateFieldGet(this, _AssetsController_accountsApiDataSource, "f")?.destroy?.();
567
+ __classPrivateFieldGet(this, _AssetsController_snapDataSource, "f")?.destroy?.();
568
+ if (__classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f") &&
569
+ 'destroy' in __classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f") &&
570
+ typeof __classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f").destroy ===
571
+ 'function') {
572
+ __classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f").destroy();
573
+ }
486
574
  // Stop all active subscriptions
487
575
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_stop).call(this);
488
576
  // Unregister action handlers
@@ -490,15 +578,22 @@ export class AssetsController extends BaseController {
490
578
  this.messenger.unregisterActionHandler('AssetsController:getAssetsBalance');
491
579
  this.messenger.unregisterActionHandler('AssetsController:getAssetMetadata');
492
580
  this.messenger.unregisterActionHandler('AssetsController:getAssetsPrice');
493
- this.messenger.unregisterActionHandler('AssetsController:activeChainsUpdate');
494
- this.messenger.unregisterActionHandler('AssetsController:assetsUpdate');
495
581
  this.messenger.unregisterActionHandler('AssetsController:addCustomAsset');
496
582
  this.messenger.unregisterActionHandler('AssetsController:removeCustomAsset');
497
583
  this.messenger.unregisterActionHandler('AssetsController:getCustomAssets');
584
+ this.messenger.unregisterActionHandler('AssetsController:hideAsset');
585
+ this.messenger.unregisterActionHandler('AssetsController:unhideAsset');
498
586
  }
499
587
  }
500
- _AssetsController_isEnabled = new WeakMap(), _AssetsController_defaultUpdateInterval = new WeakMap(), _AssetsController_controllerMutex = new WeakMap(), _AssetsController_activeSubscriptions = new WeakMap(), _AssetsController_enabledChains = new WeakMap(), _AssetsController_dataSources = new WeakMap(), _AssetsController_instances = new WeakSet(), _AssetsController_selectedAccounts_get = function _AssetsController_selectedAccounts_get() {
588
+ _AssetsController_isEnabled = new WeakMap(), _AssetsController_defaultUpdateInterval = 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_priceDataSource = new WeakMap(), _AssetsController_detectionMiddleware = new WeakMap(), _AssetsController_tokenDataSource = new WeakMap(), _AssetsController_instances = new WeakSet(), _AssetsController_selectedAccounts_get = function _AssetsController_selectedAccounts_get() {
501
589
  return this.messenger.call('AccountTreeController:getAccountsFromSelectedAccountGroup');
590
+ }, _AssetsController_subscriptionBalanceDataSources_get = function _AssetsController_subscriptionBalanceDataSources_get() {
591
+ return [
592
+ __classPrivateFieldGet(this, _AssetsController_backendWebsocketDataSource, "f"),
593
+ __classPrivateFieldGet(this, _AssetsController_accountsApiDataSource, "f"),
594
+ __classPrivateFieldGet(this, _AssetsController_snapDataSource, "f"),
595
+ __classPrivateFieldGet(this, _AssetsController_rpcDataSource, "f"),
596
+ ];
502
597
  }, _AssetsController_initializeState = function _AssetsController_initializeState() {
503
598
  const { enabledNetworkMap } = this.messenger.call('NetworkEnablementController:getState');
504
599
  __classPrivateFieldSet(this, _AssetsController_enabledChains, __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_extractEnabledChains).call(this, enabledNetworkMap), "f");
@@ -540,22 +635,11 @@ _AssetsController_isEnabled = new WeakMap(), _AssetsController_defaultUpdateInte
540
635
  this.messenger.subscribe('NetworkEnablementController:stateChange', ({ enabledNetworkMap }) => {
541
636
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_handleEnabledNetworksChanged).call(this, enabledNetworkMap).catch(console.error);
542
637
  });
543
- // App lifecycle: start when opened, stop when closed
544
- this.messenger.subscribe('AppStateController:appOpened', () => __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_start).call(this));
545
- this.messenger.subscribe('AppStateController:appClosed', () => __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_stop).call(this));
546
638
  // Keyring lifecycle: start when unlocked, stop when locked
547
639
  this.messenger.subscribe('KeyringController:unlock', () => __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_start).call(this));
548
640
  this.messenger.subscribe('KeyringController:lock', () => __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_stop).call(this));
549
641
  }, _AssetsController_registerActionHandlers = function _AssetsController_registerActionHandlers() {
550
- this.messenger.registerActionHandler('AssetsController:getAssets', this.getAssets.bind(this));
551
- this.messenger.registerActionHandler('AssetsController:getAssetsBalance', this.getAssetsBalance.bind(this));
552
- this.messenger.registerActionHandler('AssetsController:getAssetMetadata', this.getAssetMetadata.bind(this));
553
- this.messenger.registerActionHandler('AssetsController:getAssetsPrice', this.getAssetsPrice.bind(this));
554
- this.messenger.registerActionHandler('AssetsController:activeChainsUpdate', this.handleActiveChainsUpdate.bind(this));
555
- this.messenger.registerActionHandler('AssetsController:assetsUpdate', this.handleAssetsUpdate.bind(this));
556
- this.messenger.registerActionHandler('AssetsController:addCustomAsset', this.addCustomAsset.bind(this));
557
- this.messenger.registerActionHandler('AssetsController:removeCustomAsset', this.removeCustomAsset.bind(this));
558
- this.messenger.registerActionHandler('AssetsController:getCustomAssets', this.getCustomAssets.bind(this));
642
+ this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
559
643
  }, _AssetsController_executeMiddlewares =
560
644
  // ============================================================================
561
645
  // MIDDLEWARE EXECUTION
@@ -584,32 +668,6 @@ async function _AssetsController_executeMiddlewares(middlewares, request, initia
584
668
  getAssetsState: () => this.state,
585
669
  });
586
670
  return result.response;
587
- }, _AssetsController_assignChainsToDataSources = function _AssetsController_assignChainsToDataSources(requestedChains) {
588
- const assignment = new Map();
589
- const remainingChains = new Set(requestedChains);
590
- for (const sourceId of __classPrivateFieldGet(this, _AssetsController_dataSources, "f").keys()) {
591
- // Get available chains for this data source
592
- const availableChains = __classPrivateFieldGet(this, _AssetsController_dataSources, "f").get(sourceId);
593
- if (!availableChains || availableChains.size === 0) {
594
- continue;
595
- }
596
- const chainsForThisSource = [];
597
- for (const chainId of remainingChains) {
598
- // Check if this chain is available on this source
599
- if (availableChains.has(chainId)) {
600
- chainsForThisSource.push(chainId);
601
- remainingChains.delete(chainId);
602
- }
603
- }
604
- if (chainsForThisSource.length > 0) {
605
- assignment.set(sourceId, chainsForThisSource);
606
- log('Assigned chains to data source', {
607
- sourceId,
608
- chains: chainsForThisSource,
609
- });
610
- }
611
- }
612
- return assignment;
613
671
  }, _AssetsController_updateState =
614
672
  // ============================================================================
615
673
  // STATE MANAGEMENT
@@ -627,7 +685,7 @@ async function _AssetsController_updateState(response) {
627
685
  const changedBalances = [];
628
686
  const changedMetadata = [];
629
687
  this.update((state) => {
630
- // Use type assertions to avoid deep type instantiation issues with Draft<Json>
688
+ // Use type assertions to avoid deep type instantiation issues with Immer Draft types
631
689
  const metadata = state.assetsMetadata;
632
690
  const balances = state.assetsBalance;
633
691
  const prices = state.assetsPrice;
@@ -738,16 +796,21 @@ async function _AssetsController_updateState(response) {
738
796
  const accountBalances = this.state.assetsBalance[account.id] ?? {};
739
797
  for (const [assetId, balance] of Object.entries(accountBalances)) {
740
798
  const typedAssetId = assetId;
741
- const assetChainId = extractChainId(typedAssetId);
742
- if (!chainIdSet.has(assetChainId)) {
743
- continue;
744
- }
745
799
  const metadataRaw = this.state.assetsMetadata[typedAssetId];
746
800
  // Skip assets without metadata
747
801
  if (!metadataRaw) {
748
802
  continue;
749
803
  }
750
804
  const metadata = metadataRaw;
805
+ // Skip hidden assets (assetPreferences)
806
+ const prefs = this.state.assetPreferences[typedAssetId];
807
+ if (prefs?.hidden) {
808
+ continue;
809
+ }
810
+ const assetChainId = extractChainId(typedAssetId);
811
+ if (!chainIdSet.has(assetChainId)) {
812
+ continue;
813
+ }
751
814
  // Filter by asset type
752
815
  const tokenAssetType = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_tokenStandardToAssetType).call(this, metadata.type);
753
816
  if (!assetTypeSet.has(tokenAssetType)) {
@@ -798,7 +861,7 @@ async function _AssetsController_updateState(response) {
798
861
  selectedAccountCount: __classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get).length,
799
862
  enabledChainCount: __classPrivateFieldGet(this, _AssetsController_enabledChains, "f").size,
800
863
  });
801
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeToDataSources).call(this);
864
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
802
865
  if (__classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get).length > 0) {
803
866
  this.getAssets(__classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get), {
804
867
  chainIds: [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")],
@@ -819,77 +882,67 @@ async function _AssetsController_updateState(response) {
819
882
  // Convert to array first to avoid modifying map during iteration
820
883
  const subscriptionKeys = [...__classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").keys()];
821
884
  for (const subscriptionKey of subscriptionKeys) {
822
- // Extract sourceId from subscription key (format: "ds:${sourceId}")
823
885
  if (subscriptionKey.startsWith('ds:')) {
824
886
  const sourceId = subscriptionKey.slice(3);
825
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_unsubscribeDataSource).call(this, sourceId);
887
+ const source = __classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_subscriptionBalanceDataSources_get).find((ds) => ds.getName() === sourceId);
888
+ if (source) {
889
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_unsubscribeDataSource).call(this, source);
890
+ }
826
891
  }
827
892
  }
828
893
  __classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").clear();
829
- }, _AssetsController_subscribeToDataSources = function _AssetsController_subscribeToDataSources() {
894
+ }, _AssetsController_subscribeAssets = function _AssetsController_subscribeAssets() {
830
895
  if (__classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get).length === 0) {
831
896
  return;
832
897
  }
833
898
  // Subscribe to balance updates (batched by data source)
834
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssetsBalance).call(this);
899
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssetsBalance).call(this, __classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get), [
900
+ ...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f"),
901
+ ]);
835
902
  // Subscribe to price updates for all assets held by selected accounts
836
903
  this.subscribeAssetsPrice(__classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get), [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")]);
837
- }, _AssetsController_subscribeAssetsBalance = function _AssetsController_subscribeAssetsBalance() {
838
- // Step 1: Build chain -> accounts mapping based on account scopes and enabled networks
839
- const chainToAccounts = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_buildChainToAccountsMap).call(this, __classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get), __classPrivateFieldGet(this, _AssetsController_enabledChains, "f"));
840
- // Step 2: Split by data source active chains (ordered by priority)
841
- // Get all chains that need to be subscribed
904
+ }, _AssetsController_subscribeAssetsBalance = function _AssetsController_subscribeAssetsBalance(accounts, chainIds) {
905
+ const chainToAccounts = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_buildChainToAccountsMap).call(this, accounts, new Set(chainIds));
842
906
  const remainingChains = new Set(chainToAccounts.keys());
843
- // Assign chains to data sources based on availability (ordered by priority)
844
- const chainAssignment = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_assignChainsToDataSources).call(this, Array.from(remainingChains));
845
- log('Subscribe - chain assignment', {
846
- totalChains: remainingChains.size,
847
- dataSourceAssignments: Array.from(chainAssignment.entries()).map(([sourceId, chains]) => ({ sourceId, chainCount: chains.length })),
848
- });
849
- // Subscribe to each data source with its assigned chains and relevant accounts
850
- for (const sourceId of __classPrivateFieldGet(this, _AssetsController_dataSources, "f").keys()) {
851
- const assignedChains = chainAssignment.get(sourceId);
852
- if (!assignedChains || assignedChains.length === 0) {
853
- // Unsubscribe from data sources with no assigned chains
854
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_unsubscribeDataSource).call(this, sourceId);
855
- continue;
907
+ for (const source of __classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_subscriptionBalanceDataSources_get)) {
908
+ const availableChains = new Set(source.getActiveChainsSync());
909
+ const assignedChains = [];
910
+ for (const chainId of remainingChains) {
911
+ if (availableChains.has(chainId)) {
912
+ assignedChains.push(chainId);
913
+ remainingChains.delete(chainId);
914
+ }
856
915
  }
857
- // Collect unique accounts that need any of the assigned chains
858
- const accountsForSource = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getAccountsForChains).call(this, assignedChains, chainToAccounts);
859
- if (accountsForSource.length === 0) {
916
+ if (assignedChains.length === 0) {
917
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_unsubscribeDataSource).call(this, source);
860
918
  continue;
861
919
  }
862
- // Subscribe with ONE call per data source
863
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeToDataSource).call(this, sourceId, accountsForSource, assignedChains);
920
+ const seenIds = new Set();
921
+ const accountsForSource = assignedChains
922
+ .flatMap((chainId) => chainToAccounts.get(chainId) ?? [])
923
+ .filter((account) => !seenIds.has(account.id) && (seenIds.add(account.id), true));
924
+ if (accountsForSource.length > 0) {
925
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeDataSource).call(this, source, accountsForSource, assignedChains);
926
+ }
864
927
  }
865
928
  }, _AssetsController_buildChainToAccountsMap = function _AssetsController_buildChainToAccountsMap(accounts, chainsToSubscribe) {
866
929
  const chainToAccounts = new Map();
867
930
  for (const account of accounts) {
868
- const accountChains = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getEnabledChainsForAccount).call(this, account);
869
- for (const chainId of accountChains) {
931
+ for (const chainId of __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getEnabledChainsForAccount).call(this, account)) {
870
932
  if (!chainsToSubscribe.has(chainId)) {
871
933
  continue;
872
934
  }
873
- const existingAccounts = chainToAccounts.get(chainId) ?? [];
874
- existingAccounts.push(account);
875
- chainToAccounts.set(chainId, existingAccounts);
876
- }
877
- }
878
- return chainToAccounts;
879
- }, _AssetsController_getAccountsForChains = function _AssetsController_getAccountsForChains(chains, chainToAccounts) {
880
- const accountIds = new Set();
881
- const accounts = [];
882
- for (const chainId of chains) {
883
- const chainAccounts = chainToAccounts.get(chainId) ?? [];
884
- for (const account of chainAccounts) {
885
- if (!accountIds.has(account.id)) {
886
- accountIds.add(account.id);
887
- accounts.push(account);
935
+ let list = chainToAccounts.get(chainId);
936
+ if (!list) {
937
+ list = [];
938
+ chainToAccounts.set(chainId, list);
888
939
  }
940
+ list.push(account);
889
941
  }
890
942
  }
891
- return accounts;
892
- }, _AssetsController_subscribeToDataSource = function _AssetsController_subscribeToDataSource(sourceId, accounts, chains) {
943
+ return chainToAccounts;
944
+ }, _AssetsController_subscribeDataSource = function _AssetsController_subscribeDataSource(source, accounts, chains) {
945
+ const sourceId = source.getName();
893
946
  const subscriptionKey = `ds:${sourceId}`;
894
947
  const existingSubscription = __classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").get(subscriptionKey);
895
948
  const isUpdate = existingSubscription !== undefined;
@@ -900,53 +953,49 @@ async function _AssetsController_updateState(response) {
900
953
  accountCount: accounts.length,
901
954
  chainCount: chains.length,
902
955
  });
903
- // Call data source subscribe action via Messenger (fire-and-forget)
904
- (async () => {
905
- try {
906
- await this.messenger.call(`${sourceId}:subscribe`, {
907
- request: {
908
- accounts,
909
- chainIds: chains,
910
- assetTypes: ['fungible'],
911
- dataTypes: ['balance'],
912
- updateInterval: __classPrivateFieldGet(this, _AssetsController_defaultUpdateInterval, "f"),
913
- },
914
- subscriptionId: subscriptionKey,
915
- isUpdate,
916
- });
917
- }
918
- catch (error) {
919
- console.error(`[AssetsController] Failed to subscribe to '${sourceId}':`, error);
920
- }
921
- })().catch(console.error);
956
+ const subscribeReq = {
957
+ request: __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_buildDataRequest).call(this, accounts, chains, {
958
+ assetTypes: ['fungible'],
959
+ dataTypes: ['balance'],
960
+ updateInterval: __classPrivateFieldGet(this, _AssetsController_defaultUpdateInterval, "f"),
961
+ }),
962
+ subscriptionId: subscriptionKey,
963
+ isUpdate,
964
+ onAssetsUpdate: (response) => this.handleAssetsUpdate(response, sourceId),
965
+ getAssetsState: () => this.state,
966
+ };
967
+ source.subscribe(subscribeReq).catch((error) => {
968
+ console.error(`[AssetsController] Failed to subscribe to '${sourceId}':`, error);
969
+ });
922
970
  // Track subscription
923
971
  const subscription = {
924
972
  chains,
925
973
  accountId: subscriptionKey,
926
974
  assetTypes: ['fungible'],
927
- dataTypes: ['balance', 'price'],
975
+ dataTypes: ['balance'],
928
976
  unsubscribe: () => {
929
977
  __classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").delete(subscriptionKey);
930
978
  },
931
979
  };
932
980
  __classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").set(subscriptionKey, subscription);
933
- }, _AssetsController_unsubscribeDataSource = function _AssetsController_unsubscribeDataSource(sourceId) {
934
- const subscriptionKey = `ds:${sourceId}`;
981
+ }, _AssetsController_unsubscribeDataSource = function _AssetsController_unsubscribeDataSource(source) {
982
+ const subscriptionKey = `ds:${source.getName()}`;
935
983
  const existingSubscription = __classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").get(subscriptionKey);
936
984
  if (existingSubscription) {
937
- // Fire-and-forget unsubscribe call
938
- (async () => {
939
- try {
940
- await this.messenger.call(`${sourceId}:unsubscribe`, subscriptionKey);
941
- }
942
- catch {
943
- // Ignore errors - source may not have been subscribed
944
- }
945
- })().catch(() => {
946
- // Ignore errors - source may not have been subscribed
947
- });
985
+ source.unsubscribe(subscriptionKey).catch(() => undefined);
948
986
  existingSubscription.unsubscribe();
949
987
  }
988
+ }, _AssetsController_buildDataRequest = function _AssetsController_buildDataRequest(accounts, chainIds, partial = {}) {
989
+ const chainIdSet = new Set(chainIds);
990
+ const accountsWithSupportedChains = accounts.map((account) => ({
991
+ account,
992
+ supportedChains: __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getEnabledChainsForAccount).call(this, account).filter((chain) => chainIdSet.has(chain)),
993
+ }));
994
+ return {
995
+ accountsWithSupportedChains,
996
+ chainIds,
997
+ ...partial,
998
+ };
950
999
  }, _AssetsController_getEnabledChainsForAccount = function _AssetsController_getEnabledChainsForAccount(account) {
951
1000
  // Account scopes are CAIP-2 chain IDs like "eip155:1", "solana:mainnet", "bip122:..."
952
1001
  const scopes = account.scopes ?? [];
@@ -981,7 +1030,7 @@ async function _AssetsController_handleAccountGroupChanged() {
981
1030
  accountIds: accounts.map((a) => a.id),
982
1031
  });
983
1032
  // Subscribe and fetch for the new account group
984
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeToDataSources).call(this);
1033
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
985
1034
  if (accounts.length > 0) {
986
1035
  await this.getAssets(accounts, {
987
1036
  chainIds: [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")],
@@ -1015,7 +1064,7 @@ async function _AssetsController_handleAccountGroupChanged() {
1015
1064
  // Users may want to see historical balances even if the network is currently disabled.
1016
1065
  // The data will simply not be updated until the network is re-enabled.
1017
1066
  // Refresh subscriptions for new chain set
1018
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeToDataSources).call(this);
1067
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1019
1068
  // Do one-time fetch for newly enabled chains
1020
1069
  if (addedChains.length > 0 && __classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get).length > 0) {
1021
1070
  await this.getAssets(__classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get), {
@@ -1023,28 +1072,5 @@ async function _AssetsController_handleAccountGroupChanged() {
1023
1072
  forceUpdate: true,
1024
1073
  });
1025
1074
  }
1026
- }, _AssetsController_handleSubscriptionUpdate =
1027
- /**
1028
- * Handle an async update from a data source subscription.
1029
- * Enriches response with token metadata before updating state.
1030
- *
1031
- * @param response - The data response from the data source.
1032
- * @param _sourceId - The source ID (unused but kept for logging context).
1033
- * @param request - Optional original request for context.
1034
- */
1035
- async function _AssetsController_handleSubscriptionUpdate(response, _sourceId, request) {
1036
- // Run through enrichment middlewares (Event Stack: Detection → Token → Price)
1037
- // Include 'metadata' in dataTypes so TokenDataSource runs to enrich detected assets
1038
- const enrichedResponse = await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, [
1039
- this.messenger.call('DetectionMiddleware:getAssetsMiddleware'),
1040
- this.messenger.call('TokenDataSource:getAssetsMiddleware'),
1041
- this.messenger.call('PriceDataSource:getAssetsMiddleware'),
1042
- ], request ?? {
1043
- accounts: [],
1044
- chainIds: [],
1045
- dataTypes: ['balance', 'metadata', 'price'],
1046
- }, response);
1047
- // Update state
1048
- await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, enrichedResponse);
1049
1075
  };
1050
1076
  //# sourceMappingURL=AssetsController.mjs.map