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