@metamask/assets-controller 0.1.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.
- package/CHANGELOG.md +37 -0
- package/LICENSE +20 -0
- package/README.md +15 -0
- package/dist/AssetsController.cjs +1057 -0
- package/dist/AssetsController.cjs.map +1 -0
- package/dist/AssetsController.d.cts +260 -0
- package/dist/AssetsController.d.cts.map +1 -0
- package/dist/AssetsController.d.mts +260 -0
- package/dist/AssetsController.d.mts.map +1 -0
- package/dist/AssetsController.mjs +1050 -0
- package/dist/AssetsController.mjs.map +1 -0
- package/dist/data-sources/AbstractDataSource.cjs +103 -0
- package/dist/data-sources/AbstractDataSource.cjs.map +1 -0
- package/dist/data-sources/AbstractDataSource.d.cts +92 -0
- package/dist/data-sources/AbstractDataSource.d.cts.map +1 -0
- package/dist/data-sources/AbstractDataSource.d.mts +92 -0
- package/dist/data-sources/AbstractDataSource.d.mts.map +1 -0
- package/dist/data-sources/AbstractDataSource.mjs +99 -0
- package/dist/data-sources/AbstractDataSource.mjs.map +1 -0
- package/dist/data-sources/AccountsApiDataSource.cjs +421 -0
- package/dist/data-sources/AccountsApiDataSource.cjs.map +1 -0
- package/dist/data-sources/AccountsApiDataSource.d.cts +103 -0
- package/dist/data-sources/AccountsApiDataSource.d.cts.map +1 -0
- package/dist/data-sources/AccountsApiDataSource.d.mts +103 -0
- package/dist/data-sources/AccountsApiDataSource.d.mts.map +1 -0
- package/dist/data-sources/AccountsApiDataSource.mjs +416 -0
- package/dist/data-sources/AccountsApiDataSource.mjs.map +1 -0
- package/dist/data-sources/BackendWebsocketDataSource.cjs +388 -0
- package/dist/data-sources/BackendWebsocketDataSource.cjs.map +1 -0
- package/dist/data-sources/BackendWebsocketDataSource.d.cts +95 -0
- package/dist/data-sources/BackendWebsocketDataSource.d.cts.map +1 -0
- package/dist/data-sources/BackendWebsocketDataSource.d.mts +95 -0
- package/dist/data-sources/BackendWebsocketDataSource.d.mts.map +1 -0
- package/dist/data-sources/BackendWebsocketDataSource.mjs +383 -0
- package/dist/data-sources/BackendWebsocketDataSource.mjs.map +1 -0
- package/dist/data-sources/PriceDataSource.cjs +343 -0
- package/dist/data-sources/PriceDataSource.cjs.map +1 -0
- package/dist/data-sources/PriceDataSource.d.cts +153 -0
- package/dist/data-sources/PriceDataSource.d.cts.map +1 -0
- package/dist/data-sources/PriceDataSource.d.mts +153 -0
- package/dist/data-sources/PriceDataSource.d.mts.map +1 -0
- package/dist/data-sources/PriceDataSource.mjs +339 -0
- package/dist/data-sources/PriceDataSource.mjs.map +1 -0
- package/dist/data-sources/RpcDataSource.cjs +829 -0
- package/dist/data-sources/RpcDataSource.cjs.map +1 -0
- package/dist/data-sources/RpcDataSource.d.cts +243 -0
- package/dist/data-sources/RpcDataSource.d.cts.map +1 -0
- package/dist/data-sources/RpcDataSource.d.mts +243 -0
- package/dist/data-sources/RpcDataSource.d.mts.map +1 -0
- package/dist/data-sources/RpcDataSource.mjs +820 -0
- package/dist/data-sources/RpcDataSource.mjs.map +1 -0
- package/dist/data-sources/SnapDataSource.cjs +463 -0
- package/dist/data-sources/SnapDataSource.cjs.map +1 -0
- package/dist/data-sources/SnapDataSource.d.cts +155 -0
- package/dist/data-sources/SnapDataSource.d.cts.map +1 -0
- package/dist/data-sources/SnapDataSource.d.mts +155 -0
- package/dist/data-sources/SnapDataSource.d.mts.map +1 -0
- package/dist/data-sources/SnapDataSource.mjs +456 -0
- package/dist/data-sources/SnapDataSource.mjs.map +1 -0
- package/dist/data-sources/TokenDataSource.cjs +211 -0
- package/dist/data-sources/TokenDataSource.cjs.map +1 -0
- package/dist/data-sources/TokenDataSource.d.cts +62 -0
- package/dist/data-sources/TokenDataSource.d.cts.map +1 -0
- package/dist/data-sources/TokenDataSource.d.mts +62 -0
- package/dist/data-sources/TokenDataSource.d.mts.map +1 -0
- package/dist/data-sources/TokenDataSource.mjs +207 -0
- package/dist/data-sources/TokenDataSource.mjs.map +1 -0
- package/dist/data-sources/evm-rpc-services/clients/MulticallClient.cjs +638 -0
- package/dist/data-sources/evm-rpc-services/clients/MulticallClient.cjs.map +1 -0
- package/dist/data-sources/evm-rpc-services/clients/MulticallClient.d.cts +47 -0
- package/dist/data-sources/evm-rpc-services/clients/MulticallClient.d.cts.map +1 -0
- package/dist/data-sources/evm-rpc-services/clients/MulticallClient.d.mts +47 -0
- package/dist/data-sources/evm-rpc-services/clients/MulticallClient.d.mts.map +1 -0
- package/dist/data-sources/evm-rpc-services/clients/MulticallClient.mjs +632 -0
- package/dist/data-sources/evm-rpc-services/clients/MulticallClient.mjs.map +1 -0
- package/dist/data-sources/evm-rpc-services/clients/index.cjs +8 -0
- package/dist/data-sources/evm-rpc-services/clients/index.cjs.map +1 -0
- package/dist/data-sources/evm-rpc-services/clients/index.d.cts +3 -0
- package/dist/data-sources/evm-rpc-services/clients/index.d.cts.map +1 -0
- package/dist/data-sources/evm-rpc-services/clients/index.d.mts +3 -0
- package/dist/data-sources/evm-rpc-services/clients/index.d.mts.map +1 -0
- package/dist/data-sources/evm-rpc-services/clients/index.mjs +2 -0
- package/dist/data-sources/evm-rpc-services/clients/index.mjs.map +1 -0
- package/dist/data-sources/evm-rpc-services/index.cjs +12 -0
- package/dist/data-sources/evm-rpc-services/index.cjs.map +1 -0
- package/dist/data-sources/evm-rpc-services/index.d.cts +5 -0
- package/dist/data-sources/evm-rpc-services/index.d.cts.map +1 -0
- package/dist/data-sources/evm-rpc-services/index.d.mts +5 -0
- package/dist/data-sources/evm-rpc-services/index.d.mts.map +1 -0
- package/dist/data-sources/evm-rpc-services/index.mjs +4 -0
- package/dist/data-sources/evm-rpc-services/index.mjs.map +1 -0
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.cjs +202 -0
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.cjs.map +1 -0
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.cts +75 -0
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.cts.map +1 -0
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.mts +75 -0
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.mts.map +1 -0
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.mjs +198 -0
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.mjs.map +1 -0
- package/dist/data-sources/evm-rpc-services/services/TokenDetector.cjs +214 -0
- package/dist/data-sources/evm-rpc-services/services/TokenDetector.cjs.map +1 -0
- package/dist/data-sources/evm-rpc-services/services/TokenDetector.d.cts +71 -0
- package/dist/data-sources/evm-rpc-services/services/TokenDetector.d.cts.map +1 -0
- package/dist/data-sources/evm-rpc-services/services/TokenDetector.d.mts +71 -0
- package/dist/data-sources/evm-rpc-services/services/TokenDetector.d.mts.map +1 -0
- package/dist/data-sources/evm-rpc-services/services/TokenDetector.mjs +210 -0
- package/dist/data-sources/evm-rpc-services/services/TokenDetector.mjs.map +1 -0
- package/dist/data-sources/evm-rpc-services/services/index.cjs +8 -0
- package/dist/data-sources/evm-rpc-services/services/index.cjs.map +1 -0
- package/dist/data-sources/evm-rpc-services/services/index.d.cts +3 -0
- package/dist/data-sources/evm-rpc-services/services/index.d.cts.map +1 -0
- package/dist/data-sources/evm-rpc-services/services/index.d.mts +3 -0
- package/dist/data-sources/evm-rpc-services/services/index.d.mts.map +1 -0
- package/dist/data-sources/evm-rpc-services/services/index.mjs +3 -0
- package/dist/data-sources/evm-rpc-services/services/index.mjs.map +1 -0
- package/dist/data-sources/evm-rpc-services/types/assets.cjs +3 -0
- package/dist/data-sources/evm-rpc-services/types/assets.cjs.map +1 -0
- package/dist/data-sources/evm-rpc-services/types/assets.d.cts +53 -0
- package/dist/data-sources/evm-rpc-services/types/assets.d.cts.map +1 -0
- package/dist/data-sources/evm-rpc-services/types/assets.d.mts +53 -0
- package/dist/data-sources/evm-rpc-services/types/assets.d.mts.map +1 -0
- package/dist/data-sources/evm-rpc-services/types/assets.mjs +2 -0
- package/dist/data-sources/evm-rpc-services/types/assets.mjs.map +1 -0
- package/dist/data-sources/evm-rpc-services/types/config.cjs +3 -0
- package/dist/data-sources/evm-rpc-services/types/config.cjs.map +1 -0
- package/dist/data-sources/evm-rpc-services/types/config.d.cts +18 -0
- package/dist/data-sources/evm-rpc-services/types/config.d.cts.map +1 -0
- package/dist/data-sources/evm-rpc-services/types/config.d.mts +18 -0
- package/dist/data-sources/evm-rpc-services/types/config.d.mts.map +1 -0
- package/dist/data-sources/evm-rpc-services/types/config.mjs +2 -0
- package/dist/data-sources/evm-rpc-services/types/config.mjs.map +1 -0
- package/dist/data-sources/evm-rpc-services/types/core.cjs +3 -0
- package/dist/data-sources/evm-rpc-services/types/core.cjs.map +1 -0
- package/dist/data-sources/evm-rpc-services/types/core.d.cts +19 -0
- package/dist/data-sources/evm-rpc-services/types/core.d.cts.map +1 -0
- package/dist/data-sources/evm-rpc-services/types/core.d.mts +19 -0
- package/dist/data-sources/evm-rpc-services/types/core.d.mts.map +1 -0
- package/dist/data-sources/evm-rpc-services/types/core.mjs +2 -0
- package/dist/data-sources/evm-rpc-services/types/core.mjs.map +1 -0
- package/dist/data-sources/evm-rpc-services/types/index.cjs +3 -0
- package/dist/data-sources/evm-rpc-services/types/index.cjs.map +1 -0
- package/dist/data-sources/evm-rpc-services/types/index.d.cts +7 -0
- package/dist/data-sources/evm-rpc-services/types/index.d.cts.map +1 -0
- package/dist/data-sources/evm-rpc-services/types/index.d.mts +7 -0
- package/dist/data-sources/evm-rpc-services/types/index.d.mts.map +1 -0
- package/dist/data-sources/evm-rpc-services/types/index.mjs +2 -0
- package/dist/data-sources/evm-rpc-services/types/index.mjs.map +1 -0
- package/dist/data-sources/evm-rpc-services/types/multicall.cjs +3 -0
- package/dist/data-sources/evm-rpc-services/types/multicall.cjs.map +1 -0
- package/dist/data-sources/evm-rpc-services/types/multicall.d.cts +24 -0
- package/dist/data-sources/evm-rpc-services/types/multicall.d.cts.map +1 -0
- package/dist/data-sources/evm-rpc-services/types/multicall.d.mts +24 -0
- package/dist/data-sources/evm-rpc-services/types/multicall.d.mts.map +1 -0
- package/dist/data-sources/evm-rpc-services/types/multicall.mjs +2 -0
- package/dist/data-sources/evm-rpc-services/types/multicall.mjs.map +1 -0
- package/dist/data-sources/evm-rpc-services/types/services.cjs +3 -0
- package/dist/data-sources/evm-rpc-services/types/services.cjs.map +1 -0
- package/dist/data-sources/evm-rpc-services/types/services.d.cts +74 -0
- package/dist/data-sources/evm-rpc-services/types/services.d.cts.map +1 -0
- package/dist/data-sources/evm-rpc-services/types/services.d.mts +74 -0
- package/dist/data-sources/evm-rpc-services/types/services.d.mts.map +1 -0
- package/dist/data-sources/evm-rpc-services/types/services.mjs +2 -0
- package/dist/data-sources/evm-rpc-services/types/services.mjs.map +1 -0
- package/dist/data-sources/evm-rpc-services/types/state.cjs +3 -0
- package/dist/data-sources/evm-rpc-services/types/state.cjs.map +1 -0
- package/dist/data-sources/evm-rpc-services/types/state.d.cts +52 -0
- package/dist/data-sources/evm-rpc-services/types/state.d.cts.map +1 -0
- package/dist/data-sources/evm-rpc-services/types/state.d.mts +52 -0
- package/dist/data-sources/evm-rpc-services/types/state.d.mts.map +1 -0
- package/dist/data-sources/evm-rpc-services/types/state.mjs +2 -0
- package/dist/data-sources/evm-rpc-services/types/state.mjs.map +1 -0
- package/dist/data-sources/evm-rpc-services/utils/batch.cjs +45 -0
- package/dist/data-sources/evm-rpc-services/utils/batch.cjs.map +1 -0
- package/dist/data-sources/evm-rpc-services/utils/batch.d.cts +28 -0
- package/dist/data-sources/evm-rpc-services/utils/batch.d.cts.map +1 -0
- package/dist/data-sources/evm-rpc-services/utils/batch.d.mts +28 -0
- package/dist/data-sources/evm-rpc-services/utils/batch.d.mts.map +1 -0
- package/dist/data-sources/evm-rpc-services/utils/batch.mjs +40 -0
- package/dist/data-sources/evm-rpc-services/utils/batch.mjs.map +1 -0
- package/dist/data-sources/evm-rpc-services/utils/index.cjs +7 -0
- package/dist/data-sources/evm-rpc-services/utils/index.cjs.map +1 -0
- package/dist/data-sources/evm-rpc-services/utils/index.d.cts +2 -0
- package/dist/data-sources/evm-rpc-services/utils/index.d.cts.map +1 -0
- package/dist/data-sources/evm-rpc-services/utils/index.d.mts +2 -0
- package/dist/data-sources/evm-rpc-services/utils/index.d.mts.map +1 -0
- package/dist/data-sources/evm-rpc-services/utils/index.mjs +2 -0
- package/dist/data-sources/evm-rpc-services/utils/index.mjs.map +1 -0
- package/dist/data-sources/index.cjs +33 -0
- package/dist/data-sources/index.cjs.map +1 -0
- package/dist/data-sources/index.d.cts +9 -0
- package/dist/data-sources/index.d.cts.map +1 -0
- package/dist/data-sources/index.d.mts +9 -0
- package/dist/data-sources/index.d.mts.map +1 -0
- package/dist/data-sources/index.mjs +15 -0
- package/dist/data-sources/index.mjs.map +1 -0
- package/dist/data-sources/initDataSources.cjs +215 -0
- package/dist/data-sources/initDataSources.cjs.map +1 -0
- package/dist/data-sources/initDataSources.d.cts +140 -0
- package/dist/data-sources/initDataSources.d.cts.map +1 -0
- package/dist/data-sources/initDataSources.d.mts +140 -0
- package/dist/data-sources/initDataSources.d.mts.map +1 -0
- package/dist/data-sources/initDataSources.mjs +210 -0
- package/dist/data-sources/initDataSources.mjs.map +1 -0
- package/dist/index.cjs +47 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +21 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +21 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +25 -0
- package/dist/index.mjs.map +1 -0
- package/dist/logger.cjs +7 -0
- package/dist/logger.cjs.map +1 -0
- package/dist/logger.d.cts +5 -0
- package/dist/logger.d.cts.map +1 -0
- package/dist/logger.d.mts +5 -0
- package/dist/logger.d.mts.map +1 -0
- package/dist/logger.mjs +4 -0
- package/dist/logger.mjs.map +1 -0
- package/dist/middlewares/DetectionMiddleware.cjs +101 -0
- package/dist/middlewares/DetectionMiddleware.cjs.map +1 -0
- package/dist/middlewares/DetectionMiddleware.d.cts +54 -0
- package/dist/middlewares/DetectionMiddleware.d.cts.map +1 -0
- package/dist/middlewares/DetectionMiddleware.d.mts +54 -0
- package/dist/middlewares/DetectionMiddleware.d.mts.map +1 -0
- package/dist/middlewares/DetectionMiddleware.mjs +97 -0
- package/dist/middlewares/DetectionMiddleware.mjs.map +1 -0
- package/dist/middlewares/index.cjs +6 -0
- package/dist/middlewares/index.cjs.map +1 -0
- package/dist/middlewares/index.d.cts +2 -0
- package/dist/middlewares/index.d.cts.map +1 -0
- package/dist/middlewares/index.d.mts +2 -0
- package/dist/middlewares/index.d.mts.map +1 -0
- package/dist/middlewares/index.mjs +2 -0
- package/dist/middlewares/index.mjs.map +1 -0
- package/dist/types.cjs +39 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.cts +433 -0
- package/dist/types.d.cts.map +1 -0
- package/dist/types.d.mts +433 -0
- package/dist/types.d.mts.map +1 -0
- package/dist/types.mjs +35 -0
- package/dist/types.mjs.map +1 -0
- package/dist/utils.cjs +28 -0
- package/dist/utils.cjs.map +1 -0
- package/dist/utils.d.cts +13 -0
- package/dist/utils.d.cts.map +1 -0
- package/dist/utils.d.mts +13 -0
- package/dist/utils.d.mts.map +1 -0
- package/dist/utils.mjs +24 -0
- package/dist/utils.mjs.map +1 -0
- package/package.json +93 -0
|
@@ -0,0 +1,1057 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
3
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
4
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
5
|
+
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
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
7
|
+
};
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
14
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
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;
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.AssetsController = exports.getDefaultAssetsControllerState = void 0;
|
|
19
|
+
const base_controller_1 = require("@metamask/base-controller");
|
|
20
|
+
const utils_1 = require("@metamask/utils");
|
|
21
|
+
const async_mutex_1 = require("async-mutex");
|
|
22
|
+
const bignumber_js_1 = __importDefault(require("bignumber.js"));
|
|
23
|
+
const lodash_1 = require("lodash");
|
|
24
|
+
const logger_1 = require("./logger.cjs");
|
|
25
|
+
const utils_2 = require("./utils.cjs");
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// CONTROLLER CONSTANTS
|
|
28
|
+
// ============================================================================
|
|
29
|
+
const CONTROLLER_NAME = 'AssetsController';
|
|
30
|
+
/** Default polling interval hint for data sources (30 seconds) */
|
|
31
|
+
const DEFAULT_POLLING_INTERVAL_MS = 30000;
|
|
32
|
+
const log = (0, logger_1.createModuleLogger)(logger_1.projectLogger, CONTROLLER_NAME);
|
|
33
|
+
/**
|
|
34
|
+
* Returns the default state for AssetsController.
|
|
35
|
+
*
|
|
36
|
+
* @returns The default AssetsController state with empty metadata, balance, price, and customAssets maps.
|
|
37
|
+
*/
|
|
38
|
+
function getDefaultAssetsControllerState() {
|
|
39
|
+
return {
|
|
40
|
+
assetsMetadata: {},
|
|
41
|
+
assetsBalance: {},
|
|
42
|
+
assetsPrice: {},
|
|
43
|
+
customAssets: {},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
exports.getDefaultAssetsControllerState = getDefaultAssetsControllerState;
|
|
47
|
+
// ============================================================================
|
|
48
|
+
// STATE METADATA
|
|
49
|
+
// ============================================================================
|
|
50
|
+
const stateMetadata = {
|
|
51
|
+
assetsMetadata: {
|
|
52
|
+
persist: true,
|
|
53
|
+
includeInStateLogs: false,
|
|
54
|
+
includeInDebugSnapshot: false,
|
|
55
|
+
usedInUi: true,
|
|
56
|
+
},
|
|
57
|
+
assetsBalance: {
|
|
58
|
+
persist: true,
|
|
59
|
+
includeInStateLogs: false,
|
|
60
|
+
includeInDebugSnapshot: false,
|
|
61
|
+
usedInUi: true,
|
|
62
|
+
},
|
|
63
|
+
assetsPrice: {
|
|
64
|
+
persist: false,
|
|
65
|
+
includeInStateLogs: false,
|
|
66
|
+
includeInDebugSnapshot: false,
|
|
67
|
+
usedInUi: true,
|
|
68
|
+
},
|
|
69
|
+
customAssets: {
|
|
70
|
+
persist: true,
|
|
71
|
+
includeInStateLogs: false,
|
|
72
|
+
includeInDebugSnapshot: false,
|
|
73
|
+
usedInUi: true,
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
// ============================================================================
|
|
77
|
+
// HELPER FUNCTIONS
|
|
78
|
+
// ============================================================================
|
|
79
|
+
function extractChainId(assetId) {
|
|
80
|
+
const parsed = (0, utils_1.parseCaipAssetType)(assetId);
|
|
81
|
+
return parsed.chainId;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Normalizes all asset IDs in a DataResponse.
|
|
85
|
+
* This is applied at the controller level to ensure consistent state
|
|
86
|
+
* regardless of how data sources format their asset IDs.
|
|
87
|
+
*
|
|
88
|
+
* @param response - The DataResponse to normalize.
|
|
89
|
+
* @returns The normalized DataResponse with checksummed EVM addresses.
|
|
90
|
+
*/
|
|
91
|
+
function normalizeResponse(response) {
|
|
92
|
+
const normalized = {};
|
|
93
|
+
if (response.assetsMetadata) {
|
|
94
|
+
normalized.assetsMetadata = {};
|
|
95
|
+
for (const [assetId, metadata] of Object.entries(response.assetsMetadata)) {
|
|
96
|
+
const normalizedId = (0, utils_2.normalizeAssetId)(assetId);
|
|
97
|
+
normalized.assetsMetadata[normalizedId] = metadata;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (response.assetsPrice) {
|
|
101
|
+
normalized.assetsPrice = {};
|
|
102
|
+
for (const [assetId, price] of Object.entries(response.assetsPrice)) {
|
|
103
|
+
const normalizedId = (0, utils_2.normalizeAssetId)(assetId);
|
|
104
|
+
normalized.assetsPrice[normalizedId] = price;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (response.assetsBalance) {
|
|
108
|
+
normalized.assetsBalance = {};
|
|
109
|
+
for (const [accountId, balances] of Object.entries(response.assetsBalance)) {
|
|
110
|
+
normalized.assetsBalance[accountId] = {};
|
|
111
|
+
for (const [assetId, balance] of Object.entries(balances)) {
|
|
112
|
+
const normalizedId = (0, utils_2.normalizeAssetId)(assetId);
|
|
113
|
+
normalized.assetsBalance[accountId][normalizedId] = balance;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Preserve detectedAssets with normalized asset IDs
|
|
118
|
+
if (response.detectedAssets) {
|
|
119
|
+
normalized.detectedAssets = {};
|
|
120
|
+
for (const [accountId, assetIds] of Object.entries(response.detectedAssets)) {
|
|
121
|
+
normalized.detectedAssets[accountId] = assetIds.map((assetId) => (0, utils_2.normalizeAssetId)(assetId));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Preserve errors (chain IDs don't need normalization)
|
|
125
|
+
if (response.errors) {
|
|
126
|
+
normalized.errors = { ...response.errors };
|
|
127
|
+
}
|
|
128
|
+
return normalized;
|
|
129
|
+
}
|
|
130
|
+
// ============================================================================
|
|
131
|
+
// CONTROLLER IMPLEMENTATION
|
|
132
|
+
// ============================================================================
|
|
133
|
+
/**
|
|
134
|
+
* AssetsController provides a unified interface for managing asset balances
|
|
135
|
+
* across all blockchain networks (EVM and non-EVM) and all asset types.
|
|
136
|
+
*
|
|
137
|
+
* ## Core Responsibilities
|
|
138
|
+
*
|
|
139
|
+
* 1. **One-Time Fetch (Sync)**: For initial load, force refresh, or on-demand queries.
|
|
140
|
+
* Uses `getAssets()`, `getAssetsBalance()`, etc. with `forceUpdate: true`.
|
|
141
|
+
*
|
|
142
|
+
* 2. **Async Subscriptions**: Subscribes to data sources for ongoing updates.
|
|
143
|
+
* Data sources push updates via callbacks; the controller updates state.
|
|
144
|
+
*
|
|
145
|
+
* 3. **Dynamic Source Selection**: Routes requests to appropriate data sources
|
|
146
|
+
* based on which chains they support. When active chains change, the controller
|
|
147
|
+
* dynamically adjusts subscriptions.
|
|
148
|
+
*
|
|
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
|
|
156
|
+
*
|
|
157
|
+
* ## Architecture
|
|
158
|
+
*
|
|
159
|
+
* - Data sources declare their supported chains (async, can change over time)
|
|
160
|
+
* - Data sources are responsible for their own update mechanisms (WebSocket, polling, events)
|
|
161
|
+
* - The controller does NOT manage polling - it simply receives pushed updates
|
|
162
|
+
*/
|
|
163
|
+
class AssetsController extends base_controller_1.BaseController {
|
|
164
|
+
constructor({ messenger, state = {}, defaultUpdateInterval = DEFAULT_POLLING_INTERVAL_MS, isEnabled = () => true, }) {
|
|
165
|
+
super({
|
|
166
|
+
name: CONTROLLER_NAME,
|
|
167
|
+
messenger,
|
|
168
|
+
metadata: stateMetadata,
|
|
169
|
+
state: {
|
|
170
|
+
...getDefaultAssetsControllerState(),
|
|
171
|
+
...state,
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
_AssetsController_instances.add(this);
|
|
175
|
+
/** Whether the controller is enabled */
|
|
176
|
+
_AssetsController_isEnabled.set(this, void 0);
|
|
177
|
+
/** Default update interval hint passed to data sources */
|
|
178
|
+
_AssetsController_defaultUpdateInterval.set(this, void 0);
|
|
179
|
+
_AssetsController_controllerMutex.set(this, new async_mutex_1.Mutex());
|
|
180
|
+
/**
|
|
181
|
+
* Active balance subscriptions keyed by account ID.
|
|
182
|
+
* Each account has one logical subscription that may span multiple data sources.
|
|
183
|
+
* For example, if WebSocket covers chains A,B and RPC covers chain C,
|
|
184
|
+
* the account subscribes to both data sources for its chains.
|
|
185
|
+
*/
|
|
186
|
+
_AssetsController_activeSubscriptions.set(this, new Map());
|
|
187
|
+
/** Currently enabled chains from NetworkEnablementController */
|
|
188
|
+
_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());
|
|
195
|
+
__classPrivateFieldSet(this, _AssetsController_isEnabled, isEnabled(), "f");
|
|
196
|
+
__classPrivateFieldSet(this, _AssetsController_defaultUpdateInterval, defaultUpdateInterval, "f");
|
|
197
|
+
if (!__classPrivateFieldGet(this, _AssetsController_isEnabled, "f")) {
|
|
198
|
+
log('AssetsController is disabled, skipping initialization');
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
log('Initializing AssetsController', {
|
|
202
|
+
defaultUpdateInterval,
|
|
203
|
+
});
|
|
204
|
+
__classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_initializeState).call(this);
|
|
205
|
+
__classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeToEvents).call(this);
|
|
206
|
+
__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
|
+
}
|
|
233
|
+
// ============================================================================
|
|
234
|
+
// DATA SOURCE CHAIN MANAGEMENT
|
|
235
|
+
// ============================================================================
|
|
236
|
+
/**
|
|
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.
|
|
240
|
+
*
|
|
241
|
+
* Data sources should call this via `AssetsController:activeChainsUpdate` action.
|
|
242
|
+
*
|
|
243
|
+
* @param dataSourceId - The identifier of the data source reporting the change.
|
|
244
|
+
* @param activeChains - Array of currently active chain IDs for this source.
|
|
245
|
+
*/
|
|
246
|
+
handleActiveChainsUpdate(dataSourceId, activeChains) {
|
|
247
|
+
log('Data source active chains changed', {
|
|
248
|
+
dataSourceId,
|
|
249
|
+
chainCount: activeChains.length,
|
|
250
|
+
chains: activeChains,
|
|
251
|
+
});
|
|
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));
|
|
259
|
+
if (addedChains.length > 0 || removedChains.length > 0) {
|
|
260
|
+
// Refresh subscriptions to use updated data source availability
|
|
261
|
+
__classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeToDataSources).call(this);
|
|
262
|
+
}
|
|
263
|
+
// If chains were added and we have selected accounts, do one-time fetch
|
|
264
|
+
if (addedChains.length > 0 && __classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get).length > 0) {
|
|
265
|
+
const addedEnabledChains = addedChains.filter((chain) => __classPrivateFieldGet(this, _AssetsController_enabledChains, "f").has(chain));
|
|
266
|
+
if (addedEnabledChains.length > 0) {
|
|
267
|
+
log('Fetching balances for newly added chains', { addedEnabledChains });
|
|
268
|
+
this.getAssets(__classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get), {
|
|
269
|
+
chainIds: addedEnabledChains,
|
|
270
|
+
forceUpdate: true,
|
|
271
|
+
}).catch((error) => {
|
|
272
|
+
log('Failed to fetch balance for added chains', { error });
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
// ============================================================================
|
|
278
|
+
// PUBLIC API: QUERY METHODS
|
|
279
|
+
// ============================================================================
|
|
280
|
+
async getAssets(accounts, options) {
|
|
281
|
+
const chainIds = options?.chainIds ?? [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")];
|
|
282
|
+
const assetTypes = options?.assetTypes ?? ['fungible'];
|
|
283
|
+
const dataTypes = options?.dataTypes ?? ['balance', 'metadata', 'price'];
|
|
284
|
+
// Collect custom assets for all requested accounts
|
|
285
|
+
const customAssets = [];
|
|
286
|
+
for (const account of accounts) {
|
|
287
|
+
const accountCustomAssets = this.getCustomAssets(account.id);
|
|
288
|
+
customAssets.push(...accountCustomAssets);
|
|
289
|
+
}
|
|
290
|
+
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,
|
|
301
|
+
assetTypes,
|
|
302
|
+
dataTypes,
|
|
303
|
+
customAssets: customAssets.length > 0 ? customAssets : undefined,
|
|
304
|
+
forceUpdate: true,
|
|
305
|
+
});
|
|
306
|
+
await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_updateState).call(this, response);
|
|
307
|
+
}
|
|
308
|
+
return __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getAssetsFromState).call(this, accounts, chainIds, assetTypes);
|
|
309
|
+
}
|
|
310
|
+
async getAssetsBalance(accounts, options) {
|
|
311
|
+
// Reuse getAssets with dataTypes: ['balance'] only
|
|
312
|
+
const assets = await this.getAssets(accounts, {
|
|
313
|
+
chainIds: options?.chainIds,
|
|
314
|
+
assetTypes: options?.assetTypes,
|
|
315
|
+
forceUpdate: options?.forceUpdate,
|
|
316
|
+
dataTypes: ['balance', 'metadata'],
|
|
317
|
+
});
|
|
318
|
+
// Extract just the balance from each asset
|
|
319
|
+
const result = {};
|
|
320
|
+
for (const [accountId, accountAssets] of Object.entries(assets)) {
|
|
321
|
+
result[accountId] = {};
|
|
322
|
+
for (const [assetId, asset] of Object.entries(accountAssets)) {
|
|
323
|
+
if (asset.balance) {
|
|
324
|
+
result[accountId][assetId] = asset.balance;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return result;
|
|
329
|
+
}
|
|
330
|
+
getAssetMetadata(assetId) {
|
|
331
|
+
return this.state.assetsMetadata[assetId];
|
|
332
|
+
}
|
|
333
|
+
async getAssetsPrice(accounts, options) {
|
|
334
|
+
const assets = await this.getAssets(accounts, {
|
|
335
|
+
chainIds: options?.chainIds,
|
|
336
|
+
assetTypes: options?.assetTypes,
|
|
337
|
+
forceUpdate: options?.forceUpdate,
|
|
338
|
+
dataTypes: ['price'],
|
|
339
|
+
});
|
|
340
|
+
// Extract just the price from each asset (flattened across accounts)
|
|
341
|
+
const result = {};
|
|
342
|
+
for (const accountAssets of Object.values(assets)) {
|
|
343
|
+
for (const [assetId, asset] of Object.entries(accountAssets)) {
|
|
344
|
+
if (asset.price) {
|
|
345
|
+
result[assetId] = asset.price;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return result;
|
|
350
|
+
}
|
|
351
|
+
// ============================================================================
|
|
352
|
+
// CUSTOM ASSETS MANAGEMENT
|
|
353
|
+
// ============================================================================
|
|
354
|
+
/**
|
|
355
|
+
* Add a custom asset for an account.
|
|
356
|
+
* Custom assets are included in subscription and fetch operations.
|
|
357
|
+
*
|
|
358
|
+
* @param accountId - The account ID to add the custom asset for.
|
|
359
|
+
* @param assetId - The CAIP-19 asset ID to add.
|
|
360
|
+
*/
|
|
361
|
+
async addCustomAsset(accountId, assetId) {
|
|
362
|
+
const normalizedAssetId = (0, utils_2.normalizeAssetId)(assetId);
|
|
363
|
+
log('Adding custom asset', { accountId, assetId: normalizedAssetId });
|
|
364
|
+
this.update((state) => {
|
|
365
|
+
const customAssets = state.customAssets;
|
|
366
|
+
if (!customAssets[accountId]) {
|
|
367
|
+
customAssets[accountId] = [];
|
|
368
|
+
}
|
|
369
|
+
// Only add if not already present
|
|
370
|
+
if (!customAssets[accountId].includes(normalizedAssetId)) {
|
|
371
|
+
customAssets[accountId].push(normalizedAssetId);
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
// Fetch data for the newly added custom asset
|
|
375
|
+
const account = __classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get).find((a) => a.id === accountId);
|
|
376
|
+
if (account) {
|
|
377
|
+
const chainId = extractChainId(normalizedAssetId);
|
|
378
|
+
await this.getAssets([account], {
|
|
379
|
+
chainIds: [chainId],
|
|
380
|
+
forceUpdate: true,
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Remove a custom asset from an account.
|
|
386
|
+
*
|
|
387
|
+
* @param accountId - The account ID to remove the custom asset from.
|
|
388
|
+
* @param assetId - The CAIP-19 asset ID to remove.
|
|
389
|
+
*/
|
|
390
|
+
removeCustomAsset(accountId, assetId) {
|
|
391
|
+
const normalizedAssetId = (0, utils_2.normalizeAssetId)(assetId);
|
|
392
|
+
log('Removing custom asset', { accountId, assetId: normalizedAssetId });
|
|
393
|
+
this.update((state) => {
|
|
394
|
+
const customAssets = state.customAssets;
|
|
395
|
+
if (customAssets[accountId]) {
|
|
396
|
+
customAssets[accountId] = customAssets[accountId].filter((id) => id !== normalizedAssetId);
|
|
397
|
+
// Clean up empty arrays
|
|
398
|
+
if (customAssets[accountId].length === 0) {
|
|
399
|
+
delete customAssets[accountId];
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Get all custom assets for an account.
|
|
406
|
+
*
|
|
407
|
+
* @param accountId - The account ID to get custom assets for.
|
|
408
|
+
* @returns Array of CAIP-19 asset IDs for the account's custom assets.
|
|
409
|
+
*/
|
|
410
|
+
getCustomAssets(accountId) {
|
|
411
|
+
return (this.state.customAssets[accountId] ?? []);
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Subscribe to price updates for all assets held by the given accounts.
|
|
415
|
+
* Polls PriceDataSource which fetches prices from balance state.
|
|
416
|
+
*
|
|
417
|
+
* @param accounts - Accounts to subscribe price updates for.
|
|
418
|
+
* @param chainIds - Chain IDs to filter prices for.
|
|
419
|
+
* @param options - Subscription options.
|
|
420
|
+
* @param options.updateInterval - Polling interval in ms.
|
|
421
|
+
*/
|
|
422
|
+
subscribeAssetsPrice(accounts, chainIds, options = {}) {
|
|
423
|
+
const { updateInterval = __classPrivateFieldGet(this, _AssetsController_defaultUpdateInterval, "f") } = options;
|
|
424
|
+
const subscriptionKey = 'ds:PriceDataSource';
|
|
425
|
+
const existingSubscription = __classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").get(subscriptionKey);
|
|
426
|
+
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,
|
|
433
|
+
dataTypes: ['price'],
|
|
434
|
+
updateInterval,
|
|
435
|
+
},
|
|
436
|
+
subscriptionId: subscriptionKey,
|
|
437
|
+
isUpdate,
|
|
438
|
+
})
|
|
439
|
+
.catch(console.error);
|
|
440
|
+
// Track subscription
|
|
441
|
+
const subscription = {
|
|
442
|
+
chains: chainIds,
|
|
443
|
+
accountId: subscriptionKey,
|
|
444
|
+
assetTypes: ['fungible'],
|
|
445
|
+
dataTypes: ['price'],
|
|
446
|
+
unsubscribe: () => {
|
|
447
|
+
__classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").delete(subscriptionKey);
|
|
448
|
+
},
|
|
449
|
+
};
|
|
450
|
+
__classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").set(subscriptionKey, subscription);
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Unsubscribe from price updates.
|
|
454
|
+
*/
|
|
455
|
+
unsubscribeAssetsPrice() {
|
|
456
|
+
const subscriptionKey = 'ds:PriceDataSource';
|
|
457
|
+
const existingSubscription = __classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").get(subscriptionKey);
|
|
458
|
+
if (!existingSubscription) {
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
// Fire-and-forget - errors are handled internally by PriceDataSource
|
|
462
|
+
this.messenger
|
|
463
|
+
.call('PriceDataSource:unsubscribe', subscriptionKey)
|
|
464
|
+
.catch(console.error);
|
|
465
|
+
existingSubscription.unsubscribe();
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Handle assets updated from a data source.
|
|
469
|
+
* Called via `AssetsController:assetsUpdate` action by data sources.
|
|
470
|
+
*
|
|
471
|
+
* @param response - The data response with updated assets
|
|
472
|
+
* @param sourceId - The data source ID reporting the update
|
|
473
|
+
*/
|
|
474
|
+
async handleAssetsUpdate(response, sourceId) {
|
|
475
|
+
log('Assets updated from data source', {
|
|
476
|
+
sourceId,
|
|
477
|
+
hasBalance: Boolean(response.assetsBalance),
|
|
478
|
+
hasPrice: Boolean(response.assetsPrice),
|
|
479
|
+
});
|
|
480
|
+
await __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_handleSubscriptionUpdate).call(this, response, sourceId);
|
|
481
|
+
}
|
|
482
|
+
// ============================================================================
|
|
483
|
+
// CLEANUP
|
|
484
|
+
// ============================================================================
|
|
485
|
+
destroy() {
|
|
486
|
+
log('Destroying AssetsController', {
|
|
487
|
+
dataSourceCount: __classPrivateFieldGet(this, _AssetsController_dataSources, "f").size,
|
|
488
|
+
subscriptionCount: __classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").size,
|
|
489
|
+
});
|
|
490
|
+
// Clear data sources
|
|
491
|
+
__classPrivateFieldGet(this, _AssetsController_dataSources, "f").clear();
|
|
492
|
+
// Stop all active subscriptions
|
|
493
|
+
__classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_stop).call(this);
|
|
494
|
+
// Unregister action handlers
|
|
495
|
+
this.messenger.unregisterActionHandler('AssetsController:getAssets');
|
|
496
|
+
this.messenger.unregisterActionHandler('AssetsController:getAssetsBalance');
|
|
497
|
+
this.messenger.unregisterActionHandler('AssetsController:getAssetMetadata');
|
|
498
|
+
this.messenger.unregisterActionHandler('AssetsController:getAssetsPrice');
|
|
499
|
+
this.messenger.unregisterActionHandler('AssetsController:activeChainsUpdate');
|
|
500
|
+
this.messenger.unregisterActionHandler('AssetsController:assetsUpdate');
|
|
501
|
+
this.messenger.unregisterActionHandler('AssetsController:addCustomAsset');
|
|
502
|
+
this.messenger.unregisterActionHandler('AssetsController:removeCustomAsset');
|
|
503
|
+
this.messenger.unregisterActionHandler('AssetsController:getCustomAssets');
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
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() {
|
|
508
|
+
return this.messenger.call('AccountTreeController:getAccountsFromSelectedAccountGroup');
|
|
509
|
+
}, _AssetsController_initializeState = function _AssetsController_initializeState() {
|
|
510
|
+
const { enabledNetworkMap } = this.messenger.call('NetworkEnablementController:getState');
|
|
511
|
+
__classPrivateFieldSet(this, _AssetsController_enabledChains, __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_extractEnabledChains).call(this, enabledNetworkMap), "f");
|
|
512
|
+
log('Initialized state', {
|
|
513
|
+
enabledNetworkMap,
|
|
514
|
+
enabledChains: __classPrivateFieldGet(this, _AssetsController_enabledChains, "f"),
|
|
515
|
+
});
|
|
516
|
+
}, _AssetsController_extractEnabledChains = function _AssetsController_extractEnabledChains(enabledNetworkMap) {
|
|
517
|
+
const chains = new Set();
|
|
518
|
+
for (const [namespace, networks] of Object.entries(enabledNetworkMap)) {
|
|
519
|
+
for (const [reference, isEnabled] of Object.entries(networks)) {
|
|
520
|
+
if (isEnabled) {
|
|
521
|
+
// Check if reference is already a full CAIP-2 chain ID (contains colon)
|
|
522
|
+
if (reference.includes(':')) {
|
|
523
|
+
// Already a full chain ID, use as-is
|
|
524
|
+
chains.add(reference);
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
// Normalize EIP155 chain references from hex to decimal (CAIP-2 standard)
|
|
528
|
+
const normalizedReference = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_normalizeChainReference).call(this, namespace, reference);
|
|
529
|
+
chains.add(`${namespace}:${normalizedReference}`);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return chains;
|
|
535
|
+
}, _AssetsController_normalizeChainReference = function _AssetsController_normalizeChainReference(namespace, reference) {
|
|
536
|
+
if (namespace === 'eip155' && reference.startsWith('0x')) {
|
|
537
|
+
// Convert hex to decimal for EIP155 chains
|
|
538
|
+
return parseInt(reference, 16).toString();
|
|
539
|
+
}
|
|
540
|
+
return reference;
|
|
541
|
+
}, _AssetsController_subscribeToEvents = function _AssetsController_subscribeToEvents() {
|
|
542
|
+
// Subscribe to account group changes (when user switches between account groups like Account 1 -> Account 2)
|
|
543
|
+
this.messenger.subscribe('AccountTreeController:selectedAccountGroupChange', () => {
|
|
544
|
+
__classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_handleAccountGroupChanged).call(this).catch(console.error);
|
|
545
|
+
});
|
|
546
|
+
// Subscribe to network enablement changes (only enabledNetworkMap)
|
|
547
|
+
this.messenger.subscribe('NetworkEnablementController:stateChange', ({ enabledNetworkMap }) => {
|
|
548
|
+
__classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_handleEnabledNetworksChanged).call(this, enabledNetworkMap).catch(console.error);
|
|
549
|
+
});
|
|
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
|
+
// Keyring lifecycle: start when unlocked, stop when locked
|
|
554
|
+
this.messenger.subscribe('KeyringController:unlock', () => __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_start).call(this));
|
|
555
|
+
this.messenger.subscribe('KeyringController:lock', () => __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_stop).call(this));
|
|
556
|
+
}, _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));
|
|
566
|
+
}, _AssetsController_executeMiddlewares =
|
|
567
|
+
// ============================================================================
|
|
568
|
+
// MIDDLEWARE EXECUTION
|
|
569
|
+
// ============================================================================
|
|
570
|
+
/**
|
|
571
|
+
* Execute middlewares with request/response context.
|
|
572
|
+
*
|
|
573
|
+
* @param middlewares - Middlewares to execute in order.
|
|
574
|
+
* @param request - The data request.
|
|
575
|
+
* @param initialResponse - Optional initial response (for enriching existing data).
|
|
576
|
+
* @returns The final DataResponse after all middlewares have processed.
|
|
577
|
+
*/
|
|
578
|
+
async function _AssetsController_executeMiddlewares(middlewares, request, initialResponse = {}) {
|
|
579
|
+
const chain = middlewares.reduceRight((next, middleware) => async (ctx) => {
|
|
580
|
+
try {
|
|
581
|
+
return await middleware(ctx, next);
|
|
582
|
+
}
|
|
583
|
+
catch (error) {
|
|
584
|
+
console.error('[AssetsController] Middleware failed:', error);
|
|
585
|
+
return next(ctx);
|
|
586
|
+
}
|
|
587
|
+
}, async (ctx) => ctx);
|
|
588
|
+
const result = await chain({
|
|
589
|
+
request,
|
|
590
|
+
response: initialResponse,
|
|
591
|
+
getAssetsState: () => this.state,
|
|
592
|
+
});
|
|
593
|
+
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
|
+
}, _AssetsController_updateState =
|
|
621
|
+
// ============================================================================
|
|
622
|
+
// STATE MANAGEMENT
|
|
623
|
+
// ============================================================================
|
|
624
|
+
async function _AssetsController_updateState(response) {
|
|
625
|
+
// Normalize asset IDs (checksum EVM addresses) before storing in state
|
|
626
|
+
const normalizedResponse = normalizeResponse(response);
|
|
627
|
+
const releaseLock = await __classPrivateFieldGet(this, _AssetsController_controllerMutex, "f").acquire();
|
|
628
|
+
try {
|
|
629
|
+
const previousState = this.state;
|
|
630
|
+
const previousPrices = { ...this.state.assetsPrice };
|
|
631
|
+
// Use detectedAssets from response (assets without metadata)
|
|
632
|
+
const detectedAssets = normalizedResponse.detectedAssets ?? {};
|
|
633
|
+
// Track actual changes for logging
|
|
634
|
+
const changedBalances = [];
|
|
635
|
+
const changedMetadata = [];
|
|
636
|
+
this.update((state) => {
|
|
637
|
+
// Use type assertions to avoid deep type instantiation issues with Draft<Json>
|
|
638
|
+
const metadata = state.assetsMetadata;
|
|
639
|
+
const balances = state.assetsBalance;
|
|
640
|
+
const prices = state.assetsPrice;
|
|
641
|
+
if (normalizedResponse.assetsMetadata) {
|
|
642
|
+
for (const [key, value] of Object.entries(normalizedResponse.assetsMetadata)) {
|
|
643
|
+
if (!(0, lodash_1.isEqual)(previousState.assetsMetadata[key], value)) {
|
|
644
|
+
changedMetadata.push(key);
|
|
645
|
+
}
|
|
646
|
+
metadata[key] = value;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
if (normalizedResponse.assetsBalance) {
|
|
650
|
+
for (const [accountId, accountBalances] of Object.entries(normalizedResponse.assetsBalance)) {
|
|
651
|
+
const previousBalances = previousState.assetsBalance[accountId] ?? {};
|
|
652
|
+
if (!balances[accountId]) {
|
|
653
|
+
balances[accountId] = {};
|
|
654
|
+
}
|
|
655
|
+
for (const [assetId, balance] of Object.entries(accountBalances)) {
|
|
656
|
+
const previousBalance = previousBalances[assetId];
|
|
657
|
+
const balanceData = balance;
|
|
658
|
+
const newAmount = balanceData.amount;
|
|
659
|
+
const oldAmount = previousBalance?.amount;
|
|
660
|
+
// Track if balance actually changed
|
|
661
|
+
if (oldAmount !== newAmount) {
|
|
662
|
+
changedBalances.push({
|
|
663
|
+
accountId,
|
|
664
|
+
assetId,
|
|
665
|
+
oldAmount,
|
|
666
|
+
newAmount,
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
Object.assign(balances[accountId], accountBalances);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
// Update prices in state
|
|
674
|
+
if (normalizedResponse.assetsPrice) {
|
|
675
|
+
for (const [key, value] of Object.entries(normalizedResponse.assetsPrice)) {
|
|
676
|
+
prices[key] = value;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
});
|
|
680
|
+
// Calculate changed prices
|
|
681
|
+
const changedPriceAssets = normalizedResponse.assetsPrice
|
|
682
|
+
? Object.keys(normalizedResponse.assetsPrice).filter((assetId) => !(0, lodash_1.isEqual)(previousPrices[assetId], normalizedResponse.assetsPrice?.[assetId]))
|
|
683
|
+
: [];
|
|
684
|
+
// Log only actual changes
|
|
685
|
+
if (changedBalances.length > 0 ||
|
|
686
|
+
changedMetadata.length > 0 ||
|
|
687
|
+
changedPriceAssets.length > 0) {
|
|
688
|
+
log('State updated', {
|
|
689
|
+
changedBalances: changedBalances.length > 0 ? changedBalances : undefined,
|
|
690
|
+
changedMetadataCount: changedMetadata.length > 0 ? changedMetadata.length : undefined,
|
|
691
|
+
changedPricesCount: changedPriceAssets.length > 0
|
|
692
|
+
? changedPriceAssets.length
|
|
693
|
+
: undefined,
|
|
694
|
+
newAssets: Object.keys(detectedAssets).length > 0
|
|
695
|
+
? Object.entries(detectedAssets).map(([accountId, assets]) => ({
|
|
696
|
+
accountId,
|
|
697
|
+
assets,
|
|
698
|
+
}))
|
|
699
|
+
: undefined,
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
// Publish balance changed events
|
|
703
|
+
for (const change of changedBalances) {
|
|
704
|
+
this.messenger.publish('AssetsController:balanceChanged', {
|
|
705
|
+
accountId: change.accountId,
|
|
706
|
+
assetId: change.assetId,
|
|
707
|
+
previousAmount: change.oldAmount ?? '0',
|
|
708
|
+
newAmount: change.newAmount,
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
// Publish price changed event with full price data
|
|
712
|
+
if (changedPriceAssets.length > 0 && normalizedResponse.assetsPrice) {
|
|
713
|
+
// Build prices object with only changed prices
|
|
714
|
+
const changedPrices = {};
|
|
715
|
+
for (const assetId of changedPriceAssets) {
|
|
716
|
+
const price = normalizedResponse.assetsPrice[assetId];
|
|
717
|
+
if (price) {
|
|
718
|
+
changedPrices[assetId] = price;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
this.messenger.publish('AssetsController:priceChanged', {
|
|
722
|
+
prices: changedPrices,
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
// Publish assets detected events
|
|
726
|
+
for (const [accountId, assetIds] of Object.entries(detectedAssets)) {
|
|
727
|
+
if (assetIds.length > 0) {
|
|
728
|
+
this.messenger.publish('AssetsController:assetsDetected', {
|
|
729
|
+
accountId,
|
|
730
|
+
assetIds,
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
finally {
|
|
736
|
+
releaseLock();
|
|
737
|
+
}
|
|
738
|
+
}, _AssetsController_getAssetsFromState = function _AssetsController_getAssetsFromState(accounts, chainIds, assetTypes) {
|
|
739
|
+
const result = {};
|
|
740
|
+
// Convert to Sets for O(1) lookups
|
|
741
|
+
const chainIdSet = new Set(chainIds);
|
|
742
|
+
const assetTypeSet = new Set(assetTypes);
|
|
743
|
+
for (const account of accounts) {
|
|
744
|
+
result[account.id] = {};
|
|
745
|
+
const accountBalances = this.state.assetsBalance[account.id] ?? {};
|
|
746
|
+
for (const [assetId, balance] of Object.entries(accountBalances)) {
|
|
747
|
+
const typedAssetId = assetId;
|
|
748
|
+
const assetChainId = extractChainId(typedAssetId);
|
|
749
|
+
if (!chainIdSet.has(assetChainId)) {
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
const metadataRaw = this.state.assetsMetadata[typedAssetId];
|
|
753
|
+
// Skip assets without metadata
|
|
754
|
+
if (!metadataRaw) {
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
757
|
+
const metadata = metadataRaw;
|
|
758
|
+
// Filter by asset type
|
|
759
|
+
const tokenAssetType = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_tokenStandardToAssetType).call(this, metadata.type);
|
|
760
|
+
if (!assetTypeSet.has(tokenAssetType)) {
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
const typedBalance = balance;
|
|
764
|
+
const priceRaw = this.state.assetsPrice[typedAssetId];
|
|
765
|
+
const price = priceRaw ?? {
|
|
766
|
+
price: 0,
|
|
767
|
+
lastUpdated: 0,
|
|
768
|
+
};
|
|
769
|
+
// Compute fiat value using BigNumber for precision
|
|
770
|
+
// Note: typedBalance.amount is already in human-readable format (e.g., "1" for 1 ETH)
|
|
771
|
+
// so we do NOT divide by 10^decimals here
|
|
772
|
+
const balanceAmount = new bignumber_js_1.default(typedBalance.amount || '0');
|
|
773
|
+
const fiatValue = balanceAmount
|
|
774
|
+
.multipliedBy(price.price || 0)
|
|
775
|
+
.toNumber();
|
|
776
|
+
const asset = {
|
|
777
|
+
id: typedAssetId,
|
|
778
|
+
chainId: assetChainId,
|
|
779
|
+
balance: typedBalance,
|
|
780
|
+
metadata,
|
|
781
|
+
price,
|
|
782
|
+
fiatValue,
|
|
783
|
+
};
|
|
784
|
+
result[account.id][typedAssetId] = asset;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
return result;
|
|
788
|
+
}, _AssetsController_tokenStandardToAssetType = function _AssetsController_tokenStandardToAssetType(tokenStandard) {
|
|
789
|
+
switch (tokenStandard) {
|
|
790
|
+
case 'native':
|
|
791
|
+
case 'erc20':
|
|
792
|
+
case 'spl':
|
|
793
|
+
return 'fungible';
|
|
794
|
+
case 'erc721':
|
|
795
|
+
return 'nft';
|
|
796
|
+
case 'erc1155':
|
|
797
|
+
// ERC1155 can be either fungible or non-fungible, treat as collectible
|
|
798
|
+
return 'collectible';
|
|
799
|
+
default:
|
|
800
|
+
// Unknown standards default to fungible
|
|
801
|
+
return 'fungible';
|
|
802
|
+
}
|
|
803
|
+
}, _AssetsController_start = function _AssetsController_start() {
|
|
804
|
+
log('Starting asset tracking', {
|
|
805
|
+
selectedAccountCount: __classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get).length,
|
|
806
|
+
enabledChainCount: __classPrivateFieldGet(this, _AssetsController_enabledChains, "f").size,
|
|
807
|
+
});
|
|
808
|
+
__classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeToDataSources).call(this);
|
|
809
|
+
if (__classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get).length > 0) {
|
|
810
|
+
this.getAssets(__classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get), {
|
|
811
|
+
chainIds: [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")],
|
|
812
|
+
forceUpdate: true,
|
|
813
|
+
}).catch((error) => {
|
|
814
|
+
log('Failed to fetch assets', error);
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
}, _AssetsController_stop = function _AssetsController_stop() {
|
|
818
|
+
log('Stopping asset tracking', {
|
|
819
|
+
activeSubscriptionCount: __classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").size,
|
|
820
|
+
hasPriceSubscription: __classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").has('ds:PriceDataSource'),
|
|
821
|
+
});
|
|
822
|
+
// Stop price subscription first (uses direct messenger call)
|
|
823
|
+
this.unsubscribeAssetsPrice();
|
|
824
|
+
// Stop balance subscriptions by properly notifying data sources via messenger
|
|
825
|
+
// This ensures data sources stop their polling timers
|
|
826
|
+
// Convert to array first to avoid modifying map during iteration
|
|
827
|
+
const subscriptionKeys = [...__classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").keys()];
|
|
828
|
+
for (const subscriptionKey of subscriptionKeys) {
|
|
829
|
+
// Extract sourceId from subscription key (format: "ds:${sourceId}")
|
|
830
|
+
if (subscriptionKey.startsWith('ds:')) {
|
|
831
|
+
const sourceId = subscriptionKey.slice(3);
|
|
832
|
+
__classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_unsubscribeDataSource).call(this, sourceId);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
__classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").clear();
|
|
836
|
+
}, _AssetsController_subscribeToDataSources = function _AssetsController_subscribeToDataSources() {
|
|
837
|
+
if (__classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get).length === 0) {
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
// Subscribe to balance updates (batched by data source)
|
|
841
|
+
__classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeAssetsBalance).call(this);
|
|
842
|
+
// Subscribe to price updates for all assets held by selected accounts
|
|
843
|
+
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
|
|
849
|
+
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;
|
|
863
|
+
}
|
|
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) {
|
|
867
|
+
continue;
|
|
868
|
+
}
|
|
869
|
+
// Subscribe with ONE call per data source
|
|
870
|
+
__classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeToDataSource).call(this, sourceId, accountsForSource, assignedChains);
|
|
871
|
+
}
|
|
872
|
+
}, _AssetsController_buildChainToAccountsMap = function _AssetsController_buildChainToAccountsMap(accounts, chainsToSubscribe) {
|
|
873
|
+
const chainToAccounts = new Map();
|
|
874
|
+
for (const account of accounts) {
|
|
875
|
+
const accountChains = __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_getEnabledChainsForAccount).call(this, account);
|
|
876
|
+
for (const chainId of accountChains) {
|
|
877
|
+
if (!chainsToSubscribe.has(chainId)) {
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
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);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
return accounts;
|
|
899
|
+
}, _AssetsController_subscribeToDataSource = function _AssetsController_subscribeToDataSource(sourceId, accounts, chains) {
|
|
900
|
+
const subscriptionKey = `ds:${sourceId}`;
|
|
901
|
+
const existingSubscription = __classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").get(subscriptionKey);
|
|
902
|
+
const isUpdate = existingSubscription !== undefined;
|
|
903
|
+
log('Subscribe to data source', {
|
|
904
|
+
sourceId,
|
|
905
|
+
subscriptionKey,
|
|
906
|
+
isUpdate,
|
|
907
|
+
accountCount: accounts.length,
|
|
908
|
+
chainCount: chains.length,
|
|
909
|
+
});
|
|
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);
|
|
929
|
+
// Track subscription
|
|
930
|
+
const subscription = {
|
|
931
|
+
chains,
|
|
932
|
+
accountId: subscriptionKey,
|
|
933
|
+
assetTypes: ['fungible'],
|
|
934
|
+
dataTypes: ['balance', 'price'],
|
|
935
|
+
unsubscribe: () => {
|
|
936
|
+
__classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").delete(subscriptionKey);
|
|
937
|
+
},
|
|
938
|
+
};
|
|
939
|
+
__classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").set(subscriptionKey, subscription);
|
|
940
|
+
}, _AssetsController_unsubscribeDataSource = function _AssetsController_unsubscribeDataSource(sourceId) {
|
|
941
|
+
const subscriptionKey = `ds:${sourceId}`;
|
|
942
|
+
const existingSubscription = __classPrivateFieldGet(this, _AssetsController_activeSubscriptions, "f").get(subscriptionKey);
|
|
943
|
+
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
|
+
});
|
|
955
|
+
existingSubscription.unsubscribe();
|
|
956
|
+
}
|
|
957
|
+
}, _AssetsController_getEnabledChainsForAccount = function _AssetsController_getEnabledChainsForAccount(account) {
|
|
958
|
+
// Account scopes are CAIP-2 chain IDs like "eip155:1", "solana:mainnet", "bip122:..."
|
|
959
|
+
const scopes = account.scopes ?? [];
|
|
960
|
+
const result = [];
|
|
961
|
+
for (const scope of scopes) {
|
|
962
|
+
const [namespace, reference] = scope.split(':');
|
|
963
|
+
// Wildcard scope (e.g., "eip155:0" means all enabled chains in that namespace)
|
|
964
|
+
if (reference === '0') {
|
|
965
|
+
for (const chain of __classPrivateFieldGet(this, _AssetsController_enabledChains, "f")) {
|
|
966
|
+
if (chain.startsWith(`${namespace}:`)) {
|
|
967
|
+
result.push(chain);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
else if (namespace === 'eip155' && reference?.startsWith('0x')) {
|
|
972
|
+
// Normalize hex to decimal for EIP155
|
|
973
|
+
result.push(`eip155:${parseInt(reference, 16)}`);
|
|
974
|
+
}
|
|
975
|
+
else {
|
|
976
|
+
result.push(scope);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
return result;
|
|
980
|
+
}, _AssetsController_handleAccountGroupChanged =
|
|
981
|
+
// ============================================================================
|
|
982
|
+
// EVENT HANDLERS
|
|
983
|
+
// ============================================================================
|
|
984
|
+
async function _AssetsController_handleAccountGroupChanged() {
|
|
985
|
+
const accounts = __classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get);
|
|
986
|
+
log('Account group changed', {
|
|
987
|
+
accountCount: accounts.length,
|
|
988
|
+
accountIds: accounts.map((a) => a.id),
|
|
989
|
+
});
|
|
990
|
+
// Subscribe and fetch for the new account group
|
|
991
|
+
__classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeToDataSources).call(this);
|
|
992
|
+
if (accounts.length > 0) {
|
|
993
|
+
await this.getAssets(accounts, {
|
|
994
|
+
chainIds: [...__classPrivateFieldGet(this, _AssetsController_enabledChains, "f")],
|
|
995
|
+
forceUpdate: true,
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
}, _AssetsController_handleEnabledNetworksChanged = async function _AssetsController_handleEnabledNetworksChanged(enabledNetworkMap) {
|
|
999
|
+
const previousChains = __classPrivateFieldGet(this, _AssetsController_enabledChains, "f");
|
|
1000
|
+
__classPrivateFieldSet(this, _AssetsController_enabledChains, __classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_extractEnabledChains).call(this, enabledNetworkMap), "f");
|
|
1001
|
+
// Find newly enabled chains (in new set but not in previous)
|
|
1002
|
+
const addedChains = [];
|
|
1003
|
+
for (const chain of __classPrivateFieldGet(this, _AssetsController_enabledChains, "f")) {
|
|
1004
|
+
if (!previousChains.has(chain)) {
|
|
1005
|
+
addedChains.push(chain);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
// Find disabled chains to clean up (in previous but not in new)
|
|
1009
|
+
const removedChains = [];
|
|
1010
|
+
for (const chain of previousChains) {
|
|
1011
|
+
if (!__classPrivateFieldGet(this, _AssetsController_enabledChains, "f").has(chain)) {
|
|
1012
|
+
removedChains.push(chain);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
log('Enabled networks changed', {
|
|
1016
|
+
previousCount: previousChains.size,
|
|
1017
|
+
newCount: __classPrivateFieldGet(this, _AssetsController_enabledChains, "f").size,
|
|
1018
|
+
addedChains,
|
|
1019
|
+
removedChains,
|
|
1020
|
+
});
|
|
1021
|
+
// Note: We intentionally do NOT delete balance data for disabled chains.
|
|
1022
|
+
// Users may want to see historical balances even if the network is currently disabled.
|
|
1023
|
+
// The data will simply not be updated until the network is re-enabled.
|
|
1024
|
+
// Refresh subscriptions for new chain set
|
|
1025
|
+
__classPrivateFieldGet(this, _AssetsController_instances, "m", _AssetsController_subscribeToDataSources).call(this);
|
|
1026
|
+
// Do one-time fetch for newly enabled chains
|
|
1027
|
+
if (addedChains.length > 0 && __classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get).length > 0) {
|
|
1028
|
+
await this.getAssets(__classPrivateFieldGet(this, _AssetsController_instances, "a", _AssetsController_selectedAccounts_get), {
|
|
1029
|
+
chainIds: addedChains,
|
|
1030
|
+
forceUpdate: true,
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
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
|
+
};
|
|
1057
|
+
//# sourceMappingURL=AssetsController.cjs.map
|