@metamask-previews/assets-controller 17.0.0-preview-576eebcfe → 17.0.0-preview-378f5fcf7
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 +0 -14
- package/dist/AssetsController.d.ts +1 -2
- package/dist/AssetsController.d.ts.map +1 -1
- package/dist/AssetsController.js +15 -72
- package/dist/AssetsController.js.map +1 -1
- package/dist/data-sources/PriceDataSource.d.ts.map +1 -1
- package/dist/data-sources/PriceDataSource.js +3 -28
- package/dist/data-sources/PriceDataSource.js.map +1 -1
- package/package.json +3 -6
- package/dist/migrations/dropBalancesOutsideAccountScopes.d.ts +0 -19
- package/dist/migrations/dropBalancesOutsideAccountScopes.d.ts.map +0 -1
- package/dist/migrations/dropBalancesOutsideAccountScopes.js +0 -66
- package/dist/migrations/dropBalancesOutsideAccountScopes.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PriceDataSource.d.ts","sourceRoot":"","sources":["../../src/data-sources/PriceDataSource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,iBAAiB,EAElB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAK3D,OAAO,KAAK,EACV,aAAa,EACb,WAAW,EACX,YAAY,EAEZ,UAAU,EACV,qBAAqB,EACtB,MAAM,aAAa,CAAC;AAGrB,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"PriceDataSource.d.ts","sourceRoot":"","sources":["../../src/data-sources/PriceDataSource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,iBAAiB,EAElB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAK3D,OAAO,KAAK,EACV,aAAa,EACb,WAAW,EACX,YAAY,EAEZ,UAAU,EACV,qBAAqB,EACtB,MAAM,aAAa,CAAC;AAGrB,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AA4BnE,kDAAkD;AAClD,MAAM,MAAM,qBAAqB,GAAG;IAClC,8CAA8C;IAC9C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,qBAAqB,GAAG;IAC3D,mDAAmD;IACnD,cAAc,EAAE,iBAAiB,CAAC;IAClC,qEAAqE;IACrE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;IAC7C,2FAA2F;IAC3F,cAAc,EAAE,MAAM,qBAAqB,CAAC;CAC7C,CAAC;AAwBF;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAEhE;AA0BD;;;;;;;;;GASG;AACH,qBAAa,eAAe;;IAC1B,MAAM,CAAC,QAAQ,CAAC,cAAc,qBAAmB;IAEjD,OAAO,IAAI,MAAM,CAEhB;IA+BD,YAAY,OAAO,EAAE,sBAAsB,EAa1C;IAMD;;;;;;;;;;;;;;OAcG;IACH,IAAI,gBAAgB,IAAI,UAAU,CA2EjC;IA6OD;;;;;;OAMG;IACG,KAAK,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAmCvD;IAMD;;;;;OAKG;IACG,SAAS,CAAC,mBAAmB,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6FvE;IAED;;;;OAIG;IACG,WAAW,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMvD;IAED;;;;OAIG;IACH,oBAAoB,IAAI,IAAI,CAE3B;IAED;;OAEG;IACH,OAAO,IAAI,IAAI,CAMd;CACF"}
|
|
@@ -21,31 +21,6 @@ const FRESHNESS_TTL_POLL_RATIO = 0.9;
|
|
|
21
21
|
const PRICE_API_BATCH_SIZE = 50;
|
|
22
22
|
const log = createModuleLogger(projectLogger, CONTROLLER_NAME);
|
|
23
23
|
// ============================================================================
|
|
24
|
-
// HELPERS
|
|
25
|
-
// ============================================================================
|
|
26
|
-
/**
|
|
27
|
-
* Extracts the CAIP-2 chain IDs the Price API v3 spot-prices endpoint
|
|
28
|
-
* supports from a `/v2/supportedNetworks` response.
|
|
29
|
-
*
|
|
30
|
-
* The endpoint returns `partialSupport` as an object keyed by spot-price
|
|
31
|
-
* endpoint (`{ spotPricesV2, spotPricesV3 }`). Since this data source only
|
|
32
|
-
* calls `/v3/spot-prices`, only `spotPricesV3` is relevant. A legacy
|
|
33
|
-
* array-shaped `partialSupport` is still accepted so a transient schema
|
|
34
|
-
* change does not disable price fetching.
|
|
35
|
-
*
|
|
36
|
-
* @param response - The supported networks response.
|
|
37
|
-
* @param response.fullSupport - Chains supported by every endpoint.
|
|
38
|
-
* @param response.partialSupport - Chains supported by specific endpoints.
|
|
39
|
-
* @returns Unique CAIP-2 chain IDs supported by v3 spot prices.
|
|
40
|
-
*/
|
|
41
|
-
function getV3SpotPriceSupportedChainIds(response) {
|
|
42
|
-
const { fullSupport, partialSupport } = response;
|
|
43
|
-
const partial = Array.isArray(partialSupport)
|
|
44
|
-
? partialSupport
|
|
45
|
-
: (partialSupport?.spotPricesV3 ?? []);
|
|
46
|
-
return new Set([...(fullSupport ?? []), ...partial]);
|
|
47
|
-
}
|
|
48
|
-
// ============================================================================
|
|
49
24
|
// HELPER FUNCTIONS
|
|
50
25
|
// ============================================================================
|
|
51
26
|
/**
|
|
@@ -329,15 +304,15 @@ export class PriceDataSource {
|
|
|
329
304
|
}
|
|
330
305
|
}
|
|
331
306
|
/**
|
|
332
|
-
* Price API
|
|
333
|
-
* (`fullSupport` plus `partialSupport.spotPricesV3`).
|
|
307
|
+
* Price API supported networks.
|
|
334
308
|
*
|
|
335
309
|
* @returns CAIP-2 chain IDs; empty on error.
|
|
336
310
|
*/
|
|
337
311
|
async #getSupportedNetworks() {
|
|
338
312
|
try {
|
|
339
313
|
const response = await fetchWithTimeout(() => this.#apiClient.prices.fetchPriceV2SupportedNetworks(), this.#fetchTimeoutMs);
|
|
340
|
-
|
|
314
|
+
const allNetworks = [...response.fullSupport, ...response.partialSupport];
|
|
315
|
+
return new Set(allNetworks);
|
|
341
316
|
}
|
|
342
317
|
catch (error) {
|
|
343
318
|
log('Failed to fetch price supported networks', { error });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PriceDataSource.js","sourceRoot":"","sources":["../../src/data-sources/PriceDataSource.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n PriceV2SupportedNetworksResponse,\n SupportedCurrency,\n V3SpotPricesResponse,\n} from '@metamask/core-backend';\nimport { ApiPlatformClient } from '@metamask/core-backend';\nimport { parseCaipAssetType } from '@metamask/utils';\n\nimport { projectLogger, createModuleLogger } from '../logger.js';\nimport { forDataTypes } from '../types.js';\nimport type {\n Caip19AssetId,\n DataRequest,\n DataResponse,\n FungibleAssetPrice,\n Middleware,\n AssetsControllerState,\n} from '../types.js';\nimport { DedupingBatchFetcher } from '../utils/dedupingBatchFetcher.js';\nimport { fetchWithTimeout, safeNormalizeAssetId } from '../utils/index.js';\nimport type { SubscriptionRequest } from './AbstractDataSource.js';\nimport { reduceInBatchesSerially } from './evm-rpc-services/index.js';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst CONTROLLER_NAME = 'PriceDataSource';\nconst DEFAULT_POLL_INTERVAL = 60_000; // 1 minute for price updates\nconst DEFAULT_FETCH_TIMEOUT_MS = 15_000;\n\n/**\n * Fraction of the poll interval used to cap the freshness TTL. Kept strictly\n * below 1 so an asset fetched on one poll is reliably stale by the next poll;\n * the margin absorbs network latency and timer jitter (see the cap in\n * `subscribe`).\n */\nconst FRESHNESS_TTL_POLL_RATIO = 0.9;\n\n/** Maximum number of asset IDs per Price API request. */\nconst PRICE_API_BATCH_SIZE = 50;\n\nconst log = createModuleLogger(projectLogger, CONTROLLER_NAME);\n\n// ============================================================================\n// HELPERS\n// ============================================================================\n\n/**\n * Extracts the CAIP-2 chain IDs the Price API v3 spot-prices endpoint\n * supports from a `/v2/supportedNetworks` response.\n *\n * The endpoint returns `partialSupport` as an object keyed by spot-price\n * endpoint (`{ spotPricesV2, spotPricesV3 }`). Since this data source only\n * calls `/v3/spot-prices`, only `spotPricesV3` is relevant. A legacy\n * array-shaped `partialSupport` is still accepted so a transient schema\n * change does not disable price fetching.\n *\n * @param response - The supported networks response.\n * @param response.fullSupport - Chains supported by every endpoint.\n * @param response.partialSupport - Chains supported by specific endpoints.\n * @returns Unique CAIP-2 chain IDs supported by v3 spot prices.\n */\nfunction getV3SpotPriceSupportedChainIds(response: {\n fullSupport?: string[];\n partialSupport?:\n | string[]\n | Partial<PriceV2SupportedNetworksResponse['partialSupport']>;\n}): Set<string> {\n const { fullSupport, partialSupport } = response;\n const partial = Array.isArray(partialSupport)\n ? partialSupport\n : (partialSupport?.spotPricesV3 ?? []);\n return new Set([...(fullSupport ?? []), ...partial]);\n}\n\n// ============================================================================\n// OPTIONS\n// ============================================================================\n\n/** Optional configuration for PriceDataSource. */\nexport type PriceDataSourceConfig = {\n /** Polling interval in ms (default: 60000) */\n pollInterval?: number;\n /**\n * Timeout in ms for a single Price API call (default: 15000). When it fires,\n * the batch rejects so the caller can proceed without prices.\n */\n fetchTimeoutMs?: number;\n /**\n * Minimum age (ms) before a price is considered stale and re-fetched.\n * Assets fetched more recently than this are skipped to avoid redundant\n * API calls from overlapping middleware / subscription / manual triggers.\n * Defaults to pollInterval (60 000 ms).\n */\n priceFreshnessTtlMs?: number;\n};\n\nexport type PriceDataSourceOptions = PriceDataSourceConfig & {\n /** ApiPlatformClient for API calls with caching */\n queryApiClient: ApiPlatformClient;\n /** Function returning the currently-active ISO 4217 currency code */\n getSelectedCurrency: () => SupportedCurrency;\n /** Current AssetsController state. Used for balance-based pricing and freshness checks. */\n getAssetsState: () => AssetsControllerState;\n};\n\n// ============================================================================\n// HELPER FUNCTIONS\n// ============================================================================\n\n/**\n * Asset reference patterns that should NOT be sent to the Price API.\n * These are internal resource tracking values without market prices.\n */\nconst NON_PRICEABLE_ASSET_PATTERNS = [\n // Synthetic slip44 staking-position assets: the Price API only knows about\n // pure numeric coin-type references (e.g. slip44:195). Any suffix after the\n // number (e.g. slip44:195-ready-for-withdrawal, slip44:195-in-lock-period,\n // slip44:195-staking-rewards, slip44:195-staked-for-…) is a MetaMask-internal\n // synthetic asset that has no market price.\n /\\/slip44:\\d+-/u,\n // Tron non-price resource assets (bandwidth, energy)\n /\\/slip44:bandwidth$/u,\n /\\/slip44:energy$/u,\n /\\/slip44:maximum-bandwidth$/u,\n /\\/slip44:maximum-energy$/u,\n];\n\n/**\n * Check if an asset ID represents a priceable asset.\n * Filters out internal resource tracking values that don't have market prices.\n *\n * @param assetId - The CAIP-19 asset ID to check.\n * @returns True if the asset has market price data.\n */\nexport function isPriceableAsset(assetId: Caip19AssetId): boolean {\n return !NON_PRICEABLE_ASSET_PATTERNS.some((pattern) => pattern.test(assetId));\n}\n\n/** Market data item from spot prices response (same as FungibleAssetPrice without lastUpdated) */\ntype SpotPriceMarketData = Omit<\n FungibleAssetPrice,\n 'lastUpdated' | 'assetPriceType'\n>;\n\n/**\n * Type guard to check if market data has a valid price\n *\n * @param data - The data to check.\n * @returns True if data is valid SpotPriceMarketData.\n */\nfunction isValidMarketData(data: unknown): data is SpotPriceMarketData {\n return (\n typeof data === 'object' &&\n data !== null &&\n typeof (data as Record<string, unknown>).price === 'number'\n );\n}\n\n// ============================================================================\n// PRICE DATA SOURCE\n// ============================================================================\n\n/**\n * PriceDataSource fetches asset prices from the Price API.\n *\n * This data source:\n * - Fetches prices from Price API v3 spot-prices endpoint\n * - Supports one-time fetch and subscription-based polling\n * - In subscribe mode, uses constructor `getAssetsState` to read assetsBalance and fetch prices\n *\n * Usage: Create with queryApiClient and getAssetsState; subscribe() polls prices for held assets.\n */\nexport class PriceDataSource {\n static readonly controllerName = CONTROLLER_NAME;\n\n getName(): string {\n return PriceDataSource.controllerName;\n }\n\n readonly #getSelectedCurrency: () => SupportedCurrency;\n\n readonly #pollInterval: number;\n\n /** ApiPlatformClient for cached API calls */\n readonly #apiClient: ApiPlatformClient;\n\n readonly #fetchTimeoutMs: number;\n\n /**\n * Deduplicates price fetches by asset ID: skips assets fetched within the\n * freshness TTL and joins concurrent in-flight fetches for the same asset so\n * overlapping triggers (middleware + subscription poll) don't issue duplicate\n * API requests.\n */\n readonly #deduper: DedupingBatchFetcher<Caip19AssetId, FungibleAssetPrice>;\n\n readonly #getAssetsState: () => AssetsControllerState;\n\n /** Active subscriptions by ID */\n readonly #activeSubscriptions: Map<\n string,\n {\n cleanup: () => void;\n request: DataRequest;\n onAssetsUpdate: (response: DataResponse) => void | Promise<void>;\n }\n > = new Map();\n\n constructor(options: PriceDataSourceOptions) {\n this.#getSelectedCurrency = options.getSelectedCurrency;\n this.#pollInterval = options.pollInterval ?? DEFAULT_POLL_INTERVAL;\n this.#apiClient = options.queryApiClient;\n this.#fetchTimeoutMs = options.fetchTimeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;\n this.#getAssetsState = options.getAssetsState;\n this.#deduper = new DedupingBatchFetcher({\n fetchBatch: (\n assetIds,\n ): Promise<Record<Caip19AssetId, FungibleAssetPrice>> =>\n this.#executeBatchFetch(assetIds),\n freshnessTtlMs: options.priceFreshnessTtlMs ?? this.#pollInterval,\n });\n }\n\n // ============================================================================\n // MIDDLEWARE\n // ============================================================================\n\n /**\n * Get the middleware for enriching responses with price data.\n *\n * This middleware:\n * 1. Extracts the response from context\n * 2. Fetches prices for detected assets (assets without metadata)\n * 3. Enriches the response with fetched prices\n * 4. Calls next() at the end to continue the middleware chain\n *\n * Note: This middleware ONLY fetches prices for detected assets.\n * For fetching prices for all assets, use the subscription mechanism\n * which polls prices for all assets in the balance state.\n *\n * @returns The middleware function for the assets pipeline.\n */\n get assetsMiddleware(): Middleware {\n return forDataTypes(['price'], async (ctx, next) => {\n // Extract response from context\n const { response, request } = ctx;\n\n const statePrices = (this.#getAssetsState()?.assetsPrice ?? {}) as Record<\n string,\n FungibleAssetPrice\n >;\n\n const assetIds = new Set<Caip19AssetId>();\n\n for (const assetId of request.assetsForPriceUpdate ?? []) {\n assetIds.add(assetId);\n }\n\n // Detected assets only need a price fetch when state has none yet.\n // Explicit assetsForPriceUpdate (e.g. currency change) are always fetched.\n for (const detectedAccountAssets of Object.values(\n response.detectedAssets ?? {},\n )) {\n for (const assetId of detectedAccountAssets) {\n const normalizedAssetId = safeNormalizeAssetId(assetId);\n const alreadyQueued = request.assetsForPriceUpdate?.some(\n (queuedId) =>\n queuedId === assetId || queuedId === normalizedAssetId,\n );\n if (\n statePrices[assetId] === undefined &&\n statePrices[normalizedAssetId] === undefined &&\n !alreadyQueued\n ) {\n assetIds.add(normalizedAssetId);\n }\n }\n }\n\n if (assetIds.size === 0) {\n return next(ctx);\n }\n\n // Filter to only priceable assets\n const priceableAssetIds = [...assetIds].filter(isPriceableAsset);\n\n if (priceableAssetIds.length === 0) {\n return next(ctx);\n }\n\n const supportedNetworks = await this.#getSupportedNetworks();\n const supportedAssetIds = this.#filterAssetsByNetwork(\n priceableAssetIds,\n supportedNetworks,\n );\n\n if (supportedAssetIds.length === 0) {\n return next(ctx);\n }\n\n if (request.forceUpdate) {\n this.#deduper.invalidateKeys(supportedAssetIds);\n }\n\n try {\n const spotPrices = await this.#fetchSpotPrices(supportedAssetIds);\n response.assetsPrice = {\n ...(response.assetsPrice ?? {}),\n ...spotPrices,\n };\n } catch (error) {\n log('Failed to fetch prices via middleware', { error });\n }\n\n // Call next() at the end to continue the middleware chain\n return next(ctx);\n });\n }\n\n // ============================================================================\n // HELPERS\n // ============================================================================\n\n /**\n * Fetch spot prices for a single batch of asset IDs (must be ≤ PRICE_API_BATCH_SIZE).\n *\n * @param assetIds - Array of CAIP-19 asset IDs (already within batch size limit).\n * @param selectedCurrency - The user's selected display currency.\n * @returns Raw spot-prices responses for the selected currency and USD.\n */\n async #fetchSpotPricesBatch(\n assetIds: string[],\n selectedCurrency: SupportedCurrency,\n ): Promise<{\n selectedCurrencyPrices: V3SpotPricesResponse;\n usdPrices: V3SpotPricesResponse;\n }> {\n if (selectedCurrency === 'usd') {\n const selectedCurrencyPrices = await fetchWithTimeout(\n () =>\n this.#apiClient.prices.fetchV3SpotPrices(assetIds, {\n currency: selectedCurrency,\n includeMarketData: true,\n }),\n this.#fetchTimeoutMs,\n );\n return { selectedCurrencyPrices, usdPrices: selectedCurrencyPrices };\n }\n\n const [selectedCurrencyPrices, usdPrices] = await Promise.all([\n fetchWithTimeout(\n () =>\n this.#apiClient.prices.fetchV3SpotPrices(assetIds, {\n currency: selectedCurrency,\n includeMarketData: true,\n }),\n this.#fetchTimeoutMs,\n ),\n fetchWithTimeout(\n () =>\n this.#apiClient.prices.fetchV3SpotPrices(assetIds, {\n currency: 'usd',\n includeMarketData: true,\n }),\n this.#fetchTimeoutMs,\n ),\n ]);\n\n return { selectedCurrencyPrices, usdPrices };\n }\n\n /**\n * Execute the actual batched API call for a set of asset IDs and return\n * parsed price results. Used as the `fetchBatch` callback for the deduper,\n * so it does NOT check freshness or inflight state — that is handled by\n * {@link DedupingBatchFetcher}.\n *\n * @param assetIds - Asset IDs to fetch (already filtered/deduplicated).\n * @returns Parsed prices keyed by CAIP-19 asset ID.\n */\n async #executeBatchFetch(\n assetIds: Caip19AssetId[],\n ): Promise<Record<Caip19AssetId, FungibleAssetPrice>> {\n const selectedCurrency = this.#getSelectedCurrency();\n\n type BatchResult = {\n selectedCurrencyPrices: V3SpotPricesResponse;\n usdPrices: V3SpotPricesResponse;\n };\n\n const batchResults = await reduceInBatchesSerially<string, BatchResult[]>({\n values: assetIds,\n batchSize: PRICE_API_BATCH_SIZE,\n eachBatch: async (workingResult, batch) => {\n const result = await this.#fetchSpotPricesBatch(\n batch,\n selectedCurrency,\n );\n return [...(workingResult as BatchResult[]), result];\n },\n initialResult: [],\n });\n\n const fetchedAt = Date.now();\n const prices: Record<Caip19AssetId, FungibleAssetPrice> = {};\n\n for (const { selectedCurrencyPrices, usdPrices } of batchResults) {\n for (const [assetId, marketData] of Object.entries(\n selectedCurrencyPrices,\n )) {\n const usdMarketData = usdPrices[assetId];\n\n if (\n !isValidMarketData(marketData) ||\n !isValidMarketData(usdMarketData)\n ) {\n continue;\n }\n\n prices[assetId as Caip19AssetId] = {\n ...marketData,\n assetPriceType: 'fungible',\n usdPrice: usdMarketData.price,\n lastUpdated: fetchedAt,\n };\n }\n }\n\n return prices;\n }\n\n /**\n * Fetch spot prices for all provided asset IDs, deduplicating via the\n * deduper (freshness TTL + per-asset inflight coalescing).\n *\n * @param assetIds - Array of CAIP-19 asset IDs.\n * @returns Spot prices response (only contains entries for assets that were\n * actually fetched or joined from inflight).\n */\n async #fetchSpotPrices(\n assetIds: Caip19AssetId[],\n ): Promise<Record<Caip19AssetId, FungibleAssetPrice>> {\n return this.#deduper.fetch(assetIds);\n }\n\n /**\n * Get unique asset IDs from the assetsBalance state.\n * Filters by accounts and chains from the request.\n *\n * @param request - Data request with accounts and chainIds filters.\n * @returns Array of CAIP-19 asset IDs from balance state.\n */\n #getAssetIdsFromBalanceState(request: DataRequest): Caip19AssetId[] {\n try {\n const state = this.#getAssetsState();\n const assetIds = new Set<Caip19AssetId>();\n\n const accountIds = request.accountsWithSupportedChains.map(\n (a) => a.account.id,\n );\n const accountFilter =\n accountIds.length > 0 ? new Set(accountIds) : undefined;\n const chainFilter =\n request.chainIds.length > 0 ? new Set(request.chainIds) : undefined;\n\n if (state?.assetsBalance) {\n for (const [accountId, accountBalances] of Object.entries(\n state.assetsBalance,\n )) {\n // Filter by account if specified\n if (accountFilter && !accountFilter.has(accountId)) {\n continue;\n }\n\n for (const assetId of Object.keys(accountBalances)) {\n // Filter by chain if specified; skip malformed asset IDs for this entry only\n if (chainFilter) {\n try {\n const { chainId } = parseCaipAssetType(\n assetId as Caip19AssetId,\n );\n if (!chainFilter.has(chainId)) {\n continue;\n }\n } catch (error) {\n log('Skipping malformed asset ID in balance state', {\n assetId,\n error,\n });\n continue;\n }\n }\n assetIds.add(assetId as Caip19AssetId);\n }\n }\n }\n\n return [...assetIds];\n } catch (error) {\n log('Failed to get asset IDs from balance state', { error });\n return [];\n }\n }\n\n /**\n * Price API networks supported by the v3 spot-prices endpoint\n * (`fullSupport` plus `partialSupport.spotPricesV3`).\n *\n * @returns CAIP-2 chain IDs; empty on error.\n */\n async #getSupportedNetworks(): Promise<Set<string>> {\n try {\n const response = await fetchWithTimeout(\n (): Promise<PriceV2SupportedNetworksResponse> =>\n this.#apiClient.prices.fetchPriceV2SupportedNetworks(),\n this.#fetchTimeoutMs,\n );\n\n return getV3SpotPriceSupportedChainIds(response);\n } catch (error) {\n log('Failed to fetch price supported networks', { error });\n return new Set();\n }\n }\n\n /**\n * Keeps asset IDs whose chain is in `supportedNetworks`.\n *\n * @param assetIds - CAIP-19 asset IDs.\n * @param supportedNetworks - Supported CAIP-2 chain IDs.\n * @returns Matching IDs, or all IDs if the set is empty.\n */\n #filterAssetsByNetwork(\n assetIds: Caip19AssetId[],\n supportedNetworks: Set<string>,\n ): Caip19AssetId[] {\n if (supportedNetworks.size === 0) {\n return assetIds;\n }\n return assetIds.filter((assetId) => {\n try {\n const parsed = parseCaipAssetType(assetId);\n const chainId = `${parsed.chain.namespace}:${parsed.chain.reference}`;\n return supportedNetworks.has(chainId);\n } catch {\n return false;\n }\n });\n }\n\n // ============================================================================\n // FETCH\n // ============================================================================\n\n /**\n * Fetch prices for assets held by the accounts and chains in the request.\n * Reads asset IDs from balance state via the constructor `getAssetsState`.\n *\n * @param request - The data request specifying accounts and chains.\n * @returns DataResponse containing asset prices.\n */\n async fetch(request: DataRequest): Promise<DataResponse> {\n const response: DataResponse = {};\n\n // Get asset IDs from balance state\n const rawAssetIds = this.#getAssetIdsFromBalanceState(request);\n\n // Filter out non-priceable assets (e.g., Tron bandwidth/energy resources)\n const priceableAssetIds = rawAssetIds.filter(isPriceableAsset);\n\n if (priceableAssetIds.length === 0) {\n return response;\n }\n\n const supportedNetworks = await this.#getSupportedNetworks();\n const assetIds = this.#filterAssetsByNetwork(\n priceableAssetIds,\n supportedNetworks,\n );\n\n if (assetIds.length === 0) {\n return response;\n }\n\n try {\n const spotPrices = await this.#fetchSpotPrices([...assetIds]);\n\n response.assetsPrice = {\n ...(response.assetsPrice ?? {}),\n ...spotPrices,\n };\n } catch (error) {\n log('Failed to fetch prices', { error });\n }\n\n return response;\n }\n\n // ============================================================================\n // SUBSCRIBE\n // ============================================================================\n\n /**\n * Subscribe to price updates.\n * Sets up polling that fetches prices for all assets in assetsBalance state.\n *\n * @param subscriptionRequest - The subscription request configuration.\n */\n async subscribe(subscriptionRequest: SubscriptionRequest): Promise<void> {\n const { request, subscriptionId, isUpdate } = subscriptionRequest;\n\n // Handle subscription update: refresh request and fetch immediately so\n // newly held / still-unpriced assets (e.g. seeded natives) do not wait\n // for the next poll tick. Deduper freshness TTL skips recently priced IDs.\n if (isUpdate) {\n const existing = this.#activeSubscriptions.get(subscriptionId);\n if (existing) {\n existing.request = request;\n existing.onAssetsUpdate = subscriptionRequest.onAssetsUpdate;\n\n try {\n const fetchResponse = await this.fetch(request);\n if (\n fetchResponse.assetsPrice &&\n Object.keys(fetchResponse.assetsPrice).length > 0\n ) {\n await existing.onAssetsUpdate({\n ...fetchResponse,\n updateMode: 'merge',\n });\n }\n } catch (error) {\n log('Subscription update fetch failed', { subscriptionId, error });\n }\n return;\n }\n }\n\n // Clean up existing subscription\n await this.unsubscribe(subscriptionId);\n\n const pollInterval = request.updateInterval ?? this.#pollInterval;\n\n // Cap the freshness TTL strictly below the effective poll interval.\n // `fetchedAt` is stamped when a fetch completes (slightly after the tick\n // that triggered it), so a TTL equal to the poll interval would leave the\n // asset still \"fresh\" at the next tick, making the subscription re-fetch\n // only every other poll. The margin also absorbs network latency / jitter.\n this.#deduper.freshnessTtlMs = Math.min(\n this.#deduper.freshnessTtlMs,\n Math.floor(pollInterval * FRESHNESS_TTL_POLL_RATIO),\n );\n\n // Create poll function - fetches prices using constructor getAssetsState.\n // The freshness TTL naturally gates re-fetches: assets fetched less than\n // `priceFreshnessTtlMs` ago are skipped, preventing duplicates when middleware\n // or other triggers already fetched the same assets between polls.\n // Concurrent middleware calls will join the inflight promise rather than\n // issuing duplicate requests.\n const pollFn = async (): Promise<void> => {\n try {\n const subscription = this.#activeSubscriptions.get(subscriptionId);\n if (!subscription) {\n return;\n }\n\n const fetchResponse = await this.fetch(subscription.request);\n\n // Only report if we got prices\n if (\n fetchResponse.assetsPrice &&\n Object.keys(fetchResponse.assetsPrice).length > 0\n ) {\n await subscription.onAssetsUpdate({\n ...fetchResponse,\n // merge overwrites existing spot prices on each poll; update would\n // seed-only and leave the first price forever.\n updateMode: 'merge',\n });\n }\n } catch (error) {\n log('Subscription poll failed', { subscriptionId, error });\n }\n };\n\n // Set up polling\n const timer = setInterval(() => {\n pollFn().catch(console.error);\n }, pollInterval);\n\n // Store subscription\n this.#activeSubscriptions.set(subscriptionId, {\n cleanup: () => {\n clearInterval(timer);\n },\n request,\n onAssetsUpdate: subscriptionRequest.onAssetsUpdate,\n });\n\n // Initial fetch\n await pollFn();\n }\n\n /**\n * Unsubscribe from price updates.\n *\n * @param subscriptionId - The ID of the subscription to cancel.\n */\n async unsubscribe(subscriptionId: string): Promise<void> {\n const subscription = this.#activeSubscriptions.get(subscriptionId);\n if (subscription) {\n subscription.cleanup();\n this.#activeSubscriptions.delete(subscriptionId);\n }\n }\n\n /**\n * Invalidate the price freshness cache, forcing the next fetch to call the\n * API regardless of TTL. Use when external state changes (e.g. selected\n * currency) require a full refresh.\n */\n invalidatePriceCache(): void {\n this.#deduper.invalidate();\n }\n\n /**\n * Destroy the data source and clean up all subscriptions.\n */\n destroy(): void {\n for (const subscription of this.#activeSubscriptions.values()) {\n subscription.cleanup();\n }\n this.#activeSubscriptions.clear();\n this.#deduper.destroy();\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"PriceDataSource.js","sourceRoot":"","sources":["../../src/data-sources/PriceDataSource.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n PriceSupportedNetworksResponse,\n SupportedCurrency,\n V3SpotPricesResponse,\n} from '@metamask/core-backend';\nimport { ApiPlatformClient } from '@metamask/core-backend';\nimport { parseCaipAssetType } from '@metamask/utils';\n\nimport { projectLogger, createModuleLogger } from '../logger.js';\nimport { forDataTypes } from '../types.js';\nimport type {\n Caip19AssetId,\n DataRequest,\n DataResponse,\n FungibleAssetPrice,\n Middleware,\n AssetsControllerState,\n} from '../types.js';\nimport { DedupingBatchFetcher } from '../utils/dedupingBatchFetcher.js';\nimport { fetchWithTimeout, safeNormalizeAssetId } from '../utils/index.js';\nimport type { SubscriptionRequest } from './AbstractDataSource.js';\nimport { reduceInBatchesSerially } from './evm-rpc-services/index.js';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst CONTROLLER_NAME = 'PriceDataSource';\nconst DEFAULT_POLL_INTERVAL = 60_000; // 1 minute for price updates\nconst DEFAULT_FETCH_TIMEOUT_MS = 15_000;\n\n/**\n * Fraction of the poll interval used to cap the freshness TTL. Kept strictly\n * below 1 so an asset fetched on one poll is reliably stale by the next poll;\n * the margin absorbs network latency and timer jitter (see the cap in\n * `subscribe`).\n */\nconst FRESHNESS_TTL_POLL_RATIO = 0.9;\n\n/** Maximum number of asset IDs per Price API request. */\nconst PRICE_API_BATCH_SIZE = 50;\n\nconst log = createModuleLogger(projectLogger, CONTROLLER_NAME);\n\n// ============================================================================\n// OPTIONS\n// ============================================================================\n\n/** Optional configuration for PriceDataSource. */\nexport type PriceDataSourceConfig = {\n /** Polling interval in ms (default: 60000) */\n pollInterval?: number;\n /**\n * Timeout in ms for a single Price API call (default: 15000). When it fires,\n * the batch rejects so the caller can proceed without prices.\n */\n fetchTimeoutMs?: number;\n /**\n * Minimum age (ms) before a price is considered stale and re-fetched.\n * Assets fetched more recently than this are skipped to avoid redundant\n * API calls from overlapping middleware / subscription / manual triggers.\n * Defaults to pollInterval (60 000 ms).\n */\n priceFreshnessTtlMs?: number;\n};\n\nexport type PriceDataSourceOptions = PriceDataSourceConfig & {\n /** ApiPlatformClient for API calls with caching */\n queryApiClient: ApiPlatformClient;\n /** Function returning the currently-active ISO 4217 currency code */\n getSelectedCurrency: () => SupportedCurrency;\n /** Current AssetsController state. Used for balance-based pricing and freshness checks. */\n getAssetsState: () => AssetsControllerState;\n};\n\n// ============================================================================\n// HELPER FUNCTIONS\n// ============================================================================\n\n/**\n * Asset reference patterns that should NOT be sent to the Price API.\n * These are internal resource tracking values without market prices.\n */\nconst NON_PRICEABLE_ASSET_PATTERNS = [\n // Synthetic slip44 staking-position assets: the Price API only knows about\n // pure numeric coin-type references (e.g. slip44:195). Any suffix after the\n // number (e.g. slip44:195-ready-for-withdrawal, slip44:195-in-lock-period,\n // slip44:195-staking-rewards, slip44:195-staked-for-…) is a MetaMask-internal\n // synthetic asset that has no market price.\n /\\/slip44:\\d+-/u,\n // Tron non-price resource assets (bandwidth, energy)\n /\\/slip44:bandwidth$/u,\n /\\/slip44:energy$/u,\n /\\/slip44:maximum-bandwidth$/u,\n /\\/slip44:maximum-energy$/u,\n];\n\n/**\n * Check if an asset ID represents a priceable asset.\n * Filters out internal resource tracking values that don't have market prices.\n *\n * @param assetId - The CAIP-19 asset ID to check.\n * @returns True if the asset has market price data.\n */\nexport function isPriceableAsset(assetId: Caip19AssetId): boolean {\n return !NON_PRICEABLE_ASSET_PATTERNS.some((pattern) => pattern.test(assetId));\n}\n\n/** Market data item from spot prices response (same as FungibleAssetPrice without lastUpdated) */\ntype SpotPriceMarketData = Omit<\n FungibleAssetPrice,\n 'lastUpdated' | 'assetPriceType'\n>;\n\n/**\n * Type guard to check if market data has a valid price\n *\n * @param data - The data to check.\n * @returns True if data is valid SpotPriceMarketData.\n */\nfunction isValidMarketData(data: unknown): data is SpotPriceMarketData {\n return (\n typeof data === 'object' &&\n data !== null &&\n typeof (data as Record<string, unknown>).price === 'number'\n );\n}\n\n// ============================================================================\n// PRICE DATA SOURCE\n// ============================================================================\n\n/**\n * PriceDataSource fetches asset prices from the Price API.\n *\n * This data source:\n * - Fetches prices from Price API v3 spot-prices endpoint\n * - Supports one-time fetch and subscription-based polling\n * - In subscribe mode, uses constructor `getAssetsState` to read assetsBalance and fetch prices\n *\n * Usage: Create with queryApiClient and getAssetsState; subscribe() polls prices for held assets.\n */\nexport class PriceDataSource {\n static readonly controllerName = CONTROLLER_NAME;\n\n getName(): string {\n return PriceDataSource.controllerName;\n }\n\n readonly #getSelectedCurrency: () => SupportedCurrency;\n\n readonly #pollInterval: number;\n\n /** ApiPlatformClient for cached API calls */\n readonly #apiClient: ApiPlatformClient;\n\n readonly #fetchTimeoutMs: number;\n\n /**\n * Deduplicates price fetches by asset ID: skips assets fetched within the\n * freshness TTL and joins concurrent in-flight fetches for the same asset so\n * overlapping triggers (middleware + subscription poll) don't issue duplicate\n * API requests.\n */\n readonly #deduper: DedupingBatchFetcher<Caip19AssetId, FungibleAssetPrice>;\n\n readonly #getAssetsState: () => AssetsControllerState;\n\n /** Active subscriptions by ID */\n readonly #activeSubscriptions: Map<\n string,\n {\n cleanup: () => void;\n request: DataRequest;\n onAssetsUpdate: (response: DataResponse) => void | Promise<void>;\n }\n > = new Map();\n\n constructor(options: PriceDataSourceOptions) {\n this.#getSelectedCurrency = options.getSelectedCurrency;\n this.#pollInterval = options.pollInterval ?? DEFAULT_POLL_INTERVAL;\n this.#apiClient = options.queryApiClient;\n this.#fetchTimeoutMs = options.fetchTimeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;\n this.#getAssetsState = options.getAssetsState;\n this.#deduper = new DedupingBatchFetcher({\n fetchBatch: (\n assetIds,\n ): Promise<Record<Caip19AssetId, FungibleAssetPrice>> =>\n this.#executeBatchFetch(assetIds),\n freshnessTtlMs: options.priceFreshnessTtlMs ?? this.#pollInterval,\n });\n }\n\n // ============================================================================\n // MIDDLEWARE\n // ============================================================================\n\n /**\n * Get the middleware for enriching responses with price data.\n *\n * This middleware:\n * 1. Extracts the response from context\n * 2. Fetches prices for detected assets (assets without metadata)\n * 3. Enriches the response with fetched prices\n * 4. Calls next() at the end to continue the middleware chain\n *\n * Note: This middleware ONLY fetches prices for detected assets.\n * For fetching prices for all assets, use the subscription mechanism\n * which polls prices for all assets in the balance state.\n *\n * @returns The middleware function for the assets pipeline.\n */\n get assetsMiddleware(): Middleware {\n return forDataTypes(['price'], async (ctx, next) => {\n // Extract response from context\n const { response, request } = ctx;\n\n const statePrices = (this.#getAssetsState()?.assetsPrice ?? {}) as Record<\n string,\n FungibleAssetPrice\n >;\n\n const assetIds = new Set<Caip19AssetId>();\n\n for (const assetId of request.assetsForPriceUpdate ?? []) {\n assetIds.add(assetId);\n }\n\n // Detected assets only need a price fetch when state has none yet.\n // Explicit assetsForPriceUpdate (e.g. currency change) are always fetched.\n for (const detectedAccountAssets of Object.values(\n response.detectedAssets ?? {},\n )) {\n for (const assetId of detectedAccountAssets) {\n const normalizedAssetId = safeNormalizeAssetId(assetId);\n const alreadyQueued = request.assetsForPriceUpdate?.some(\n (queuedId) =>\n queuedId === assetId || queuedId === normalizedAssetId,\n );\n if (\n statePrices[assetId] === undefined &&\n statePrices[normalizedAssetId] === undefined &&\n !alreadyQueued\n ) {\n assetIds.add(normalizedAssetId);\n }\n }\n }\n\n if (assetIds.size === 0) {\n return next(ctx);\n }\n\n // Filter to only priceable assets\n const priceableAssetIds = [...assetIds].filter(isPriceableAsset);\n\n if (priceableAssetIds.length === 0) {\n return next(ctx);\n }\n\n const supportedNetworks = await this.#getSupportedNetworks();\n const supportedAssetIds = this.#filterAssetsByNetwork(\n priceableAssetIds,\n supportedNetworks,\n );\n\n if (supportedAssetIds.length === 0) {\n return next(ctx);\n }\n\n if (request.forceUpdate) {\n this.#deduper.invalidateKeys(supportedAssetIds);\n }\n\n try {\n const spotPrices = await this.#fetchSpotPrices(supportedAssetIds);\n response.assetsPrice = {\n ...(response.assetsPrice ?? {}),\n ...spotPrices,\n };\n } catch (error) {\n log('Failed to fetch prices via middleware', { error });\n }\n\n // Call next() at the end to continue the middleware chain\n return next(ctx);\n });\n }\n\n // ============================================================================\n // HELPERS\n // ============================================================================\n\n /**\n * Fetch spot prices for a single batch of asset IDs (must be ≤ PRICE_API_BATCH_SIZE).\n *\n * @param assetIds - Array of CAIP-19 asset IDs (already within batch size limit).\n * @param selectedCurrency - The user's selected display currency.\n * @returns Raw spot-prices responses for the selected currency and USD.\n */\n async #fetchSpotPricesBatch(\n assetIds: string[],\n selectedCurrency: SupportedCurrency,\n ): Promise<{\n selectedCurrencyPrices: V3SpotPricesResponse;\n usdPrices: V3SpotPricesResponse;\n }> {\n if (selectedCurrency === 'usd') {\n const selectedCurrencyPrices = await fetchWithTimeout(\n () =>\n this.#apiClient.prices.fetchV3SpotPrices(assetIds, {\n currency: selectedCurrency,\n includeMarketData: true,\n }),\n this.#fetchTimeoutMs,\n );\n return { selectedCurrencyPrices, usdPrices: selectedCurrencyPrices };\n }\n\n const [selectedCurrencyPrices, usdPrices] = await Promise.all([\n fetchWithTimeout(\n () =>\n this.#apiClient.prices.fetchV3SpotPrices(assetIds, {\n currency: selectedCurrency,\n includeMarketData: true,\n }),\n this.#fetchTimeoutMs,\n ),\n fetchWithTimeout(\n () =>\n this.#apiClient.prices.fetchV3SpotPrices(assetIds, {\n currency: 'usd',\n includeMarketData: true,\n }),\n this.#fetchTimeoutMs,\n ),\n ]);\n\n return { selectedCurrencyPrices, usdPrices };\n }\n\n /**\n * Execute the actual batched API call for a set of asset IDs and return\n * parsed price results. Used as the `fetchBatch` callback for the deduper,\n * so it does NOT check freshness or inflight state — that is handled by\n * {@link DedupingBatchFetcher}.\n *\n * @param assetIds - Asset IDs to fetch (already filtered/deduplicated).\n * @returns Parsed prices keyed by CAIP-19 asset ID.\n */\n async #executeBatchFetch(\n assetIds: Caip19AssetId[],\n ): Promise<Record<Caip19AssetId, FungibleAssetPrice>> {\n const selectedCurrency = this.#getSelectedCurrency();\n\n type BatchResult = {\n selectedCurrencyPrices: V3SpotPricesResponse;\n usdPrices: V3SpotPricesResponse;\n };\n\n const batchResults = await reduceInBatchesSerially<string, BatchResult[]>({\n values: assetIds,\n batchSize: PRICE_API_BATCH_SIZE,\n eachBatch: async (workingResult, batch) => {\n const result = await this.#fetchSpotPricesBatch(\n batch,\n selectedCurrency,\n );\n return [...(workingResult as BatchResult[]), result];\n },\n initialResult: [],\n });\n\n const fetchedAt = Date.now();\n const prices: Record<Caip19AssetId, FungibleAssetPrice> = {};\n\n for (const { selectedCurrencyPrices, usdPrices } of batchResults) {\n for (const [assetId, marketData] of Object.entries(\n selectedCurrencyPrices,\n )) {\n const usdMarketData = usdPrices[assetId];\n\n if (\n !isValidMarketData(marketData) ||\n !isValidMarketData(usdMarketData)\n ) {\n continue;\n }\n\n prices[assetId as Caip19AssetId] = {\n ...marketData,\n assetPriceType: 'fungible',\n usdPrice: usdMarketData.price,\n lastUpdated: fetchedAt,\n };\n }\n }\n\n return prices;\n }\n\n /**\n * Fetch spot prices for all provided asset IDs, deduplicating via the\n * deduper (freshness TTL + per-asset inflight coalescing).\n *\n * @param assetIds - Array of CAIP-19 asset IDs.\n * @returns Spot prices response (only contains entries for assets that were\n * actually fetched or joined from inflight).\n */\n async #fetchSpotPrices(\n assetIds: Caip19AssetId[],\n ): Promise<Record<Caip19AssetId, FungibleAssetPrice>> {\n return this.#deduper.fetch(assetIds);\n }\n\n /**\n * Get unique asset IDs from the assetsBalance state.\n * Filters by accounts and chains from the request.\n *\n * @param request - Data request with accounts and chainIds filters.\n * @returns Array of CAIP-19 asset IDs from balance state.\n */\n #getAssetIdsFromBalanceState(request: DataRequest): Caip19AssetId[] {\n try {\n const state = this.#getAssetsState();\n const assetIds = new Set<Caip19AssetId>();\n\n const accountIds = request.accountsWithSupportedChains.map(\n (a) => a.account.id,\n );\n const accountFilter =\n accountIds.length > 0 ? new Set(accountIds) : undefined;\n const chainFilter =\n request.chainIds.length > 0 ? new Set(request.chainIds) : undefined;\n\n if (state?.assetsBalance) {\n for (const [accountId, accountBalances] of Object.entries(\n state.assetsBalance,\n )) {\n // Filter by account if specified\n if (accountFilter && !accountFilter.has(accountId)) {\n continue;\n }\n\n for (const assetId of Object.keys(accountBalances)) {\n // Filter by chain if specified; skip malformed asset IDs for this entry only\n if (chainFilter) {\n try {\n const { chainId } = parseCaipAssetType(\n assetId as Caip19AssetId,\n );\n if (!chainFilter.has(chainId)) {\n continue;\n }\n } catch (error) {\n log('Skipping malformed asset ID in balance state', {\n assetId,\n error,\n });\n continue;\n }\n }\n assetIds.add(assetId as Caip19AssetId);\n }\n }\n }\n\n return [...assetIds];\n } catch (error) {\n log('Failed to get asset IDs from balance state', { error });\n return [];\n }\n }\n\n /**\n * Price API supported networks.\n *\n * @returns CAIP-2 chain IDs; empty on error.\n */\n async #getSupportedNetworks(): Promise<Set<string>> {\n try {\n const response = await fetchWithTimeout(\n (): Promise<PriceSupportedNetworksResponse> =>\n this.#apiClient.prices.fetchPriceV2SupportedNetworks(),\n this.#fetchTimeoutMs,\n );\n\n const allNetworks = [...response.fullSupport, ...response.partialSupport];\n return new Set(allNetworks);\n } catch (error) {\n log('Failed to fetch price supported networks', { error });\n return new Set();\n }\n }\n\n /**\n * Keeps asset IDs whose chain is in `supportedNetworks`.\n *\n * @param assetIds - CAIP-19 asset IDs.\n * @param supportedNetworks - Supported CAIP-2 chain IDs.\n * @returns Matching IDs, or all IDs if the set is empty.\n */\n #filterAssetsByNetwork(\n assetIds: Caip19AssetId[],\n supportedNetworks: Set<string>,\n ): Caip19AssetId[] {\n if (supportedNetworks.size === 0) {\n return assetIds;\n }\n return assetIds.filter((assetId) => {\n try {\n const parsed = parseCaipAssetType(assetId);\n const chainId = `${parsed.chain.namespace}:${parsed.chain.reference}`;\n return supportedNetworks.has(chainId);\n } catch {\n return false;\n }\n });\n }\n\n // ============================================================================\n // FETCH\n // ============================================================================\n\n /**\n * Fetch prices for assets held by the accounts and chains in the request.\n * Reads asset IDs from balance state via the constructor `getAssetsState`.\n *\n * @param request - The data request specifying accounts and chains.\n * @returns DataResponse containing asset prices.\n */\n async fetch(request: DataRequest): Promise<DataResponse> {\n const response: DataResponse = {};\n\n // Get asset IDs from balance state\n const rawAssetIds = this.#getAssetIdsFromBalanceState(request);\n\n // Filter out non-priceable assets (e.g., Tron bandwidth/energy resources)\n const priceableAssetIds = rawAssetIds.filter(isPriceableAsset);\n\n if (priceableAssetIds.length === 0) {\n return response;\n }\n\n const supportedNetworks = await this.#getSupportedNetworks();\n const assetIds = this.#filterAssetsByNetwork(\n priceableAssetIds,\n supportedNetworks,\n );\n\n if (assetIds.length === 0) {\n return response;\n }\n\n try {\n const spotPrices = await this.#fetchSpotPrices([...assetIds]);\n\n response.assetsPrice = {\n ...(response.assetsPrice ?? {}),\n ...spotPrices,\n };\n } catch (error) {\n log('Failed to fetch prices', { error });\n }\n\n return response;\n }\n\n // ============================================================================\n // SUBSCRIBE\n // ============================================================================\n\n /**\n * Subscribe to price updates.\n * Sets up polling that fetches prices for all assets in assetsBalance state.\n *\n * @param subscriptionRequest - The subscription request configuration.\n */\n async subscribe(subscriptionRequest: SubscriptionRequest): Promise<void> {\n const { request, subscriptionId, isUpdate } = subscriptionRequest;\n\n // Handle subscription update: refresh request and fetch immediately so\n // newly held / still-unpriced assets (e.g. seeded natives) do not wait\n // for the next poll tick. Deduper freshness TTL skips recently priced IDs.\n if (isUpdate) {\n const existing = this.#activeSubscriptions.get(subscriptionId);\n if (existing) {\n existing.request = request;\n existing.onAssetsUpdate = subscriptionRequest.onAssetsUpdate;\n\n try {\n const fetchResponse = await this.fetch(request);\n if (\n fetchResponse.assetsPrice &&\n Object.keys(fetchResponse.assetsPrice).length > 0\n ) {\n await existing.onAssetsUpdate({\n ...fetchResponse,\n updateMode: 'merge',\n });\n }\n } catch (error) {\n log('Subscription update fetch failed', { subscriptionId, error });\n }\n return;\n }\n }\n\n // Clean up existing subscription\n await this.unsubscribe(subscriptionId);\n\n const pollInterval = request.updateInterval ?? this.#pollInterval;\n\n // Cap the freshness TTL strictly below the effective poll interval.\n // `fetchedAt` is stamped when a fetch completes (slightly after the tick\n // that triggered it), so a TTL equal to the poll interval would leave the\n // asset still \"fresh\" at the next tick, making the subscription re-fetch\n // only every other poll. The margin also absorbs network latency / jitter.\n this.#deduper.freshnessTtlMs = Math.min(\n this.#deduper.freshnessTtlMs,\n Math.floor(pollInterval * FRESHNESS_TTL_POLL_RATIO),\n );\n\n // Create poll function - fetches prices using constructor getAssetsState.\n // The freshness TTL naturally gates re-fetches: assets fetched less than\n // `priceFreshnessTtlMs` ago are skipped, preventing duplicates when middleware\n // or other triggers already fetched the same assets between polls.\n // Concurrent middleware calls will join the inflight promise rather than\n // issuing duplicate requests.\n const pollFn = async (): Promise<void> => {\n try {\n const subscription = this.#activeSubscriptions.get(subscriptionId);\n if (!subscription) {\n return;\n }\n\n const fetchResponse = await this.fetch(subscription.request);\n\n // Only report if we got prices\n if (\n fetchResponse.assetsPrice &&\n Object.keys(fetchResponse.assetsPrice).length > 0\n ) {\n await subscription.onAssetsUpdate({\n ...fetchResponse,\n // merge overwrites existing spot prices on each poll; update would\n // seed-only and leave the first price forever.\n updateMode: 'merge',\n });\n }\n } catch (error) {\n log('Subscription poll failed', { subscriptionId, error });\n }\n };\n\n // Set up polling\n const timer = setInterval(() => {\n pollFn().catch(console.error);\n }, pollInterval);\n\n // Store subscription\n this.#activeSubscriptions.set(subscriptionId, {\n cleanup: () => {\n clearInterval(timer);\n },\n request,\n onAssetsUpdate: subscriptionRequest.onAssetsUpdate,\n });\n\n // Initial fetch\n await pollFn();\n }\n\n /**\n * Unsubscribe from price updates.\n *\n * @param subscriptionId - The ID of the subscription to cancel.\n */\n async unsubscribe(subscriptionId: string): Promise<void> {\n const subscription = this.#activeSubscriptions.get(subscriptionId);\n if (subscription) {\n subscription.cleanup();\n this.#activeSubscriptions.delete(subscriptionId);\n }\n }\n\n /**\n * Invalidate the price freshness cache, forcing the next fetch to call the\n * API regardless of TTL. Use when external state changes (e.g. selected\n * currency) require a full refresh.\n */\n invalidatePriceCache(): void {\n this.#deduper.invalidate();\n }\n\n /**\n * Destroy the data source and clean up all subscriptions.\n */\n destroy(): void {\n for (const subscription of this.#activeSubscriptions.values()) {\n subscription.cleanup();\n }\n this.#activeSubscriptions.clear();\n this.#deduper.destroy();\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metamask-previews/assets-controller",
|
|
3
|
-
"version": "17.0.0-preview-
|
|
3
|
+
"version": "17.0.0-preview-378f5fcf7",
|
|
4
4
|
"description": "Tracks assets balances/prices and handles token detection across all digital assets",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Ethereum",
|
|
@@ -46,8 +46,6 @@
|
|
|
46
46
|
"since-latest-release": "../../scripts/since-latest-release.sh",
|
|
47
47
|
"test": "NODE_OPTIONS=--experimental-vm-modules jest --reporters=jest-silent-reporter",
|
|
48
48
|
"test:clean": "NODE_OPTIONS=--experimental-vm-modules jest --clearCache",
|
|
49
|
-
"test:integration": "NODE_OPTIONS=--experimental-vm-modules jest --config jest.config.integration.cjs",
|
|
50
|
-
"test:unit": "NODE_OPTIONS=--experimental-vm-modules jest --config jest.config.unit.cjs",
|
|
51
49
|
"test:verbose": "NODE_OPTIONS=--experimental-vm-modules jest --verbose",
|
|
52
50
|
"test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch"
|
|
53
51
|
},
|
|
@@ -57,7 +55,7 @@
|
|
|
57
55
|
"@ethersproject/providers": "^5.8.0",
|
|
58
56
|
"@metamask/account-tree-controller": "^11.0.0",
|
|
59
57
|
"@metamask/accounts-controller": "^40.0.0",
|
|
60
|
-
"@metamask/assets-controllers": "^112.
|
|
58
|
+
"@metamask/assets-controllers": "^112.0.4",
|
|
61
59
|
"@metamask/base-controller": "^10.0.0",
|
|
62
60
|
"@metamask/client-controller": "^2.0.0",
|
|
63
61
|
"@metamask/config-registry-controller": "^4.0.0",
|
|
@@ -68,11 +66,10 @@
|
|
|
68
66
|
"@metamask/keyring-internal-api": "^12.0.0",
|
|
69
67
|
"@metamask/keyring-snap-client": "^10.0.0",
|
|
70
68
|
"@metamask/messenger": "^3.0.0",
|
|
71
|
-
"@metamask/multichain-transactions-controller": "^8.0.0",
|
|
72
69
|
"@metamask/network-controller": "^37.0.0",
|
|
73
70
|
"@metamask/network-enablement-controller": "^7.0.1",
|
|
74
71
|
"@metamask/permission-controller": "^14.0.0",
|
|
75
|
-
"@metamask/phishing-controller": "^18.
|
|
72
|
+
"@metamask/phishing-controller": "^18.1.1",
|
|
76
73
|
"@metamask/polling-controller": "^17.0.0",
|
|
77
74
|
"@metamask/preferences-controller": "^24.0.0",
|
|
78
75
|
"@metamask/remote-feature-flag-controller": "^7.0.0",
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { InternalAccount } from '@metamask/keyring-internal-api';
|
|
2
|
-
import type { AccountId, AssetBalance, Caip19AssetId } from '../types.js';
|
|
3
|
-
/**
|
|
4
|
-
* Remove balance entries the given accounts cannot own.
|
|
5
|
-
*
|
|
6
|
-
* Ownership is the chain namespace of the account's scopes, not the
|
|
7
|
-
* currently enabled chains. `eip155:0` keeps every EVM chain, including
|
|
8
|
-
* ones the user has turned off. An account with no scopes is left untouched,
|
|
9
|
-
* as is any account that is not in `accounts`.
|
|
10
|
-
*
|
|
11
|
-
* Mutates `balances`. Intended to run inside an Immer `update` draft, once
|
|
12
|
-
* per unlock, the same way spam cleanup is applied.
|
|
13
|
-
*
|
|
14
|
-
* @param balances - `assetsBalance` being updated.
|
|
15
|
-
* @param accounts - Accounts whose scopes define the namespaces to keep.
|
|
16
|
-
* @returns True when at least one balance entry was removed.
|
|
17
|
-
*/
|
|
18
|
-
export declare function dropBalancesOutsideAccountScopes(balances: Record<AccountId, Record<Caip19AssetId, AssetBalance>>, accounts: readonly InternalAccount[]): boolean;
|
|
19
|
-
//# sourceMappingURL=dropBalancesOutsideAccountScopes.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"dropBalancesOutsideAccountScopes.d.ts","sourceRoot":"","sources":["../../src/migrations/dropBalancesOutsideAccountScopes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAQtE,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE1E;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gCAAgC,CAC9C,QAAQ,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC,EAChE,QAAQ,EAAE,SAAS,eAAe,EAAE,GACnC,OAAO,CAQT"}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { isCaipAssetType, isCaipChainId, parseCaipAssetType, parseCaipChainId, } from '@metamask/utils';
|
|
2
|
-
/**
|
|
3
|
-
* Remove balance entries the given accounts cannot own.
|
|
4
|
-
*
|
|
5
|
-
* Ownership is the chain namespace of the account's scopes, not the
|
|
6
|
-
* currently enabled chains. `eip155:0` keeps every EVM chain, including
|
|
7
|
-
* ones the user has turned off. An account with no scopes is left untouched,
|
|
8
|
-
* as is any account that is not in `accounts`.
|
|
9
|
-
*
|
|
10
|
-
* Mutates `balances`. Intended to run inside an Immer `update` draft, once
|
|
11
|
-
* per unlock, the same way spam cleanup is applied.
|
|
12
|
-
*
|
|
13
|
-
* @param balances - `assetsBalance` being updated.
|
|
14
|
-
* @param accounts - Accounts whose scopes define the namespaces to keep.
|
|
15
|
-
* @returns True when at least one balance entry was removed.
|
|
16
|
-
*/
|
|
17
|
-
export function dropBalancesOutsideAccountScopes(balances, accounts) {
|
|
18
|
-
let removed = false;
|
|
19
|
-
for (const account of accounts) {
|
|
20
|
-
if (dropAccountBalancesOutsideScopes(balances, account)) {
|
|
21
|
-
removed = true;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
return removed;
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Remove one account's balances whose chain namespace is outside its scopes.
|
|
28
|
-
*
|
|
29
|
-
* @param balances - `assetsBalance` being updated.
|
|
30
|
-
* @param account - Account whose scopes define the namespaces to keep.
|
|
31
|
-
* @returns True when at least one balance entry was removed.
|
|
32
|
-
*/
|
|
33
|
-
function dropAccountBalancesOutsideScopes(balances, account) {
|
|
34
|
-
// Persisted accounts can omit `scopes` even though the type requires it.
|
|
35
|
-
const scopes = account.scopes ?? [];
|
|
36
|
-
if (scopes.length === 0) {
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
const accountBalances = balances[account.id];
|
|
40
|
-
if (!accountBalances) {
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
const namespaces = new Set();
|
|
44
|
-
for (const scope of scopes) {
|
|
45
|
-
if (isCaipChainId(scope)) {
|
|
46
|
-
namespaces.add(parseCaipChainId(scope).namespace);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
if (namespaces.size === 0) {
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
let removed = false;
|
|
53
|
-
for (const assetId of Object.keys(accountBalances)) {
|
|
54
|
-
// Malformed keys are left alone; this cleanup only targets valid CAIP-19
|
|
55
|
-
// entries stamped onto the wrong account.
|
|
56
|
-
if (!isCaipAssetType(assetId)) {
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
if (!namespaces.has(parseCaipAssetType(assetId).chain.namespace)) {
|
|
60
|
-
delete accountBalances[assetId];
|
|
61
|
-
removed = true;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return removed;
|
|
65
|
-
}
|
|
66
|
-
//# sourceMappingURL=dropBalancesOutsideAccountScopes.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"dropBalancesOutsideAccountScopes.js","sourceRoot":"","sources":["../../src/migrations/dropBalancesOutsideAccountScopes.ts"],"names":[],"mappings":"AACA,OAAO,EACL,eAAe,EACf,aAAa,EACb,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,iBAAiB,CAAC;AAIzB;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,gCAAgC,CAC9C,QAAgE,EAChE,QAAoC;IAEpC,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,gCAAgC,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,CAAC;YACxD,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;GAMG;AACH,SAAS,gCAAgC,CACvC,QAAgE,EAChE,OAAwB;IAExB,yEAAyE;IACzE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC;IACpC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,eAAe,GAAG,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC7C,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;IACrC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,UAAU,CAAC,GAAG,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,CAAC;QACpD,CAAC;IACH,CAAC;IACD,IAAI,UAAU,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,eAAe,CAAoB,EAAE,CAAC;QACtE,yEAAyE;QACzE,0CAA0C;QAC1C,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,EAAE,CAAC;YAC9B,SAAS;QACX,CAAC;QACD,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;YACjE,OAAO,eAAe,CAAC,OAAO,CAAC,CAAC;YAChC,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC","sourcesContent":["import type { InternalAccount } from '@metamask/keyring-internal-api';\nimport {\n isCaipAssetType,\n isCaipChainId,\n parseCaipAssetType,\n parseCaipChainId,\n} from '@metamask/utils';\n\nimport type { AccountId, AssetBalance, Caip19AssetId } from '../types.js';\n\n/**\n * Remove balance entries the given accounts cannot own.\n *\n * Ownership is the chain namespace of the account's scopes, not the\n * currently enabled chains. `eip155:0` keeps every EVM chain, including\n * ones the user has turned off. An account with no scopes is left untouched,\n * as is any account that is not in `accounts`.\n *\n * Mutates `balances`. Intended to run inside an Immer `update` draft, once\n * per unlock, the same way spam cleanup is applied.\n *\n * @param balances - `assetsBalance` being updated.\n * @param accounts - Accounts whose scopes define the namespaces to keep.\n * @returns True when at least one balance entry was removed.\n */\nexport function dropBalancesOutsideAccountScopes(\n balances: Record<AccountId, Record<Caip19AssetId, AssetBalance>>,\n accounts: readonly InternalAccount[],\n): boolean {\n let removed = false;\n for (const account of accounts) {\n if (dropAccountBalancesOutsideScopes(balances, account)) {\n removed = true;\n }\n }\n return removed;\n}\n\n/**\n * Remove one account's balances whose chain namespace is outside its scopes.\n *\n * @param balances - `assetsBalance` being updated.\n * @param account - Account whose scopes define the namespaces to keep.\n * @returns True when at least one balance entry was removed.\n */\nfunction dropAccountBalancesOutsideScopes(\n balances: Record<AccountId, Record<Caip19AssetId, AssetBalance>>,\n account: InternalAccount,\n): boolean {\n // Persisted accounts can omit `scopes` even though the type requires it.\n const scopes = account.scopes ?? [];\n if (scopes.length === 0) {\n return false;\n }\n\n const accountBalances = balances[account.id];\n if (!accountBalances) {\n return false;\n }\n\n const namespaces = new Set<string>();\n for (const scope of scopes) {\n if (isCaipChainId(scope)) {\n namespaces.add(parseCaipChainId(scope).namespace);\n }\n }\n if (namespaces.size === 0) {\n return false;\n }\n\n let removed = false;\n for (const assetId of Object.keys(accountBalances) as Caip19AssetId[]) {\n // Malformed keys are left alone; this cleanup only targets valid CAIP-19\n // entries stamped onto the wrong account.\n if (!isCaipAssetType(assetId)) {\n continue;\n }\n if (!namespaces.has(parseCaipAssetType(assetId).chain.namespace)) {\n delete accountBalances[assetId];\n removed = true;\n }\n }\n return removed;\n}\n"]}
|