@metamask/assets-controller 0.2.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 (126) hide show
  1. package/CHANGELOG.md +17 -1
  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 +201 -237
  11. package/dist/AssetsController.cjs.map +1 -1
  12. package/dist/AssetsController.d.cts +29 -92
  13. package/dist/AssetsController.d.cts.map +1 -1
  14. package/dist/AssetsController.d.mts +29 -92
  15. package/dist/AssetsController.d.mts.map +1 -1
  16. package/dist/AssetsController.mjs +201 -237
  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 -55
  53. package/dist/data-sources/RpcDataSource.d.cts.map +1 -1
  54. package/dist/data-sources/RpcDataSource.d.mts +16 -55
  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/index.cjs +1 -6
  75. package/dist/data-sources/index.cjs.map +1 -1
  76. package/dist/data-sources/index.d.cts +6 -7
  77. package/dist/data-sources/index.d.cts.map +1 -1
  78. package/dist/data-sources/index.d.mts +6 -7
  79. package/dist/data-sources/index.d.mts.map +1 -1
  80. package/dist/data-sources/index.mjs +1 -3
  81. package/dist/data-sources/index.mjs.map +1 -1
  82. package/dist/index.cjs +6 -6
  83. package/dist/index.cjs.map +1 -1
  84. package/dist/index.d.cts +11 -11
  85. package/dist/index.d.cts.map +1 -1
  86. package/dist/index.d.mts +11 -11
  87. package/dist/index.d.mts.map +1 -1
  88. package/dist/index.mjs +3 -3
  89. package/dist/index.mjs.map +1 -1
  90. package/dist/middlewares/DetectionMiddleware.cjs +4 -27
  91. package/dist/middlewares/DetectionMiddleware.cjs.map +1 -1
  92. package/dist/middlewares/DetectionMiddleware.d.cts +3 -26
  93. package/dist/middlewares/DetectionMiddleware.d.cts.map +1 -1
  94. package/dist/middlewares/DetectionMiddleware.d.mts +3 -26
  95. package/dist/middlewares/DetectionMiddleware.d.mts.map +1 -1
  96. package/dist/middlewares/DetectionMiddleware.mjs +4 -27
  97. package/dist/middlewares/DetectionMiddleware.mjs.map +1 -1
  98. package/dist/middlewares/index.cjs.map +1 -1
  99. package/dist/middlewares/index.d.cts +1 -1
  100. package/dist/middlewares/index.d.cts.map +1 -1
  101. package/dist/middlewares/index.d.mts +1 -1
  102. package/dist/middlewares/index.d.mts.map +1 -1
  103. package/dist/middlewares/index.mjs.map +1 -1
  104. package/dist/selectors/balance.cjs +306 -0
  105. package/dist/selectors/balance.cjs.map +1 -0
  106. package/dist/selectors/balance.d.cts +22 -0
  107. package/dist/selectors/balance.d.cts.map +1 -0
  108. package/dist/selectors/balance.d.mts +22 -0
  109. package/dist/selectors/balance.d.mts.map +1 -0
  110. package/dist/selectors/balance.mjs +297 -0
  111. package/dist/selectors/balance.mjs.map +1 -0
  112. package/dist/types.cjs.map +1 -1
  113. package/dist/types.d.cts +53 -18
  114. package/dist/types.d.cts.map +1 -1
  115. package/dist/types.d.mts +53 -18
  116. package/dist/types.d.mts.map +1 -1
  117. package/dist/types.mjs.map +1 -1
  118. package/package.json +2 -2
  119. package/dist/data-sources/initDataSources.cjs +0 -215
  120. package/dist/data-sources/initDataSources.cjs.map +0 -1
  121. package/dist/data-sources/initDataSources.d.cts +0 -140
  122. package/dist/data-sources/initDataSources.d.cts.map +0 -1
  123. package/dist/data-sources/initDataSources.d.mts +0 -140
  124. package/dist/data-sources/initDataSources.d.mts.map +0 -1
  125. package/dist/data-sources/initDataSources.mjs +0 -210
  126. package/dist/data-sources/initDataSources.mjs.map +0 -1
@@ -1,27 +1,46 @@
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);
@@ -147,13 +166,8 @@ function normalizeResponse(response) {
147
166
  * based on which chains they support. When active chains change, the controller
148
167
  * dynamically adjusts subscriptions.
149
168
  *
150
- * 4. **App Lifecycle Management**: Listens to app open/close events via messenger
151
- * to start/stop subscriptions automatically, conserving resources when app is closed.
152
- *
153
- * ## App Lifecycle
154
- *
155
- * - **App Opened** (`AppStateController:appOpened`): Starts subscriptions, fetches initial data
156
- * - **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.
157
171
  *
158
172
  * ## Architecture
159
173
  *
@@ -162,7 +176,7 @@ function normalizeResponse(response) {
162
176
  * - The controller does NOT manage polling - it simply receives pushed updates
163
177
  */
164
178
  export class AssetsController extends BaseController {
165
- constructor({ messenger, state = {}, defaultUpdateInterval = DEFAULT_POLLING_INTERVAL_MS, isEnabled = () => true, }) {
179
+ constructor({ messenger, state = {}, defaultUpdateInterval = DEFAULT_POLLING_INTERVAL_MS, isEnabled = () => true, queryApiClient, rpcDataSourceConfig, }) {
166
180
  super({
167
181
  name: CONTROLLER_NAME,
168
182
  messenger,
@@ -187,79 +201,82 @@ export class AssetsController extends BaseController {
187
201
  _AssetsController_activeSubscriptions.set(this, new Map());
188
202
  /** Currently enabled chains from NetworkEnablementController */
189
203
  _AssetsController_enabledChains.set(this, new Set());
190
- /**
191
- * Registered data sources with their available chains.
192
- * Updated continuously and independently from subscription flows.
193
- * Key: sourceId, Value: Set of currently available chainIds
194
- */
195
- _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);
196
211
  __classPrivateFieldSet(this, _AssetsController_isEnabled, isEnabled(), "f");
197
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");
198
240
  if (!__classPrivateFieldGet(this, _AssetsController_isEnabled, "f")) {
199
241
  log('AssetsController is disabled, skipping initialization');
200
242
  return;
201
243
  }
202
244
  log('Initializing AssetsController', {
203
- defaultUpdateInterval,
245
+ defaultUpdateInterval: __classPrivateFieldGet(this, _AssetsController_defaultUpdateInterval, "f"),
204
246
  });
205
247
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_initializeState).call(this);
206
248
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeToEvents).call(this);
207
249
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_registerActionHandlers).call(this);
208
- // Register data sources (order = subscription priority)
209
- this.registerDataSources([
210
- 'BackendWebsocketDataSource', // Real-time push updates
211
- 'AccountsApiDataSource', // HTTP polling fallback
212
- 'SnapDataSource', // Solana/Bitcoin/Tron snaps
213
- 'RpcDataSource', // Direct blockchain queries
214
- ]);
215
- }
216
- // ============================================================================
217
- // DATA SOURCE MANAGEMENT
218
- // ============================================================================
219
- /**
220
- * Register data sources with the controller.
221
- * Order of the array determines subscription order.
222
- *
223
- * Data sources report chain changes by calling `AssetsController:activeChainsUpdate` action.
224
- *
225
- * @param dataSourceIds - Array of data source identifiers to register.
226
- */
227
- registerDataSources(dataSourceIds) {
228
- for (const id of dataSourceIds) {
229
- log('Registering data source', { id });
230
- // Initialize available chains tracking for this source
231
- __classPrivateFieldGet(this, _AssetsController_dataSources, "f").set(id, new Set());
232
- }
233
250
  }
234
251
  // ============================================================================
235
252
  // DATA SOURCE CHAIN MANAGEMENT
236
253
  // ============================================================================
237
254
  /**
238
- * Handle when a data source's active chains change.
239
- * Active chains are chains that are both supported AND available.
240
- * 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).
241
260
  *
242
- * 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.
243
262
  *
244
263
  * @param dataSourceId - The identifier of the data source reporting the change.
245
- * @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.
246
266
  */
247
- handleActiveChainsUpdate(dataSourceId, activeChains) {
267
+ handleActiveChainsUpdate(dataSourceId, activeChains, previousChains) {
248
268
  log('Data source active chains changed', {
249
269
  dataSourceId,
250
270
  chainCount: activeChains.length,
251
271
  chains: activeChains,
252
272
  });
253
- const previousChains = __classPrivateFieldGet(this, _AssetsController_dataSources, "f").get(dataSourceId) ?? new Set();
254
- const newChains = new Set(activeChains);
255
- // Update centralized available chains tracking
256
- __classPrivateFieldGet(this, _AssetsController_dataSources, "f").set(dataSourceId, newChains);
257
- // Check for changes
258
- const addedChains = activeChains.filter((chain) => !previousChains.has(chain));
259
- 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));
260
277
  if (addedChains.length > 0 || removedChains.length > 0) {
261
278
  // Refresh subscriptions to use updated data source availability
262
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeToDataSources).call(this);
279
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
263
280
  }
264
281
  // If chains were added and we have selected accounts, do one-time fetch
265
282
  if (addedChains.length > 0 && __classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get).length > 0) {
@@ -289,21 +306,20 @@ export class AssetsController extends BaseController {
289
306
  customAssets.push(...accountCustomAssets);
290
307
  }
291
308
  if (options?.forceUpdate) {
292
- const response = await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, [
293
- this.messenger.call('AccountsApiDataSource:getAssetsMiddleware'),
294
- this.messenger.call('SnapDataSource:getAssetsMiddleware'),
295
- this.messenger.call('RpcDataSource:getAssetsMiddleware'),
296
- this.messenger.call('DetectionMiddleware:getAssetsMiddleware'),
297
- this.messenger.call('TokenDataSource:getAssetsMiddleware'),
298
- this.messenger.call('PriceDataSource:getAssetsMiddleware'),
299
- ], {
300
- accounts,
301
- chainIds,
309
+ const request = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_buildDataRequest).call(this, accounts, chainIds, {
302
310
  assetTypes,
303
311
  dataTypes,
304
312
  customAssets: customAssets.length > 0 ? customAssets : undefined,
305
313
  forceUpdate: true,
306
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);
307
323
  await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, response);
308
324
  }
309
325
  return __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getAssetsFromState).call(this, accounts, chainIds, assetTypes);
@@ -457,6 +473,9 @@ export class AssetsController extends BaseController {
457
473
  }
458
474
  });
459
475
  }
476
+ // ============================================================================
477
+ // SUBSCRIPTIONS
478
+ // ============================================================================
460
479
  /**
461
480
  * Subscribe to price updates for all assets held by the given accounts.
462
481
  * Polls PriceDataSource which fetches prices from balance state.
@@ -471,19 +490,17 @@ export class AssetsController extends BaseController {
471
490
  const subscriptionKey = 'ds:PriceDataSource';
472
491
  const existingSubscription = __classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").get(subscriptionKey);
473
492
  const isUpdate = existingSubscription !== undefined;
474
- // Fire-and-forget - errors are handled internally by PriceDataSource
475
- this.messenger
476
- .call('PriceDataSource:subscribe', {
477
- request: {
478
- accounts,
479
- chainIds,
493
+ const subscribeReq = {
494
+ request: __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_buildDataRequest).call(this, accounts, chainIds, {
480
495
  dataTypes: ['price'],
481
496
  updateInterval,
482
- },
497
+ }),
483
498
  subscriptionId: subscriptionKey,
484
499
  isUpdate,
485
- })
486
- .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);
487
504
  // Track subscription
488
505
  const subscription = {
489
506
  chains: chainIds,
@@ -505,37 +522,55 @@ export class AssetsController extends BaseController {
505
522
  if (!existingSubscription) {
506
523
  return;
507
524
  }
508
- // Fire-and-forget - errors are handled internally by PriceDataSource
509
- this.messenger
510
- .call('PriceDataSource:unsubscribe', subscriptionKey)
511
- .catch(console.error);
525
+ __classPrivateFieldGet(this, _AssetsController_priceDataSource, "f").unsubscribe(subscriptionKey).catch(console.error);
512
526
  existingSubscription.unsubscribe();
513
527
  }
514
528
  /**
515
529
  * Handle assets updated from a data source.
516
- * 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.
517
532
  *
518
533
  * @param response - The data response with updated assets
519
534
  * @param sourceId - The data source ID reporting the update
535
+ * @param request - Optional original request for context when enriching
520
536
  */
521
- async handleAssetsUpdate(response, sourceId) {
537
+ async handleAssetsUpdate(response, sourceId, request) {
522
538
  log('Assets updated from data source', {
523
539
  sourceId,
524
540
  hasBalance: Boolean(response.assetsBalance),
525
541
  hasPrice: Boolean(response.assetsPrice),
526
542
  });
527
- 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);
528
555
  }
529
556
  // ============================================================================
530
557
  // CLEANUP
531
558
  // ============================================================================
532
559
  destroy() {
533
560
  log('Destroying AssetsController', {
534
- dataSourceCount: __classPrivateFieldGet(this, _AssetsController_dataSources, "f").size,
561
+ dataSourceCount: __classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_subscriptionBalanceDataSources_get).length,
535
562
  subscriptionCount: __classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").size,
536
563
  });
537
- // Clear data sources
538
- __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
+ }
539
574
  // Stop all active subscriptions
540
575
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_stop).call(this);
541
576
  // Unregister action handlers
@@ -543,8 +578,6 @@ export class AssetsController extends BaseController {
543
578
  this.messenger.unregisterActionHandler('AssetsController:getAssetsBalance');
544
579
  this.messenger.unregisterActionHandler('AssetsController:getAssetMetadata');
545
580
  this.messenger.unregisterActionHandler('AssetsController:getAssetsPrice');
546
- this.messenger.unregisterActionHandler('AssetsController:activeChainsUpdate');
547
- this.messenger.unregisterActionHandler('AssetsController:assetsUpdate');
548
581
  this.messenger.unregisterActionHandler('AssetsController:addCustomAsset');
549
582
  this.messenger.unregisterActionHandler('AssetsController:removeCustomAsset');
550
583
  this.messenger.unregisterActionHandler('AssetsController:getCustomAssets');
@@ -552,8 +585,15 @@ export class AssetsController extends BaseController {
552
585
  this.messenger.unregisterActionHandler('AssetsController:unhideAsset');
553
586
  }
554
587
  }
555
- _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() {
556
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
+ ];
557
597
  }, _AssetsController_initializeState = function _AssetsController_initializeState() {
558
598
  const { enabledNetworkMap } = this.messenger.call('NetworkEnablementController:getState');
559
599
  __classPrivateFieldSet(this, _AssetsController_enabledChains, __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_extractEnabledChains).call(this, enabledNetworkMap), "f");
@@ -595,24 +635,11 @@ _AssetsController_isEnabled = new WeakMap(), _AssetsController_defaultUpdateInte
595
635
  this.messenger.subscribe('NetworkEnablementController:stateChange', ({ enabledNetworkMap }) => {
596
636
  __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_handleEnabledNetworksChanged).call(this, enabledNetworkMap).catch(console.error);
597
637
  });
598
- // App lifecycle: start when opened, stop when closed
599
- this.messenger.subscribe('AppStateController:appOpened', () => __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_start).call(this));
600
- this.messenger.subscribe('AppStateController:appClosed', () => __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_stop).call(this));
601
638
  // Keyring lifecycle: start when unlocked, stop when locked
602
639
  this.messenger.subscribe('KeyringController:unlock', () => __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_start).call(this));
603
640
  this.messenger.subscribe('KeyringController:lock', () => __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_stop).call(this));
604
641
  }, _AssetsController_registerActionHandlers = function _AssetsController_registerActionHandlers() {
605
- this.messenger.registerActionHandler('AssetsController:getAssets', this.getAssets.bind(this));
606
- this.messenger.registerActionHandler('AssetsController:getAssetsBalance', this.getAssetsBalance.bind(this));
607
- this.messenger.registerActionHandler('AssetsController:getAssetMetadata', this.getAssetMetadata.bind(this));
608
- this.messenger.registerActionHandler('AssetsController:getAssetsPrice', this.getAssetsPrice.bind(this));
609
- this.messenger.registerActionHandler('AssetsController:activeChainsUpdate', this.handleActiveChainsUpdate.bind(this));
610
- this.messenger.registerActionHandler('AssetsController:assetsUpdate', this.handleAssetsUpdate.bind(this));
611
- this.messenger.registerActionHandler('AssetsController:addCustomAsset', this.addCustomAsset.bind(this));
612
- this.messenger.registerActionHandler('AssetsController:removeCustomAsset', this.removeCustomAsset.bind(this));
613
- this.messenger.registerActionHandler('AssetsController:getCustomAssets', this.getCustomAssets.bind(this));
614
- this.messenger.registerActionHandler('AssetsController:hideAsset', this.hideAsset.bind(this));
615
- this.messenger.registerActionHandler('AssetsController:unhideAsset', this.unhideAsset.bind(this));
642
+ this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
616
643
  }, _AssetsController_executeMiddlewares =
617
644
  // ============================================================================
618
645
  // MIDDLEWARE EXECUTION
@@ -641,32 +668,6 @@ async function _AssetsController_executeMiddlewares(middlewares, request, initia
641
668
  getAssetsState: () => this.state,
642
669
  });
643
670
  return result.response;
644
- }, _AssetsController_assignChainsToDataSources = function _AssetsController_assignChainsToDataSources(requestedChains) {
645
- const assignment = new Map();
646
- const remainingChains = new Set(requestedChains);
647
- for (const sourceId of __classPrivateFieldGet(this, _AssetsController_dataSources, "f").keys()) {
648
- // Get available chains for this data source
649
- const availableChains = __classPrivateFieldGet(this, _AssetsController_dataSources, "f").get(sourceId);
650
- if (!availableChains || availableChains.size === 0) {
651
- continue;
652
- }
653
- const chainsForThisSource = [];
654
- for (const chainId of remainingChains) {
655
- // Check if this chain is available on this source
656
- if (availableChains.has(chainId)) {
657
- chainsForThisSource.push(chainId);
658
- remainingChains.delete(chainId);
659
- }
660
- }
661
- if (chainsForThisSource.length > 0) {
662
- assignment.set(sourceId, chainsForThisSource);
663
- log('Assigned chains to data source', {
664
- sourceId,
665
- chains: chainsForThisSource,
666
- });
667
- }
668
- }
669
- return assignment;
670
671
  }, _AssetsController_updateState =
671
672
  // ============================================================================
672
673
  // STATE MANAGEMENT
@@ -860,7 +861,7 @@ async function _AssetsController_updateState(response) {
860
861
  selectedAccountCount: __classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get).length,
861
862
  enabledChainCount: __classPrivateFieldGet(this, _AssetsController_enabledChains, "f").size,
862
863
  });
863
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeToDataSources).call(this);
864
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
864
865
  if (__classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get).length > 0) {
865
866
  this.getAssets(__classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get), {
866
867
  chainIds: [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")],
@@ -881,77 +882,67 @@ async function _AssetsController_updateState(response) {
881
882
  // Convert to array first to avoid modifying map during iteration
882
883
  const subscriptionKeys = [...__classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").keys()];
883
884
  for (const subscriptionKey of subscriptionKeys) {
884
- // Extract sourceId from subscription key (format: "ds:${sourceId}")
885
885
  if (subscriptionKey.startsWith('ds:')) {
886
886
  const sourceId = subscriptionKey.slice(3);
887
- __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
+ }
888
891
  }
889
892
  }
890
893
  __classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").clear();
891
- }, _AssetsController_subscribeToDataSources = function _AssetsController_subscribeToDataSources() {
894
+ }, _AssetsController_subscribeAssets = function _AssetsController_subscribeAssets() {
892
895
  if (__classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get).length === 0) {
893
896
  return;
894
897
  }
895
898
  // Subscribe to balance updates (batched by data source)
896
- __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
+ ]);
897
902
  // Subscribe to price updates for all assets held by selected accounts
898
903
  this.subscribeAssetsPrice(__classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get), [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")]);
899
- }, _AssetsController_subscribeAssetsBalance = function _AssetsController_subscribeAssetsBalance() {
900
- // Step 1: Build chain -> accounts mapping based on account scopes and enabled networks
901
- const chainToAccounts = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_buildChainToAccountsMap).call(this, __classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get), __classPrivateFieldGet(this, _AssetsController_enabledChains, "f"));
902
- // Step 2: Split by data source active chains (ordered by priority)
903
- // 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));
904
906
  const remainingChains = new Set(chainToAccounts.keys());
905
- // Assign chains to data sources based on availability (ordered by priority)
906
- const chainAssignment = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_assignChainsToDataSources).call(this, Array.from(remainingChains));
907
- log('Subscribe - chain assignment', {
908
- totalChains: remainingChains.size,
909
- dataSourceAssignments: Array.from(chainAssignment.entries()).map(([sourceId, chains]) => ({ sourceId, chainCount: chains.length })),
910
- });
911
- // Subscribe to each data source with its assigned chains and relevant accounts
912
- for (const sourceId of __classPrivateFieldGet(this, _AssetsController_dataSources, "f").keys()) {
913
- const assignedChains = chainAssignment.get(sourceId);
914
- if (!assignedChains || assignedChains.length === 0) {
915
- // Unsubscribe from data sources with no assigned chains
916
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_unsubscribeDataSource).call(this, sourceId);
917
- 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
+ }
918
915
  }
919
- // Collect unique accounts that need any of the assigned chains
920
- const accountsForSource = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getAccountsForChains).call(this, assignedChains, chainToAccounts);
921
- if (accountsForSource.length === 0) {
916
+ if (assignedChains.length === 0) {
917
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_unsubscribeDataSource).call(this, source);
922
918
  continue;
923
919
  }
924
- // Subscribe with ONE call per data source
925
- __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
+ }
926
927
  }
927
928
  }, _AssetsController_buildChainToAccountsMap = function _AssetsController_buildChainToAccountsMap(accounts, chainsToSubscribe) {
928
929
  const chainToAccounts = new Map();
929
930
  for (const account of accounts) {
930
- const accountChains = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getEnabledChainsForAccount).call(this, account);
931
- for (const chainId of accountChains) {
931
+ for (const chainId of __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getEnabledChainsForAccount).call(this, account)) {
932
932
  if (!chainsToSubscribe.has(chainId)) {
933
933
  continue;
934
934
  }
935
- const existingAccounts = chainToAccounts.get(chainId) ?? [];
936
- existingAccounts.push(account);
937
- chainToAccounts.set(chainId, existingAccounts);
938
- }
939
- }
940
- return chainToAccounts;
941
- }, _AssetsController_getAccountsForChains = function _AssetsController_getAccountsForChains(chains, chainToAccounts) {
942
- const accountIds = new Set();
943
- const accounts = [];
944
- for (const chainId of chains) {
945
- const chainAccounts = chainToAccounts.get(chainId) ?? [];
946
- for (const account of chainAccounts) {
947
- if (!accountIds.has(account.id)) {
948
- accountIds.add(account.id);
949
- accounts.push(account);
935
+ let list = chainToAccounts.get(chainId);
936
+ if (!list) {
937
+ list = [];
938
+ chainToAccounts.set(chainId, list);
950
939
  }
940
+ list.push(account);
951
941
  }
952
942
  }
953
- return accounts;
954
- }, _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();
955
946
  const subscriptionKey = `ds:${sourceId}`;
956
947
  const existingSubscription = __classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").get(subscriptionKey);
957
948
  const isUpdate = existingSubscription !== undefined;
@@ -962,53 +953,49 @@ async function _AssetsController_updateState(response) {
962
953
  accountCount: accounts.length,
963
954
  chainCount: chains.length,
964
955
  });
965
- // Call data source subscribe action via Messenger (fire-and-forget)
966
- (async () => {
967
- try {
968
- await this.messenger.call(`${sourceId}:subscribe`, {
969
- request: {
970
- accounts,
971
- chainIds: chains,
972
- assetTypes: ['fungible'],
973
- dataTypes: ['balance'],
974
- updateInterval: __classPrivateFieldGet(this, _AssetsController_defaultUpdateInterval, "f"),
975
- },
976
- subscriptionId: subscriptionKey,
977
- isUpdate,
978
- });
979
- }
980
- catch (error) {
981
- console.error(`[AssetsController] Failed to subscribe to '${sourceId}':`, error);
982
- }
983
- })().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
+ });
984
970
  // Track subscription
985
971
  const subscription = {
986
972
  chains,
987
973
  accountId: subscriptionKey,
988
974
  assetTypes: ['fungible'],
989
- dataTypes: ['balance', 'price'],
975
+ dataTypes: ['balance'],
990
976
  unsubscribe: () => {
991
977
  __classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").delete(subscriptionKey);
992
978
  },
993
979
  };
994
980
  __classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").set(subscriptionKey, subscription);
995
- }, _AssetsController_unsubscribeDataSource = function _AssetsController_unsubscribeDataSource(sourceId) {
996
- const subscriptionKey = `ds:${sourceId}`;
981
+ }, _AssetsController_unsubscribeDataSource = function _AssetsController_unsubscribeDataSource(source) {
982
+ const subscriptionKey = `ds:${source.getName()}`;
997
983
  const existingSubscription = __classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").get(subscriptionKey);
998
984
  if (existingSubscription) {
999
- // Fire-and-forget unsubscribe call
1000
- (async () => {
1001
- try {
1002
- await this.messenger.call(`${sourceId}:unsubscribe`, subscriptionKey);
1003
- }
1004
- catch {
1005
- // Ignore errors - source may not have been subscribed
1006
- }
1007
- })().catch(() => {
1008
- // Ignore errors - source may not have been subscribed
1009
- });
985
+ source.unsubscribe(subscriptionKey).catch(() => undefined);
1010
986
  existingSubscription.unsubscribe();
1011
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
+ };
1012
999
  }, _AssetsController_getEnabledChainsForAccount = function _AssetsController_getEnabledChainsForAccount(account) {
1013
1000
  // Account scopes are CAIP-2 chain IDs like "eip155:1", "solana:mainnet", "bip122:..."
1014
1001
  const scopes = account.scopes ?? [];
@@ -1043,7 +1030,7 @@ async function _AssetsController_handleAccountGroupChanged() {
1043
1030
  accountIds: accounts.map((a) => a.id),
1044
1031
  });
1045
1032
  // Subscribe and fetch for the new account group
1046
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeToDataSources).call(this);
1033
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1047
1034
  if (accounts.length > 0) {
1048
1035
  await this.getAssets(accounts, {
1049
1036
  chainIds: [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")],
@@ -1077,7 +1064,7 @@ async function _AssetsController_handleAccountGroupChanged() {
1077
1064
  // Users may want to see historical balances even if the network is currently disabled.
1078
1065
  // The data will simply not be updated until the network is re-enabled.
1079
1066
  // Refresh subscriptions for new chain set
1080
- __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeToDataSources).call(this);
1067
+ __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssets).call(this);
1081
1068
  // Do one-time fetch for newly enabled chains
1082
1069
  if (addedChains.length > 0 && __classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get).length > 0) {
1083
1070
  await this.getAssets(__classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get), {
@@ -1085,28 +1072,5 @@ async function _AssetsController_handleAccountGroupChanged() {
1085
1072
  forceUpdate: true,
1086
1073
  });
1087
1074
  }
1088
- }, _AssetsController_handleSubscriptionUpdate =
1089
- /**
1090
- * Handle an async update from a data source subscription.
1091
- * Enriches response with token metadata before updating state.
1092
- *
1093
- * @param response - The data response from the data source.
1094
- * @param _sourceId - The source ID (unused but kept for logging context).
1095
- * @param request - Optional original request for context.
1096
- */
1097
- async function _AssetsController_handleSubscriptionUpdate(response, _sourceId, request) {
1098
- // Run through enrichment middlewares (Event Stack: Detection → Token → Price)
1099
- // Include 'metadata' in dataTypes so TokenDataSource runs to enrich detected assets
1100
- const enrichedResponse = await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_executeMiddlewares).call(this, [
1101
- this.messenger.call('DetectionMiddleware:getAssetsMiddleware'),
1102
- this.messenger.call('TokenDataSource:getAssetsMiddleware'),
1103
- this.messenger.call('PriceDataSource:getAssetsMiddleware'),
1104
- ], request ?? {
1105
- accounts: [],
1106
- chainIds: [],
1107
- dataTypes: ['balance', 'metadata', 'price'],
1108
- }, response);
1109
- // Update state
1110
- await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, enrichedResponse);
1111
1075
  };
1112
1076
  //# sourceMappingURL=AssetsController.mjs.map