@metamask-previews/assets-controller 6.0.0-preview-e7ff8e76d → 6.0.0-preview-8a15a8aa8
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 +18 -0
- package/dist/AssetsController.cjs +50 -12
- package/dist/AssetsController.cjs.map +1 -1
- package/dist/AssetsController.d.cts.map +1 -1
- package/dist/AssetsController.d.mts.map +1 -1
- package/dist/AssetsController.mjs +50 -12
- package/dist/AssetsController.mjs.map +1 -1
- package/dist/data-sources/BackendWebsocketDataSource.cjs +2 -1
- package/dist/data-sources/BackendWebsocketDataSource.cjs.map +1 -1
- package/dist/data-sources/BackendWebsocketDataSource.d.cts.map +1 -1
- package/dist/data-sources/BackendWebsocketDataSource.d.mts.map +1 -1
- package/dist/data-sources/BackendWebsocketDataSource.mjs +2 -1
- package/dist/data-sources/BackendWebsocketDataSource.mjs.map +1 -1
- package/dist/data-sources/RpcDataSource.cjs +8 -5
- package/dist/data-sources/RpcDataSource.cjs.map +1 -1
- package/dist/data-sources/RpcDataSource.d.cts +4 -3
- package/dist/data-sources/RpcDataSource.d.cts.map +1 -1
- package/dist/data-sources/RpcDataSource.d.mts +4 -3
- package/dist/data-sources/RpcDataSource.d.mts.map +1 -1
- package/dist/data-sources/RpcDataSource.mjs +8 -5
- package/dist/data-sources/RpcDataSource.mjs.map +1 -1
- package/dist/data-sources/TokenDataSource.cjs +8 -6
- package/dist/data-sources/TokenDataSource.cjs.map +1 -1
- package/dist/data-sources/TokenDataSource.d.cts.map +1 -1
- package/dist/data-sources/TokenDataSource.d.mts.map +1 -1
- package/dist/data-sources/TokenDataSource.mjs +8 -6
- package/dist/data-sources/TokenDataSource.mjs.map +1 -1
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.cjs +4 -3
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.cjs.map +1 -1
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.cts.map +1 -1
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.mts.map +1 -1
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.mjs +4 -3
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.mjs.map +1 -1
- package/dist/utils/formatExchangeRatesForBridge.cjs +9 -6
- package/dist/utils/formatExchangeRatesForBridge.cjs.map +1 -1
- package/dist/utils/formatExchangeRatesForBridge.d.cts.map +1 -1
- package/dist/utils/formatExchangeRatesForBridge.d.mts.map +1 -1
- package/dist/utils/formatExchangeRatesForBridge.mjs +10 -7
- package/dist/utils/formatExchangeRatesForBridge.mjs.map +1 -1
- package/dist/utils/formatStateForTransactionPay.cjs +7 -4
- package/dist/utils/formatStateForTransactionPay.cjs.map +1 -1
- package/dist/utils/formatStateForTransactionPay.d.cts.map +1 -1
- package/dist/utils/formatStateForTransactionPay.d.mts.map +1 -1
- package/dist/utils/formatStateForTransactionPay.mjs +7 -4
- package/dist/utils/formatStateForTransactionPay.mjs.map +1 -1
- package/dist/utils/isNativeAsset.cjs +39 -0
- package/dist/utils/isNativeAsset.cjs.map +1 -0
- package/dist/utils/isNativeAsset.d.cts +16 -0
- package/dist/utils/isNativeAsset.d.cts.map +1 -0
- package/dist/utils/isNativeAsset.d.mts +16 -0
- package/dist/utils/isNativeAsset.d.mts.map +1 -0
- package/dist/utils/isNativeAsset.mjs +35 -0
- package/dist/utils/isNativeAsset.mjs.map +1 -0
- package/package.json +8 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TokenDataSource.mjs","sourceRoot":"","sources":["../../src/data-sources/TokenDataSource.ts"],"names":[],"mappings":"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","sourcesContent":["import type { V3AssetResponse } from '@metamask/core-backend';\nimport { ApiPlatformClient } from '@metamask/core-backend';\nimport type {\n BulkTokenScanResponse,\n PhishingControllerBulkScanTokensAction,\n} from '@metamask/phishing-controller';\nimport { TokenScanResultType } from '@metamask/phishing-controller';\nimport { KnownCaipNamespace, parseCaipAssetType } from '@metamask/utils';\nimport type { CaipAssetType } from '@metamask/utils';\n\nimport type { AssetsControllerMessenger } from '../AssetsController';\nimport { projectLogger, createModuleLogger } from '../logger';\nimport { forDataTypes } from '../types';\nimport type {\n Caip19AssetId,\n AssetMetadata,\n Middleware,\n FungibleAssetMetadata,\n} from '../types';\nimport {\n isStakingContractAssetId,\n reduceInBatchesSerially,\n} from './evm-rpc-services';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst CONTROLLER_NAME = 'TokenDataSource';\n\nconst log = createModuleLogger(projectLogger, CONTROLLER_NAME);\n\n/** Max asset IDs per tokens API request. */\nconst TOKENS_API_BATCH_SIZE = 50;\n\n/** Max tokens per PhishingController:bulkScanTokens request (see PhishingController). */\nconst BULK_SCAN_BATCH_SIZE = 100;\n\n/**\n * Minimum number of aggregator occurrences required for an EVM ERC-20 token to\n * pass the spam filter. Non-EVM tokens are filtered via Blockaid bulk scan instead.\n */\nconst MIN_TOKEN_OCCURRENCES = 3;\n\n/** CAIP-19 `assetNamespace` segments used across filtering logic. */\nenum CaipAssetNamespace {\n Slip44 = 'slip44',\n Erc20 = 'erc20',\n Token = 'token',\n}\n\n// ============================================================================\n// OPTIONS\n// ============================================================================\n\nexport type TokenDataSourceOptions = {\n /** ApiPlatformClient for API calls with caching */\n queryApiClient: ApiPlatformClient;\n /** Returns CAIP-19 native asset IDs from NetworkEnablementController state */\n getNativeAssetIds: () => string[];\n};\n\n/**\n * Messenger actions `TokenDataSource` may invoke (via {@link AssetsControllerMessenger}).\n * Not re-exported from the package public `index` (repo ESLint); import from this module when\n * typing a messenger in the same package or tests.\n */\nexport type TokenDataSourceAllowedActions =\n PhishingControllerBulkScanTokensAction;\n\n// ============================================================================\n// HELPER FUNCTIONS\n// ============================================================================\n\n/**\n * Transform V3 API response to FungibleAssetMetadata for state storage.\n *\n * Mapping:\n * - assetId → used to derive `type` (native/erc20/spl)\n * - iconUrl → image\n * - All other fields map directly\n *\n * @param assetId - CAIP-19 asset ID used to derive token type.\n * @param assetData - V3 API response data.\n * @returns FungibleAssetMetadata for state storage.\n */\nfunction transformV3AssetResponseToMetadata(\n assetId: string,\n assetData: V3AssetResponse,\n): AssetMetadata {\n const parsed = parseCaipAssetType(assetId as CaipAssetType);\n let tokenType: 'native' | 'erc20' | 'spl' = 'erc20';\n\n if (parsed.assetNamespace === 'slip44') {\n tokenType = 'native';\n } else if (parsed.assetNamespace === 'spl') {\n tokenType = 'spl';\n }\n\n const metadata: FungibleAssetMetadata = {\n // Type derived from assetId\n type: tokenType,\n // BaseAssetMetadata fields\n name: assetData.name,\n symbol: assetData.symbol,\n decimals: assetData.decimals,\n image: assetData.iconUrl,\n // Direct mapping fields\n coingeckoId: assetData.coingeckoId,\n occurrences: assetData.occurrences,\n aggregators: assetData.aggregators,\n labels: assetData.labels,\n erc20Permit: assetData.erc20Permit,\n fees: assetData.fees,\n honeypotStatus: assetData.honeypotStatus,\n storage: assetData.storage,\n isContractVerified: assetData.isContractVerified,\n description: assetData.description,\n };\n\n return metadata;\n}\n\n// ============================================================================\n// TOKEN DATA SOURCE\n// ============================================================================\n\n/**\n * TokenDataSource enriches responses with token metadata from the Tokens API.\n *\n * This middleware-based data source:\n * - Checks detected assets for missing metadata/images\n * - Fetches metadata from Tokens API v3 for assets needing enrichment\n * - Merges fetched metadata into the response\n *\n * Pass the same {@link AssetsControllerMessenger} as other data sources for Blockaid\n * token scans.\n */\nexport class TokenDataSource {\n readonly name = CONTROLLER_NAME;\n\n getName(): string {\n return this.name;\n }\n\n /** ApiPlatformClient for cached API calls */\n readonly #apiClient: ApiPlatformClient;\n\n /** Returns CAIP-19 native asset IDs from NetworkEnablementController state */\n readonly #getNativeAssetIds: () => string[];\n\n /** Shared controller messenger — used for `PhishingController:bulkScanTokens`. */\n readonly #messenger: AssetsControllerMessenger;\n\n constructor(\n messenger: AssetsControllerMessenger,\n options: TokenDataSourceOptions,\n ) {\n this.#messenger = messenger;\n this.#apiClient = options.queryApiClient;\n this.#getNativeAssetIds = options.getNativeAssetIds;\n }\n\n /**\n * Gets the supported networks from the API.\n * Caching is handled by ApiPlatformClient.\n *\n * @returns Set of supported chain IDs in CAIP format\n */\n async #getSupportedNetworks(): Promise<Set<string>> {\n try {\n // Use v2/supportedNetworks which returns CAIP chain IDs\n // ApiPlatformClient handles caching\n const response =\n await this.#apiClient.tokens.fetchTokenV2SupportedNetworks();\n\n // Combine full and partial support networks\n const allNetworks = [...response.fullSupport, ...response.partialSupport];\n\n return new Set(allNetworks);\n } catch (error) {\n log('Failed to fetch supported networks', { error });\n return new Set();\n }\n }\n\n /**\n * Filters asset IDs to only include those from supported networks.\n *\n * @param assetIds - Array of CAIP-19 asset IDs\n * @param supportedNetworks - Set of supported chain IDs\n * @returns Array of asset IDs from supported networks\n */\n #filterAssetsByNetwork(\n assetIds: string[],\n supportedNetworks: Set<string>,\n ): string[] {\n return assetIds.filter((assetId) => {\n try {\n const parsed = parseCaipAssetType(assetId as CaipAssetType);\n // chainId is in format \"eip155:1\" or \"tron:728126428\"\n // parsed.chain has namespace and reference properties\n const chainId = `${parsed.chain.namespace}:${parsed.chain.reference}`;\n return supportedNetworks.has(chainId);\n } catch {\n // If we can't parse the asset ID, filter it out\n return false;\n }\n });\n }\n\n /**\n * Filters non-EVM fungible `token` assets flagged as malicious by Blockaid\n * via `PhishingController:bulkScanTokens`. Only the `token` namespace (e.g.\n * Solana mints) is scanned; native (`slip44`) and EVM assets are not handled\n * here (EVM uses occurrence-count filtering instead). Fails open on error.\n *\n * @param assets - CAIP-19 asset IDs to filter (non-EVM only).\n * @returns Asset IDs with malicious tokens removed.\n */\n async #filterBlockaidSpamTokens(assets: string[]): Promise<string[]> {\n if (assets.length === 0) {\n return assets;\n }\n\n const tokensByChain: Record<string, { asset: string; address: string }[]> =\n {};\n\n for (const asset of assets) {\n try {\n const { assetNamespace, assetReference, chain } = parseCaipAssetType(\n asset as CaipAssetType,\n );\n\n if (assetNamespace === CaipAssetNamespace.Token) {\n const chainName = chain.namespace;\n if (!tokensByChain[chainName]) {\n tokensByChain[chainName] = [];\n }\n tokensByChain[chainName].push({ asset, address: assetReference });\n }\n } catch {\n // Malformed or unsupported for bulk scan — keep asset (fail open)\n }\n }\n\n if (Object.keys(tokensByChain).length === 0) {\n return assets;\n }\n\n const rejectedAssets = new Set<string>();\n\n try {\n for (const [chainId, tokenEntries] of Object.entries(tokensByChain)) {\n const addresses = tokenEntries.map((entry) => entry.address);\n const batches: string[][] = [];\n for (let i = 0; i < addresses.length; i += BULK_SCAN_BATCH_SIZE) {\n batches.push(addresses.slice(i, i + BULK_SCAN_BATCH_SIZE));\n }\n\n const batchResults = await Promise.allSettled(\n batches.map((batch) =>\n this.#messenger.call('PhishingController:bulkScanTokens', {\n chainId,\n tokens: batch,\n }),\n ),\n );\n\n const scanResponse: BulkTokenScanResponse = {};\n for (const result of batchResults) {\n if (result.status === 'fulfilled') {\n Object.assign(scanResponse, result.value);\n }\n }\n\n for (const entry of tokenEntries) {\n const result = scanResponse[entry.address];\n if (result?.result_type === TokenScanResultType.Malicious) {\n rejectedAssets.add(entry.asset);\n }\n }\n }\n } catch (error) {\n log('Blockaid bulk token scan failed; keeping all tokens', { error });\n return assets;\n }\n\n return assets.filter((asset) => !rejectedAssets.has(asset));\n }\n\n /**\n * Get the middleware for enriching responses with token metadata.\n *\n * This middleware:\n * 1. Extracts the response from context\n * 2. Fetches metadata for detected assets (assets without metadata)\n * 3. Enriches the response with fetched metadata\n * 4. Calls next() at the end to continue the middleware chain\n *\n * @returns The middleware function for the assets pipeline.\n */\n get assetsMiddleware(): Middleware {\n return forDataTypes(['metadata'], async (ctx, next) => {\n // Extract response from context\n const { response } = ctx;\n\n const { assetsInfo: stateMetadata, customAssets } = ctx.getAssetsState();\n const assetIdsNeedingMetadata = new Set<string>();\n\n // Custom assets are user-imported — exempt from spam filtering.\n const customAssetIds = new Set<string>(\n Object.values(customAssets ?? {}).flat(),\n );\n\n // Always include native asset IDs from NetworkEnablementController\n for (const nativeAssetId of this.#getNativeAssetIds()) {\n assetIdsNeedingMetadata.add(nativeAssetId);\n }\n\n // Also fetch metadata for detected assets that are missing it\n if (response.detectedAssets) {\n for (const detectedIds of Object.values(response.detectedAssets)) {\n for (const assetId of detectedIds) {\n // Skip if response already has metadata with image\n const responseMetadata = response.assetsInfo?.[assetId];\n if (responseMetadata?.image) {\n continue;\n }\n\n // Skip if state already has metadata with image\n const existingMetadata = stateMetadata[assetId];\n if (existingMetadata?.image) {\n continue;\n }\n\n // Skip staking contracts; we use built-in metadata and do not fetch from the tokens API\n if (isStakingContractAssetId(assetId)) {\n continue;\n }\n\n assetIdsNeedingMetadata.add(assetId);\n }\n }\n }\n\n if (assetIdsNeedingMetadata.size === 0) {\n return next(ctx);\n }\n\n // Filter asset IDs to only include supported networks\n const supportedNetworks = await this.#getSupportedNetworks();\n const supportedAssetIds = this.#filterAssetsByNetwork(\n [...assetIdsNeedingMetadata],\n supportedNetworks,\n );\n\n if (supportedAssetIds.length === 0) {\n return next(ctx);\n }\n\n try {\n const fetchOptions = {\n includeIconUrl: true,\n includeMarketData: true,\n includeMetadata: true,\n includeLabels: true,\n includeRwaData: true,\n includeAggregators: true,\n includeOccurrences: true,\n };\n\n const metadataResponse = await reduceInBatchesSerially<\n string,\n V3AssetResponse[]\n >({\n values: supportedAssetIds,\n batchSize: TOKENS_API_BATCH_SIZE,\n eachBatch: async (workingResult, batch) => {\n const batchResponse = await this.#apiClient.tokens.fetchV3Assets(\n batch,\n fetchOptions,\n );\n return [...(workingResult as V3AssetResponse[]), ...batchResponse];\n },\n initialResult: [],\n });\n\n // Split assets by chain type: EVM uses occurrence-count filtering;\n // non-EVM non-native uses Blockaid; native (slip44) is always allowed.\n const occurrencesByAssetId = new Map(\n metadataResponse.map((a) => [a.assetId, a.occurrences]),\n );\n\n const evmErc20Ids: string[] = [];\n const nonEvmTokenIds: string[] = [];\n\n for (const assetData of metadataResponse) {\n const { assetNamespace, chain } = parseCaipAssetType(\n assetData.assetId as CaipAssetType,\n );\n if (assetNamespace === CaipAssetNamespace.Slip44) {\n // Native assets are always kept — no filtering.\n } else if (\n assetNamespace === CaipAssetNamespace.Erc20 &&\n chain.namespace === KnownCaipNamespace.Eip155\n ) {\n evmErc20Ids.push(assetData.assetId);\n } else if (assetNamespace === CaipAssetNamespace.Token) {\n nonEvmTokenIds.push(assetData.assetId);\n }\n }\n\n // EVM: require minimum occurrence count to suppress low-signal tokens.\n // Tokens with no occurrence data (undefined) are treated the same as\n // zero occurrences and filtered out.\n // Custom assets (user-imported) bypass the occurrence filter.\n const allowedEvmIds = new Set(\n evmErc20Ids.filter(\n (id) =>\n customAssetIds.has(id) ||\n (occurrencesByAssetId.get(id) ?? 0) >= MIN_TOKEN_OCCURRENCES,\n ),\n );\n\n // Non-EVM: Blockaid bulk scan.\n // Custom assets (user-imported) bypass Blockaid filtering.\n const nonEvmToScan = nonEvmTokenIds.filter(\n (id) => !customAssetIds.has(id),\n );\n const allowedNonEvmIds = new Set([\n ...nonEvmTokenIds.filter((id) => customAssetIds.has(id)),\n ...(await this.#filterBlockaidSpamTokens(nonEvmToScan)),\n ]);\n\n // Start with every asset the API returned; only remove those that\n // fail their respective filter (EVM occurrences / non-EVM Blockaid).\n // Native (slip44) and unrecognised namespaces are kept (fail open).\n const allowedAssetIds = new Set(metadataResponse.map((a) => a.assetId));\n\n for (const id of evmErc20Ids) {\n if (!allowedEvmIds.has(id)) {\n allowedAssetIds.delete(id);\n }\n }\n for (const id of nonEvmTokenIds) {\n if (!allowedNonEvmIds.has(id)) {\n allowedAssetIds.delete(id);\n }\n }\n\n response.assetsInfo ??= {};\n\n const filteredOutAssets = new Set<string>();\n\n for (const assetData of metadataResponse) {\n if (!allowedAssetIds.has(assetData.assetId)) {\n filteredOutAssets.add(assetData.assetId);\n continue;\n }\n\n const caipAssetId = assetData.assetId as Caip19AssetId;\n response.assetsInfo[caipAssetId] = transformV3AssetResponseToMetadata(\n assetData.assetId,\n assetData,\n );\n }\n\n if (filteredOutAssets.size > 0) {\n if (response.assetsBalance) {\n for (const accountBalances of Object.values(\n response.assetsBalance,\n )) {\n for (const assetId of filteredOutAssets) {\n delete (accountBalances as Record<string, unknown>)[assetId];\n }\n }\n }\n\n if (response.detectedAssets) {\n for (const [accountId, assetIds] of Object.entries(\n response.detectedAssets,\n )) {\n response.detectedAssets[accountId] = assetIds.filter(\n (id) => !filteredOutAssets.has(id),\n );\n }\n }\n }\n } catch (error) {\n log('Failed to fetch metadata', { error });\n }\n\n // Call next() at the end to continue the middleware chain\n return next(ctx);\n });\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"TokenDataSource.mjs","sourceRoot":"","sources":["../../src/data-sources/TokenDataSource.ts"],"names":[],"mappings":"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","sourcesContent":["import type { V3AssetResponse } from '@metamask/core-backend';\nimport { ApiPlatformClient } from '@metamask/core-backend';\nimport type {\n BulkTokenScanResponse,\n PhishingControllerBulkScanTokensAction,\n} from '@metamask/phishing-controller';\nimport { TokenScanResultType } from '@metamask/phishing-controller';\nimport { KnownCaipNamespace, parseCaipAssetType } from '@metamask/utils';\nimport type { CaipAssetType } from '@metamask/utils';\n\nimport type { AssetsControllerMessenger } from '../AssetsController';\nimport { projectLogger, createModuleLogger } from '../logger';\nimport { forDataTypes } from '../types';\nimport type {\n Caip19AssetId,\n AssetMetadata,\n Middleware,\n FungibleAssetMetadata,\n} from '../types';\nimport { isNativeAsset } from '../utils/isNativeAsset';\nimport {\n isStakingContractAssetId,\n reduceInBatchesSerially,\n} from './evm-rpc-services';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst CONTROLLER_NAME = 'TokenDataSource';\n\nconst log = createModuleLogger(projectLogger, CONTROLLER_NAME);\n\n/** Max asset IDs per tokens API request. */\nconst TOKENS_API_BATCH_SIZE = 50;\n\n/** Max tokens per PhishingController:bulkScanTokens request (see PhishingController). */\nconst BULK_SCAN_BATCH_SIZE = 100;\n\n/**\n * Minimum number of aggregator occurrences required for an EVM ERC-20 token to\n * pass the spam filter. Non-EVM tokens are filtered via Blockaid bulk scan instead.\n */\nconst MIN_TOKEN_OCCURRENCES = 3;\n\n/** CAIP-19 `assetNamespace` segments used across filtering logic. */\nenum CaipAssetNamespace {\n Slip44 = 'slip44',\n Erc20 = 'erc20',\n Token = 'token',\n}\n\n// ============================================================================\n// OPTIONS\n// ============================================================================\n\nexport type TokenDataSourceOptions = {\n /** ApiPlatformClient for API calls with caching */\n queryApiClient: ApiPlatformClient;\n /** Returns CAIP-19 native asset IDs from NetworkEnablementController state */\n getNativeAssetIds: () => string[];\n};\n\n/**\n * Messenger actions `TokenDataSource` may invoke (via {@link AssetsControllerMessenger}).\n * Not re-exported from the package public `index` (repo ESLint); import from this module when\n * typing a messenger in the same package or tests.\n */\nexport type TokenDataSourceAllowedActions =\n PhishingControllerBulkScanTokensAction;\n\n// ============================================================================\n// HELPER FUNCTIONS\n// ============================================================================\n\n/**\n * Transform V3 API response to FungibleAssetMetadata for state storage.\n *\n * Mapping:\n * - assetId → used to derive `type` (native/erc20/spl)\n * - iconUrl → image\n * - All other fields map directly\n *\n * @param assetId - CAIP-19 asset ID used to derive token type.\n * @param assetData - V3 API response data.\n * @returns FungibleAssetMetadata for state storage.\n */\nfunction transformV3AssetResponseToMetadata(\n assetId: Caip19AssetId,\n assetData: V3AssetResponse,\n): AssetMetadata {\n const parsed = parseCaipAssetType(assetId);\n let tokenType: 'native' | 'erc20' | 'spl' = 'erc20';\n\n if (isNativeAsset(assetId)) {\n tokenType = 'native';\n } else if (parsed.assetNamespace === 'spl') {\n tokenType = 'spl';\n }\n\n const metadata: FungibleAssetMetadata = {\n // Type derived from assetId\n type: tokenType,\n // BaseAssetMetadata fields\n name: assetData.name,\n symbol: assetData.symbol,\n decimals: assetData.decimals,\n image: assetData.iconUrl,\n // Direct mapping fields\n coingeckoId: assetData.coingeckoId,\n occurrences: assetData.occurrences,\n aggregators: assetData.aggregators,\n labels: assetData.labels,\n erc20Permit: assetData.erc20Permit,\n fees: assetData.fees,\n honeypotStatus: assetData.honeypotStatus,\n storage: assetData.storage,\n isContractVerified: assetData.isContractVerified,\n description: assetData.description,\n };\n\n return metadata;\n}\n\n// ============================================================================\n// TOKEN DATA SOURCE\n// ============================================================================\n\n/**\n * TokenDataSource enriches responses with token metadata from the Tokens API.\n *\n * This middleware-based data source:\n * - Checks detected assets for missing metadata/images\n * - Fetches metadata from Tokens API v3 for assets needing enrichment\n * - Merges fetched metadata into the response\n *\n * Pass the same {@link AssetsControllerMessenger} as other data sources for Blockaid\n * token scans.\n */\nexport class TokenDataSource {\n readonly name = CONTROLLER_NAME;\n\n getName(): string {\n return this.name;\n }\n\n /** ApiPlatformClient for cached API calls */\n readonly #apiClient: ApiPlatformClient;\n\n /** Returns CAIP-19 native asset IDs from NetworkEnablementController state */\n readonly #getNativeAssetIds: () => string[];\n\n /** Shared controller messenger — used for `PhishingController:bulkScanTokens`. */\n readonly #messenger: AssetsControllerMessenger;\n\n constructor(\n messenger: AssetsControllerMessenger,\n options: TokenDataSourceOptions,\n ) {\n this.#messenger = messenger;\n this.#apiClient = options.queryApiClient;\n this.#getNativeAssetIds = options.getNativeAssetIds;\n }\n\n /**\n * Gets the supported networks from the API.\n * Caching is handled by ApiPlatformClient.\n *\n * @returns Set of supported chain IDs in CAIP format\n */\n async #getSupportedNetworks(): Promise<Set<string>> {\n try {\n // Use v2/supportedNetworks which returns CAIP chain IDs\n // ApiPlatformClient handles caching\n const response =\n await this.#apiClient.tokens.fetchTokenV2SupportedNetworks();\n\n // Combine full and partial support networks\n const allNetworks = [...response.fullSupport, ...response.partialSupport];\n\n return new Set(allNetworks);\n } catch (error) {\n log('Failed to fetch supported networks', { error });\n return new Set();\n }\n }\n\n /**\n * Filters asset IDs to only include those from supported networks.\n *\n * @param assetIds - Array of CAIP-19 asset IDs\n * @param supportedNetworks - Set of supported chain IDs\n * @returns Array of asset IDs from supported networks\n */\n #filterAssetsByNetwork(\n assetIds: string[],\n supportedNetworks: Set<string>,\n ): string[] {\n return assetIds.filter((assetId) => {\n try {\n const parsed = parseCaipAssetType(assetId as CaipAssetType);\n // chainId is in format \"eip155:1\" or \"tron:728126428\"\n // parsed.chain has namespace and reference properties\n const chainId = `${parsed.chain.namespace}:${parsed.chain.reference}`;\n return supportedNetworks.has(chainId);\n } catch {\n // If we can't parse the asset ID, filter it out\n return false;\n }\n });\n }\n\n /**\n * Filters non-EVM fungible `token` assets flagged as malicious by Blockaid\n * via `PhishingController:bulkScanTokens`. Only the `token` namespace (e.g.\n * Solana mints) is scanned; native (`slip44`) and EVM assets are not handled\n * here (EVM uses occurrence-count filtering instead). Fails open on error.\n *\n * @param assets - CAIP-19 asset IDs to filter (non-EVM only).\n * @returns Asset IDs with malicious tokens removed.\n */\n async #filterBlockaidSpamTokens(assets: string[]): Promise<string[]> {\n if (assets.length === 0) {\n return assets;\n }\n\n const tokensByChain: Record<string, { asset: string; address: string }[]> =\n {};\n\n for (const asset of assets) {\n try {\n const { assetNamespace, assetReference, chain } = parseCaipAssetType(\n asset as CaipAssetType,\n );\n\n if (assetNamespace === CaipAssetNamespace.Token) {\n const chainName = chain.namespace;\n if (!tokensByChain[chainName]) {\n tokensByChain[chainName] = [];\n }\n tokensByChain[chainName].push({ asset, address: assetReference });\n }\n } catch {\n // Malformed or unsupported for bulk scan — keep asset (fail open)\n }\n }\n\n if (Object.keys(tokensByChain).length === 0) {\n return assets;\n }\n\n const rejectedAssets = new Set<string>();\n\n try {\n for (const [chainId, tokenEntries] of Object.entries(tokensByChain)) {\n const addresses = tokenEntries.map((entry) => entry.address);\n const batches: string[][] = [];\n for (let i = 0; i < addresses.length; i += BULK_SCAN_BATCH_SIZE) {\n batches.push(addresses.slice(i, i + BULK_SCAN_BATCH_SIZE));\n }\n\n const batchResults = await Promise.allSettled(\n batches.map((batch) =>\n this.#messenger.call('PhishingController:bulkScanTokens', {\n chainId,\n tokens: batch,\n }),\n ),\n );\n\n const scanResponse: BulkTokenScanResponse = {};\n for (const result of batchResults) {\n if (result.status === 'fulfilled') {\n Object.assign(scanResponse, result.value);\n }\n }\n\n for (const entry of tokenEntries) {\n const result = scanResponse[entry.address];\n if (result?.result_type === TokenScanResultType.Malicious) {\n rejectedAssets.add(entry.asset);\n }\n }\n }\n } catch (error) {\n log('Blockaid bulk token scan failed; keeping all tokens', { error });\n return assets;\n }\n\n return assets.filter((asset) => !rejectedAssets.has(asset));\n }\n\n /**\n * Get the middleware for enriching responses with token metadata.\n *\n * This middleware:\n * 1. Extracts the response from context\n * 2. Fetches metadata for detected assets (assets without metadata)\n * 3. Enriches the response with fetched metadata\n * 4. Calls next() at the end to continue the middleware chain\n *\n * @returns The middleware function for the assets pipeline.\n */\n get assetsMiddleware(): Middleware {\n return forDataTypes(['metadata'], async (ctx, next) => {\n // Extract response from context\n const { response } = ctx;\n\n const { assetsInfo: stateMetadata, customAssets } = ctx.getAssetsState();\n const assetIdsNeedingMetadata = new Set<string>();\n\n // Custom assets are user-imported — exempt from spam filtering.\n const customAssetIds = new Set<string>(\n Object.values(customAssets ?? {}).flat(),\n );\n\n // Always include native asset IDs from NetworkEnablementController\n for (const nativeAssetId of this.#getNativeAssetIds()) {\n assetIdsNeedingMetadata.add(nativeAssetId);\n }\n\n // Also fetch metadata for detected assets that are missing it\n if (response.detectedAssets) {\n for (const detectedIds of Object.values(response.detectedAssets)) {\n for (const assetId of detectedIds) {\n // Skip if response already has metadata with image\n const responseMetadata = response.assetsInfo?.[assetId];\n if (responseMetadata?.image) {\n continue;\n }\n\n // Skip if state already has metadata with image\n const existingMetadata = stateMetadata[assetId];\n if (existingMetadata?.image) {\n continue;\n }\n\n // Skip staking contracts; we use built-in metadata and do not fetch from the tokens API\n if (isStakingContractAssetId(assetId)) {\n continue;\n }\n\n assetIdsNeedingMetadata.add(assetId);\n }\n }\n }\n\n if (assetIdsNeedingMetadata.size === 0) {\n return next(ctx);\n }\n\n // Filter asset IDs to only include supported networks\n const supportedNetworks = await this.#getSupportedNetworks();\n const supportedAssetIds = this.#filterAssetsByNetwork(\n [...assetIdsNeedingMetadata],\n supportedNetworks,\n );\n\n if (supportedAssetIds.length === 0) {\n return next(ctx);\n }\n\n try {\n const fetchOptions = {\n includeIconUrl: true,\n includeMarketData: true,\n includeMetadata: true,\n includeLabels: true,\n includeRwaData: true,\n includeAggregators: true,\n includeOccurrences: true,\n };\n\n const metadataResponse = await reduceInBatchesSerially<\n string,\n V3AssetResponse[]\n >({\n values: supportedAssetIds,\n batchSize: TOKENS_API_BATCH_SIZE,\n eachBatch: async (workingResult, batch) => {\n const batchResponse = await this.#apiClient.tokens.fetchV3Assets(\n batch,\n fetchOptions,\n );\n return [...(workingResult as V3AssetResponse[]), ...batchResponse];\n },\n initialResult: [],\n });\n\n // Split assets by chain type: EVM uses occurrence-count filtering;\n // non-EVM non-native uses Blockaid; native (slip44) is always allowed.\n const occurrencesByAssetId = new Map(\n metadataResponse.map((a) => [a.assetId, a.occurrences]),\n );\n\n const evmErc20Ids: string[] = [];\n const nonEvmTokenIds: string[] = [];\n\n for (const assetData of metadataResponse) {\n const assetId = assetData.assetId as Caip19AssetId;\n const { assetNamespace, chain } = parseCaipAssetType(assetId);\n if (isNativeAsset(assetId)) {\n // Native assets are always kept — no filtering.\n } else if (\n assetNamespace === CaipAssetNamespace.Erc20 &&\n chain.namespace === KnownCaipNamespace.Eip155\n ) {\n evmErc20Ids.push(assetId);\n } else if (assetNamespace === CaipAssetNamespace.Token) {\n nonEvmTokenIds.push(assetId);\n }\n }\n\n // EVM: require minimum occurrence count to suppress low-signal tokens.\n // Tokens with no occurrence data (undefined) are treated the same as\n // zero occurrences and filtered out.\n // Custom assets (user-imported) bypass the occurrence filter.\n const allowedEvmIds = new Set(\n evmErc20Ids.filter(\n (id) =>\n customAssetIds.has(id) ||\n (occurrencesByAssetId.get(id) ?? 0) >= MIN_TOKEN_OCCURRENCES,\n ),\n );\n\n // Non-EVM: Blockaid bulk scan.\n // Custom assets (user-imported) bypass Blockaid filtering.\n const nonEvmToScan = nonEvmTokenIds.filter(\n (id) => !customAssetIds.has(id),\n );\n const allowedNonEvmIds = new Set([\n ...nonEvmTokenIds.filter((id) => customAssetIds.has(id)),\n ...(await this.#filterBlockaidSpamTokens(nonEvmToScan)),\n ]);\n\n // Start with every asset the API returned; only remove those that\n // fail their respective filter (EVM occurrences / non-EVM Blockaid).\n // Native (slip44) and unrecognised namespaces are kept (fail open).\n const allowedAssetIds = new Set(metadataResponse.map((a) => a.assetId));\n\n for (const id of evmErc20Ids) {\n if (!allowedEvmIds.has(id)) {\n allowedAssetIds.delete(id);\n }\n }\n for (const id of nonEvmTokenIds) {\n if (!allowedNonEvmIds.has(id)) {\n allowedAssetIds.delete(id);\n }\n }\n\n response.assetsInfo ??= {};\n\n const filteredOutAssets = new Set<string>();\n\n for (const assetData of metadataResponse) {\n if (!allowedAssetIds.has(assetData.assetId)) {\n filteredOutAssets.add(assetData.assetId);\n continue;\n }\n\n const caipAssetId = assetData.assetId as Caip19AssetId;\n response.assetsInfo[caipAssetId] = transformV3AssetResponseToMetadata(\n caipAssetId,\n assetData,\n );\n }\n\n if (filteredOutAssets.size > 0) {\n if (response.assetsBalance) {\n for (const accountBalances of Object.values(\n response.assetsBalance,\n )) {\n for (const assetId of filteredOutAssets) {\n delete (accountBalances as Record<string, unknown>)[assetId];\n }\n }\n }\n\n if (response.detectedAssets) {\n for (const [accountId, assetIds] of Object.entries(\n response.detectedAssets,\n )) {\n response.detectedAssets[accountId] = assetIds.filter(\n (id) => !filteredOutAssets.has(id),\n );\n }\n }\n }\n } catch (error) {\n log('Failed to fetch metadata', { error });\n }\n\n // Call next() at the end to continue the middleware chain\n return next(ctx);\n });\n }\n}\n"]}
|
|
@@ -16,6 +16,7 @@ exports.BalanceFetcher = void 0;
|
|
|
16
16
|
const polling_controller_1 = require("@metamask/polling-controller");
|
|
17
17
|
const utils_1 = require("@metamask/utils");
|
|
18
18
|
const constants_1 = require("../../../utils/constants.cjs");
|
|
19
|
+
const isNativeAsset_1 = require("../../../utils/isNativeAsset.cjs");
|
|
19
20
|
const utils_2 = require("../utils/index.cjs");
|
|
20
21
|
const DEFAULT_BALANCE_INTERVAL = 30000; // 30 seconds
|
|
21
22
|
/**
|
|
@@ -133,15 +134,15 @@ _BalanceFetcher_multicallClient = new WeakMap(), _BalanceFetcher_messenger = new
|
|
|
133
134
|
const chainIdDecimal = parseInt(chainId, 16).toString();
|
|
134
135
|
const assetsToFetch = new Map();
|
|
135
136
|
for (const assetId of Object.keys(accountBalances)) {
|
|
136
|
-
const { chain: { reference: chainReference },
|
|
137
|
+
const { chain: { reference: chainReference }, assetReference, } = (0, utils_1.parseCaipAssetType)(assetId);
|
|
137
138
|
if (chainReference === chainIdDecimal) {
|
|
138
139
|
const assetIdLowerCase = assetId.toLowerCase();
|
|
139
140
|
if (assetsToFetch.has(assetIdLowerCase)) {
|
|
140
141
|
continue;
|
|
141
142
|
}
|
|
142
|
-
const isNative =
|
|
143
|
+
const isNative = (0, isNativeAsset_1.isNativeAsset)(assetId);
|
|
143
144
|
const tokenAddress = isNative
|
|
144
|
-
? constants_1.ZERO_ADDRESS
|
|
145
|
+
? constants_1.ZERO_ADDRESS // BalanceFetcher requires the use of zero address even for chains in which the native token has a non-zero address
|
|
145
146
|
: assetReference.toLowerCase();
|
|
146
147
|
assetsToFetch.set(assetIdLowerCase, {
|
|
147
148
|
assetId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BalanceFetcher.cjs","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/BalanceFetcher.ts"],"names":[],"mappings":"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","sourcesContent":["import { StaticIntervalPollingControllerOnly } from '@metamask/polling-controller';\nimport { parseCaipAssetType } from '@metamask/utils';\n\nimport { ZERO_ADDRESS } from '../../../utils/constants';\nimport type { MulticallClient } from '../clients';\nimport type {\n AccountId,\n Address,\n AssetBalance,\n AssetFetchEntry,\n AssetsBalanceState,\n BalanceFetchResult,\n BalanceOfRequest,\n BalanceOfResponse,\n CaipAssetType,\n ChainId,\n} from '../types';\nimport { reduceInBatchesSerially } from '../utils';\n\nconst DEFAULT_BALANCE_INTERVAL = 30_000; // 30 seconds\n\n/**\n * Minimal messenger interface for BalanceFetcher.\n */\nexport type BalanceFetcherMessenger = {\n call: (action: 'AssetsController:getState') => AssetsBalanceState;\n};\n\nexport type BalanceFetcherConfig = {\n defaultBatchSize?: number;\n defaultTimeoutMs?: number;\n /** Polling interval in ms (default: 30s) */\n pollingInterval?: number;\n};\n\n/**\n * Polling input for BalanceFetcher - identifies what to poll for.\n */\nexport type BalancePollingInput = {\n /** Chain ID (hex format) */\n chainId: ChainId;\n /** Account ID */\n accountId: AccountId;\n /** Account address */\n accountAddress: Address;\n};\n\n/**\n * Callback type for balance updates.\n */\nexport type OnBalanceUpdateCallback = (\n result: BalanceFetchResult,\n) => void | Promise<void>;\n\n/**\n * BalanceFetcher - Fetches token balances via multicall.\n * Extends StaticIntervalPollingControllerOnly for built-in polling support.\n *\n * Callers provide CAIP-19 asset IDs; the fetcher extracts on-chain addresses\n * (or uses the zero address for native assets) and maps multicall responses\n * back to the original asset IDs. This ensures the returned balance entries\n * always carry the correct identifier regardless of chain.\n */\nexport class BalanceFetcher extends StaticIntervalPollingControllerOnly<BalancePollingInput>() {\n readonly #multicallClient: MulticallClient;\n\n readonly #messenger: BalanceFetcherMessenger;\n\n readonly #config: Required<Omit<BalanceFetcherConfig, 'pollingInterval'>>;\n\n #onBalanceUpdate: OnBalanceUpdateCallback | undefined;\n\n constructor(\n multicallClient: MulticallClient,\n messenger: BalanceFetcherMessenger,\n config?: BalanceFetcherConfig,\n ) {\n super();\n this.#multicallClient = multicallClient;\n this.#messenger = messenger;\n this.#config = {\n defaultBatchSize: config?.defaultBatchSize ?? 300,\n defaultTimeoutMs: config?.defaultTimeoutMs ?? 30000,\n };\n\n // Set the polling interval\n this.setIntervalLength(config?.pollingInterval ?? DEFAULT_BALANCE_INTERVAL);\n }\n\n /**\n * Set the callback to receive balance updates during polling.\n *\n * @param callback - Function to call with balance results.\n */\n setOnBalanceUpdate(callback: OnBalanceUpdateCallback): void {\n this.#onBalanceUpdate = callback;\n }\n\n /**\n * Execute a poll cycle (required by base class).\n * Fetches balances and calls the update callback.\n *\n * @param input - The polling input.\n */\n async _executePoll(input: BalancePollingInput): Promise<void> {\n const result = await this.#fetchBalances(\n input.chainId,\n input.accountId,\n input.accountAddress,\n );\n\n if (this.#onBalanceUpdate && result.balances.length > 0) {\n await this.#onBalanceUpdate(result);\n }\n }\n\n /**\n * Return asset fetch entries tracked in state for the given account and\n * chain. Both native (`slip44:`) and ERC-20 (`erc20:`) entries are included.\n *\n * @param chainId - Hex chain ID (e.g. \"0x1\").\n * @param accountId - Account UUID.\n * @returns Array of asset fetch entries from state for the requested chain.\n */\n #getAssetsToFetch(chainId: ChainId, accountId: AccountId): AssetFetchEntry[] {\n const state = this.#messenger.call('AssetsController:getState');\n\n const accountBalances = state?.assetsBalance?.[accountId];\n if (!accountBalances) {\n return [];\n }\n\n // Convert hex chainId to decimal for CAIP-2 matching\n // This is safe because we are filtring with an accountId that is for evm balances only\n const chainIdDecimal = parseInt(chainId, 16).toString();\n\n const assetsToFetch = new Map<string, AssetFetchEntry>();\n\n for (const assetId of Object.keys(accountBalances) as CaipAssetType[]) {\n const {\n chain: { reference: chainReference },\n assetNamespace,\n assetReference,\n } = parseCaipAssetType(assetId);\n\n if (chainReference === chainIdDecimal) {\n const assetIdLowerCase = assetId.toLowerCase();\n if (assetsToFetch.has(assetIdLowerCase)) {\n continue;\n }\n\n const isNative = assetNamespace === 'slip44';\n const tokenAddress = isNative\n ? ZERO_ADDRESS\n : (assetReference.toLowerCase() as Address);\n\n assetsToFetch.set(assetIdLowerCase, {\n assetId,\n address: tokenAddress,\n });\n }\n }\n\n return Array.from(assetsToFetch.values());\n }\n\n /**\n * Fetch balances for assets already tracked in state for the given\n * account and chain.\n *\n * @param chainId - Hex chain ID.\n * @param accountId - Account UUID.\n * @param accountAddress - On-chain address of the account.\n * @returns Balance fetch result.\n */\n async #fetchBalances(\n chainId: ChainId,\n accountId: AccountId,\n accountAddress: Address,\n ): Promise<BalanceFetchResult> {\n const assets = this.#getAssetsToFetch(chainId, accountId);\n\n return this.fetchBalancesForAssets(\n chainId,\n accountId,\n accountAddress,\n assets,\n );\n }\n\n /**\n * Fetch balances for the given assets via multicall.\n *\n * Each entry bundles a CAIP-19 asset ID with its on-chain address and\n * optional decimals.\n *\n * @param chainId - Hex chain ID.\n * @param accountId - Account UUID.\n * @param accountAddress - On-chain address of the account.\n * @param assets - Asset fetch entries to fetch balances for.\n * @returns Balance fetch result.\n */\n async fetchBalancesForAssets(\n chainId: ChainId,\n accountId: AccountId,\n accountAddress: Address,\n assets: AssetFetchEntry[],\n ): Promise<BalanceFetchResult> {\n const timestamp = Date.now();\n\n // Build a single map keyed by lowercase address that holds all info\n // needed to match multicall responses back to their original entries.\n const balanceRequests: BalanceOfRequest[] = [];\n const entryByAddress = new Map<string, AssetFetchEntry>();\n\n for (const entry of assets) {\n const lowerAddress = entry.address.toLowerCase();\n if (entryByAddress.has(lowerAddress)) {\n continue; // deduplicate\n }\n\n entryByAddress.set(lowerAddress, entry);\n balanceRequests.push({ tokenAddress: entry.address, accountAddress });\n }\n\n if (balanceRequests.length === 0) {\n return {\n chainId,\n accountId,\n accountAddress,\n balances: [],\n failedAddresses: [],\n timestamp,\n };\n }\n\n type FetchAccumulator = {\n balances: AssetBalance[];\n failedAddresses: Address[];\n };\n\n const result = await reduceInBatchesSerially<\n BalanceOfRequest,\n FetchAccumulator\n >({\n values: balanceRequests,\n batchSize: this.#config.defaultBatchSize,\n initialResult: {\n balances: [],\n failedAddresses: [],\n },\n eachBatch: async (workingResult, batch) => {\n const responses = await this.#multicallClient.batchBalanceOf(\n chainId,\n batch,\n );\n\n return this.#processBalanceResponses(\n responses,\n workingResult as FetchAccumulator,\n chainId,\n accountId,\n timestamp,\n entryByAddress,\n );\n },\n });\n\n return {\n chainId,\n accountId,\n accountAddress,\n ...result,\n timestamp,\n };\n }\n\n #processBalanceResponses(\n responses: BalanceOfResponse[],\n accumulator: {\n balances: AssetBalance[];\n failedAddresses: Address[];\n },\n chainId: ChainId,\n accountId: AccountId,\n timestamp: number,\n entryByAddress: Map<string, AssetFetchEntry>,\n ): {\n balances: AssetBalance[];\n failedAddresses: Address[];\n } {\n const { balances, failedAddresses } = accumulator;\n\n for (const response of responses) {\n if (!response.success) {\n failedAddresses.push(response.tokenAddress);\n continue;\n }\n\n const lowerAddress = response.tokenAddress.toLowerCase();\n const entry = entryByAddress.get(lowerAddress);\n if (!entry) {\n continue;\n }\n\n const balance = response.balance ?? '0';\n const isNative = lowerAddress === ZERO_ADDRESS.toLowerCase();\n\n let decimals: number | undefined;\n let formattedBalance: string;\n if (isNative) {\n decimals = 18;\n formattedBalance = this.#formatBalance(balance, decimals);\n } else if (entry.decimals === undefined) {\n formattedBalance = balance;\n } else {\n decimals = entry.decimals;\n formattedBalance = this.#formatBalance(balance, decimals);\n }\n\n const balanceEntry: AssetBalance = {\n assetId: entry.assetId,\n accountId,\n chainId,\n balance,\n formattedBalance,\n timestamp,\n };\n if (typeof decimals === 'number') {\n balanceEntry.decimals = decimals;\n }\n balances.push(balanceEntry);\n }\n\n return { balances, failedAddresses };\n }\n\n #formatBalance(rawBalance: string, decimals: number): string {\n if (rawBalance === '0') {\n return '0';\n }\n\n try {\n const balanceBigInt = BigInt(rawBalance);\n const divisor = BigInt(10 ** decimals);\n\n const integerPart = balanceBigInt / divisor;\n const remainder = balanceBigInt % divisor;\n const fractionalStr = remainder.toString().padStart(decimals, '0');\n const trimmedFractional = fractionalStr.replace(/0+$/u, '');\n\n if (trimmedFractional === '') {\n return integerPart.toString();\n }\n\n return `${integerPart}.${trimmedFractional}`;\n } catch {\n return rawBalance;\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"BalanceFetcher.cjs","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/BalanceFetcher.ts"],"names":[],"mappings":"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","sourcesContent":["import { StaticIntervalPollingControllerOnly } from '@metamask/polling-controller';\nimport { parseCaipAssetType } from '@metamask/utils';\n\nimport { ZERO_ADDRESS } from '../../../utils/constants';\nimport { isNativeAsset } from '../../../utils/isNativeAsset';\nimport type { MulticallClient } from '../clients';\nimport type {\n AccountId,\n Address,\n AssetBalance,\n AssetFetchEntry,\n AssetsBalanceState,\n BalanceFetchResult,\n BalanceOfRequest,\n BalanceOfResponse,\n CaipAssetType,\n ChainId,\n} from '../types';\nimport { reduceInBatchesSerially } from '../utils';\n\nconst DEFAULT_BALANCE_INTERVAL = 30_000; // 30 seconds\n\n/**\n * Minimal messenger interface for BalanceFetcher.\n */\nexport type BalanceFetcherMessenger = {\n call: (action: 'AssetsController:getState') => AssetsBalanceState;\n};\n\nexport type BalanceFetcherConfig = {\n defaultBatchSize?: number;\n defaultTimeoutMs?: number;\n /** Polling interval in ms (default: 30s) */\n pollingInterval?: number;\n};\n\n/**\n * Polling input for BalanceFetcher - identifies what to poll for.\n */\nexport type BalancePollingInput = {\n /** Chain ID (hex format) */\n chainId: ChainId;\n /** Account ID */\n accountId: AccountId;\n /** Account address */\n accountAddress: Address;\n};\n\n/**\n * Callback type for balance updates.\n */\nexport type OnBalanceUpdateCallback = (\n result: BalanceFetchResult,\n) => void | Promise<void>;\n\n/**\n * BalanceFetcher - Fetches token balances via multicall.\n * Extends StaticIntervalPollingControllerOnly for built-in polling support.\n *\n * Callers provide CAIP-19 asset IDs; the fetcher extracts on-chain addresses\n * (or uses the zero address for native assets) and maps multicall responses\n * back to the original asset IDs. This ensures the returned balance entries\n * always carry the correct identifier regardless of chain.\n */\nexport class BalanceFetcher extends StaticIntervalPollingControllerOnly<BalancePollingInput>() {\n readonly #multicallClient: MulticallClient;\n\n readonly #messenger: BalanceFetcherMessenger;\n\n readonly #config: Required<Omit<BalanceFetcherConfig, 'pollingInterval'>>;\n\n #onBalanceUpdate: OnBalanceUpdateCallback | undefined;\n\n constructor(\n multicallClient: MulticallClient,\n messenger: BalanceFetcherMessenger,\n config?: BalanceFetcherConfig,\n ) {\n super();\n this.#multicallClient = multicallClient;\n this.#messenger = messenger;\n this.#config = {\n defaultBatchSize: config?.defaultBatchSize ?? 300,\n defaultTimeoutMs: config?.defaultTimeoutMs ?? 30000,\n };\n\n // Set the polling interval\n this.setIntervalLength(config?.pollingInterval ?? DEFAULT_BALANCE_INTERVAL);\n }\n\n /**\n * Set the callback to receive balance updates during polling.\n *\n * @param callback - Function to call with balance results.\n */\n setOnBalanceUpdate(callback: OnBalanceUpdateCallback): void {\n this.#onBalanceUpdate = callback;\n }\n\n /**\n * Execute a poll cycle (required by base class).\n * Fetches balances and calls the update callback.\n *\n * @param input - The polling input.\n */\n async _executePoll(input: BalancePollingInput): Promise<void> {\n const result = await this.#fetchBalances(\n input.chainId,\n input.accountId,\n input.accountAddress,\n );\n\n if (this.#onBalanceUpdate && result.balances.length > 0) {\n await this.#onBalanceUpdate(result);\n }\n }\n\n /**\n * Return asset fetch entries tracked in state for the given account and\n * chain. Both native (`slip44:`) and ERC-20 (`erc20:`) entries are included.\n *\n * @param chainId - Hex chain ID (e.g. \"0x1\").\n * @param accountId - Account UUID.\n * @returns Array of asset fetch entries from state for the requested chain.\n */\n #getAssetsToFetch(chainId: ChainId, accountId: AccountId): AssetFetchEntry[] {\n const state = this.#messenger.call('AssetsController:getState');\n\n const accountBalances = state?.assetsBalance?.[accountId];\n if (!accountBalances) {\n return [];\n }\n\n // Convert hex chainId to decimal for CAIP-2 matching\n // This is safe because we are filtring with an accountId that is for evm balances only\n const chainIdDecimal = parseInt(chainId, 16).toString();\n\n const assetsToFetch = new Map<string, AssetFetchEntry>();\n\n for (const assetId of Object.keys(accountBalances) as CaipAssetType[]) {\n const {\n chain: { reference: chainReference },\n assetReference,\n } = parseCaipAssetType(assetId);\n\n if (chainReference === chainIdDecimal) {\n const assetIdLowerCase = assetId.toLowerCase();\n if (assetsToFetch.has(assetIdLowerCase)) {\n continue;\n }\n\n const isNative = isNativeAsset(assetId);\n const tokenAddress = isNative\n ? ZERO_ADDRESS // BalanceFetcher requires the use of zero address even for chains in which the native token has a non-zero address\n : (assetReference.toLowerCase() as Address);\n\n assetsToFetch.set(assetIdLowerCase, {\n assetId,\n address: tokenAddress,\n });\n }\n }\n\n return Array.from(assetsToFetch.values());\n }\n\n /**\n * Fetch balances for assets already tracked in state for the given\n * account and chain.\n *\n * @param chainId - Hex chain ID.\n * @param accountId - Account UUID.\n * @param accountAddress - On-chain address of the account.\n * @returns Balance fetch result.\n */\n async #fetchBalances(\n chainId: ChainId,\n accountId: AccountId,\n accountAddress: Address,\n ): Promise<BalanceFetchResult> {\n const assets = this.#getAssetsToFetch(chainId, accountId);\n\n return this.fetchBalancesForAssets(\n chainId,\n accountId,\n accountAddress,\n assets,\n );\n }\n\n /**\n * Fetch balances for the given assets via multicall.\n *\n * Each entry bundles a CAIP-19 asset ID with its on-chain address and\n * optional decimals.\n *\n * @param chainId - Hex chain ID.\n * @param accountId - Account UUID.\n * @param accountAddress - On-chain address of the account.\n * @param assets - Asset fetch entries to fetch balances for.\n * @returns Balance fetch result.\n */\n async fetchBalancesForAssets(\n chainId: ChainId,\n accountId: AccountId,\n accountAddress: Address,\n assets: AssetFetchEntry[],\n ): Promise<BalanceFetchResult> {\n const timestamp = Date.now();\n\n // Build a single map keyed by lowercase address that holds all info\n // needed to match multicall responses back to their original entries.\n const balanceRequests: BalanceOfRequest[] = [];\n const entryByAddress = new Map<string, AssetFetchEntry>();\n\n for (const entry of assets) {\n const lowerAddress = entry.address.toLowerCase();\n if (entryByAddress.has(lowerAddress)) {\n continue; // deduplicate\n }\n\n entryByAddress.set(lowerAddress, entry);\n balanceRequests.push({ tokenAddress: entry.address, accountAddress });\n }\n\n if (balanceRequests.length === 0) {\n return {\n chainId,\n accountId,\n accountAddress,\n balances: [],\n failedAddresses: [],\n timestamp,\n };\n }\n\n type FetchAccumulator = {\n balances: AssetBalance[];\n failedAddresses: Address[];\n };\n\n const result = await reduceInBatchesSerially<\n BalanceOfRequest,\n FetchAccumulator\n >({\n values: balanceRequests,\n batchSize: this.#config.defaultBatchSize,\n initialResult: {\n balances: [],\n failedAddresses: [],\n },\n eachBatch: async (workingResult, batch) => {\n const responses = await this.#multicallClient.batchBalanceOf(\n chainId,\n batch,\n );\n\n return this.#processBalanceResponses(\n responses,\n workingResult as FetchAccumulator,\n chainId,\n accountId,\n timestamp,\n entryByAddress,\n );\n },\n });\n\n return {\n chainId,\n accountId,\n accountAddress,\n ...result,\n timestamp,\n };\n }\n\n #processBalanceResponses(\n responses: BalanceOfResponse[],\n accumulator: {\n balances: AssetBalance[];\n failedAddresses: Address[];\n },\n chainId: ChainId,\n accountId: AccountId,\n timestamp: number,\n entryByAddress: Map<string, AssetFetchEntry>,\n ): {\n balances: AssetBalance[];\n failedAddresses: Address[];\n } {\n const { balances, failedAddresses } = accumulator;\n\n for (const response of responses) {\n if (!response.success) {\n failedAddresses.push(response.tokenAddress);\n continue;\n }\n\n const lowerAddress = response.tokenAddress.toLowerCase();\n const entry = entryByAddress.get(lowerAddress);\n if (!entry) {\n continue;\n }\n\n const balance = response.balance ?? '0';\n const isNative = lowerAddress === ZERO_ADDRESS.toLowerCase();\n\n let decimals: number | undefined;\n let formattedBalance: string;\n if (isNative) {\n decimals = 18;\n formattedBalance = this.#formatBalance(balance, decimals);\n } else if (entry.decimals === undefined) {\n formattedBalance = balance;\n } else {\n decimals = entry.decimals;\n formattedBalance = this.#formatBalance(balance, decimals);\n }\n\n const balanceEntry: AssetBalance = {\n assetId: entry.assetId,\n accountId,\n chainId,\n balance,\n formattedBalance,\n timestamp,\n };\n if (typeof decimals === 'number') {\n balanceEntry.decimals = decimals;\n }\n balances.push(balanceEntry);\n }\n\n return { balances, failedAddresses };\n }\n\n #formatBalance(rawBalance: string, decimals: number): string {\n if (rawBalance === '0') {\n return '0';\n }\n\n try {\n const balanceBigInt = BigInt(rawBalance);\n const divisor = BigInt(10 ** decimals);\n\n const integerPart = balanceBigInt / divisor;\n const remainder = balanceBigInt % divisor;\n const fractionalStr = remainder.toString().padStart(decimals, '0');\n const trimmedFractional = fractionalStr.replace(/0+$/u, '');\n\n if (trimmedFractional === '') {\n return integerPart.toString();\n }\n\n return `${integerPart}.${trimmedFractional}`;\n } catch {\n return rawBalance;\n }\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BalanceFetcher.d.cts","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/BalanceFetcher.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"BalanceFetcher.d.cts","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/BalanceFetcher.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,6BAAmB;AAClD,OAAO,KAAK,EACV,SAAS,EACT,OAAO,EAEP,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAIlB,OAAO,EACR,2BAAiB;AAKlB;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,IAAI,EAAE,CAAC,MAAM,EAAE,2BAA2B,KAAK,kBAAkB,CAAC;CACnE,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4CAA4C;IAC5C,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,4BAA4B;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,iBAAiB;IACjB,SAAS,EAAE,SAAS,CAAC;IACrB,sBAAsB;IACtB,cAAc,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG,CACpC,MAAM,EAAE,kBAAkB,KACvB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;;;;;;;;;;;;;;;;;;AAE1B;;;;;;;;GAQG;AACH,qBAAa,cAAe,SAAQ,mBAA0D;;gBAU1F,eAAe,EAAE,eAAe,EAChC,SAAS,EAAE,uBAAuB,EAClC,MAAM,CAAC,EAAE,oBAAoB;IAc/B;;;;OAIG;IACH,kBAAkB,CAAC,QAAQ,EAAE,uBAAuB,GAAG,IAAI;IAI3D;;;;;OAKG;IACG,YAAY,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAqF7D;;;;;;;;;;;OAWG;IACG,sBAAsB,CAC1B,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,OAAO,EACvB,MAAM,EAAE,eAAe,EAAE,GACxB,OAAO,CAAC,kBAAkB,CAAC;CAyJ/B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BalanceFetcher.d.mts","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/BalanceFetcher.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"BalanceFetcher.d.mts","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/BalanceFetcher.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,6BAAmB;AAClD,OAAO,KAAK,EACV,SAAS,EACT,OAAO,EAEP,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAIlB,OAAO,EACR,2BAAiB;AAKlB;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,IAAI,EAAE,CAAC,MAAM,EAAE,2BAA2B,KAAK,kBAAkB,CAAC;CACnE,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4CAA4C;IAC5C,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,4BAA4B;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,iBAAiB;IACjB,SAAS,EAAE,SAAS,CAAC;IACrB,sBAAsB;IACtB,cAAc,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG,CACpC,MAAM,EAAE,kBAAkB,KACvB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;;;;;;;;;;;;;;;;;;AAE1B;;;;;;;;GAQG;AACH,qBAAa,cAAe,SAAQ,mBAA0D;;gBAU1F,eAAe,EAAE,eAAe,EAChC,SAAS,EAAE,uBAAuB,EAClC,MAAM,CAAC,EAAE,oBAAoB;IAc/B;;;;OAIG;IACH,kBAAkB,CAAC,QAAQ,EAAE,uBAAuB,GAAG,IAAI;IAI3D;;;;;OAKG;IACG,YAAY,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAqF7D;;;;;;;;;;;OAWG;IACG,sBAAsB,CAC1B,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,OAAO,EACvB,MAAM,EAAE,eAAe,EAAE,GACxB,OAAO,CAAC,kBAAkB,CAAC;CAyJ/B"}
|
|
@@ -13,6 +13,7 @@ var _BalanceFetcher_instances, _BalanceFetcher_multicallClient, _BalanceFetcher_
|
|
|
13
13
|
import { StaticIntervalPollingControllerOnly } from "@metamask/polling-controller";
|
|
14
14
|
import { parseCaipAssetType } from "@metamask/utils";
|
|
15
15
|
import { ZERO_ADDRESS } from "../../../utils/constants.mjs";
|
|
16
|
+
import { isNativeAsset } from "../../../utils/isNativeAsset.mjs";
|
|
16
17
|
import { reduceInBatchesSerially } from "../utils/index.mjs";
|
|
17
18
|
const DEFAULT_BALANCE_INTERVAL = 30000; // 30 seconds
|
|
18
19
|
/**
|
|
@@ -129,15 +130,15 @@ _BalanceFetcher_multicallClient = new WeakMap(), _BalanceFetcher_messenger = new
|
|
|
129
130
|
const chainIdDecimal = parseInt(chainId, 16).toString();
|
|
130
131
|
const assetsToFetch = new Map();
|
|
131
132
|
for (const assetId of Object.keys(accountBalances)) {
|
|
132
|
-
const { chain: { reference: chainReference },
|
|
133
|
+
const { chain: { reference: chainReference }, assetReference, } = parseCaipAssetType(assetId);
|
|
133
134
|
if (chainReference === chainIdDecimal) {
|
|
134
135
|
const assetIdLowerCase = assetId.toLowerCase();
|
|
135
136
|
if (assetsToFetch.has(assetIdLowerCase)) {
|
|
136
137
|
continue;
|
|
137
138
|
}
|
|
138
|
-
const isNative =
|
|
139
|
+
const isNative = isNativeAsset(assetId);
|
|
139
140
|
const tokenAddress = isNative
|
|
140
|
-
? ZERO_ADDRESS
|
|
141
|
+
? ZERO_ADDRESS // BalanceFetcher requires the use of zero address even for chains in which the native token has a non-zero address
|
|
141
142
|
: assetReference.toLowerCase();
|
|
142
143
|
assetsToFetch.set(assetIdLowerCase, {
|
|
143
144
|
assetId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BalanceFetcher.mjs","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/BalanceFetcher.ts"],"names":[],"mappings":"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","sourcesContent":["import { StaticIntervalPollingControllerOnly } from '@metamask/polling-controller';\nimport { parseCaipAssetType } from '@metamask/utils';\n\nimport { ZERO_ADDRESS } from '../../../utils/constants';\nimport type { MulticallClient } from '../clients';\nimport type {\n AccountId,\n Address,\n AssetBalance,\n AssetFetchEntry,\n AssetsBalanceState,\n BalanceFetchResult,\n BalanceOfRequest,\n BalanceOfResponse,\n CaipAssetType,\n ChainId,\n} from '../types';\nimport { reduceInBatchesSerially } from '../utils';\n\nconst DEFAULT_BALANCE_INTERVAL = 30_000; // 30 seconds\n\n/**\n * Minimal messenger interface for BalanceFetcher.\n */\nexport type BalanceFetcherMessenger = {\n call: (action: 'AssetsController:getState') => AssetsBalanceState;\n};\n\nexport type BalanceFetcherConfig = {\n defaultBatchSize?: number;\n defaultTimeoutMs?: number;\n /** Polling interval in ms (default: 30s) */\n pollingInterval?: number;\n};\n\n/**\n * Polling input for BalanceFetcher - identifies what to poll for.\n */\nexport type BalancePollingInput = {\n /** Chain ID (hex format) */\n chainId: ChainId;\n /** Account ID */\n accountId: AccountId;\n /** Account address */\n accountAddress: Address;\n};\n\n/**\n * Callback type for balance updates.\n */\nexport type OnBalanceUpdateCallback = (\n result: BalanceFetchResult,\n) => void | Promise<void>;\n\n/**\n * BalanceFetcher - Fetches token balances via multicall.\n * Extends StaticIntervalPollingControllerOnly for built-in polling support.\n *\n * Callers provide CAIP-19 asset IDs; the fetcher extracts on-chain addresses\n * (or uses the zero address for native assets) and maps multicall responses\n * back to the original asset IDs. This ensures the returned balance entries\n * always carry the correct identifier regardless of chain.\n */\nexport class BalanceFetcher extends StaticIntervalPollingControllerOnly<BalancePollingInput>() {\n readonly #multicallClient: MulticallClient;\n\n readonly #messenger: BalanceFetcherMessenger;\n\n readonly #config: Required<Omit<BalanceFetcherConfig, 'pollingInterval'>>;\n\n #onBalanceUpdate: OnBalanceUpdateCallback | undefined;\n\n constructor(\n multicallClient: MulticallClient,\n messenger: BalanceFetcherMessenger,\n config?: BalanceFetcherConfig,\n ) {\n super();\n this.#multicallClient = multicallClient;\n this.#messenger = messenger;\n this.#config = {\n defaultBatchSize: config?.defaultBatchSize ?? 300,\n defaultTimeoutMs: config?.defaultTimeoutMs ?? 30000,\n };\n\n // Set the polling interval\n this.setIntervalLength(config?.pollingInterval ?? DEFAULT_BALANCE_INTERVAL);\n }\n\n /**\n * Set the callback to receive balance updates during polling.\n *\n * @param callback - Function to call with balance results.\n */\n setOnBalanceUpdate(callback: OnBalanceUpdateCallback): void {\n this.#onBalanceUpdate = callback;\n }\n\n /**\n * Execute a poll cycle (required by base class).\n * Fetches balances and calls the update callback.\n *\n * @param input - The polling input.\n */\n async _executePoll(input: BalancePollingInput): Promise<void> {\n const result = await this.#fetchBalances(\n input.chainId,\n input.accountId,\n input.accountAddress,\n );\n\n if (this.#onBalanceUpdate && result.balances.length > 0) {\n await this.#onBalanceUpdate(result);\n }\n }\n\n /**\n * Return asset fetch entries tracked in state for the given account and\n * chain. Both native (`slip44:`) and ERC-20 (`erc20:`) entries are included.\n *\n * @param chainId - Hex chain ID (e.g. \"0x1\").\n * @param accountId - Account UUID.\n * @returns Array of asset fetch entries from state for the requested chain.\n */\n #getAssetsToFetch(chainId: ChainId, accountId: AccountId): AssetFetchEntry[] {\n const state = this.#messenger.call('AssetsController:getState');\n\n const accountBalances = state?.assetsBalance?.[accountId];\n if (!accountBalances) {\n return [];\n }\n\n // Convert hex chainId to decimal for CAIP-2 matching\n // This is safe because we are filtring with an accountId that is for evm balances only\n const chainIdDecimal = parseInt(chainId, 16).toString();\n\n const assetsToFetch = new Map<string, AssetFetchEntry>();\n\n for (const assetId of Object.keys(accountBalances) as CaipAssetType[]) {\n const {\n chain: { reference: chainReference },\n assetNamespace,\n assetReference,\n } = parseCaipAssetType(assetId);\n\n if (chainReference === chainIdDecimal) {\n const assetIdLowerCase = assetId.toLowerCase();\n if (assetsToFetch.has(assetIdLowerCase)) {\n continue;\n }\n\n const isNative = assetNamespace === 'slip44';\n const tokenAddress = isNative\n ? ZERO_ADDRESS\n : (assetReference.toLowerCase() as Address);\n\n assetsToFetch.set(assetIdLowerCase, {\n assetId,\n address: tokenAddress,\n });\n }\n }\n\n return Array.from(assetsToFetch.values());\n }\n\n /**\n * Fetch balances for assets already tracked in state for the given\n * account and chain.\n *\n * @param chainId - Hex chain ID.\n * @param accountId - Account UUID.\n * @param accountAddress - On-chain address of the account.\n * @returns Balance fetch result.\n */\n async #fetchBalances(\n chainId: ChainId,\n accountId: AccountId,\n accountAddress: Address,\n ): Promise<BalanceFetchResult> {\n const assets = this.#getAssetsToFetch(chainId, accountId);\n\n return this.fetchBalancesForAssets(\n chainId,\n accountId,\n accountAddress,\n assets,\n );\n }\n\n /**\n * Fetch balances for the given assets via multicall.\n *\n * Each entry bundles a CAIP-19 asset ID with its on-chain address and\n * optional decimals.\n *\n * @param chainId - Hex chain ID.\n * @param accountId - Account UUID.\n * @param accountAddress - On-chain address of the account.\n * @param assets - Asset fetch entries to fetch balances for.\n * @returns Balance fetch result.\n */\n async fetchBalancesForAssets(\n chainId: ChainId,\n accountId: AccountId,\n accountAddress: Address,\n assets: AssetFetchEntry[],\n ): Promise<BalanceFetchResult> {\n const timestamp = Date.now();\n\n // Build a single map keyed by lowercase address that holds all info\n // needed to match multicall responses back to their original entries.\n const balanceRequests: BalanceOfRequest[] = [];\n const entryByAddress = new Map<string, AssetFetchEntry>();\n\n for (const entry of assets) {\n const lowerAddress = entry.address.toLowerCase();\n if (entryByAddress.has(lowerAddress)) {\n continue; // deduplicate\n }\n\n entryByAddress.set(lowerAddress, entry);\n balanceRequests.push({ tokenAddress: entry.address, accountAddress });\n }\n\n if (balanceRequests.length === 0) {\n return {\n chainId,\n accountId,\n accountAddress,\n balances: [],\n failedAddresses: [],\n timestamp,\n };\n }\n\n type FetchAccumulator = {\n balances: AssetBalance[];\n failedAddresses: Address[];\n };\n\n const result = await reduceInBatchesSerially<\n BalanceOfRequest,\n FetchAccumulator\n >({\n values: balanceRequests,\n batchSize: this.#config.defaultBatchSize,\n initialResult: {\n balances: [],\n failedAddresses: [],\n },\n eachBatch: async (workingResult, batch) => {\n const responses = await this.#multicallClient.batchBalanceOf(\n chainId,\n batch,\n );\n\n return this.#processBalanceResponses(\n responses,\n workingResult as FetchAccumulator,\n chainId,\n accountId,\n timestamp,\n entryByAddress,\n );\n },\n });\n\n return {\n chainId,\n accountId,\n accountAddress,\n ...result,\n timestamp,\n };\n }\n\n #processBalanceResponses(\n responses: BalanceOfResponse[],\n accumulator: {\n balances: AssetBalance[];\n failedAddresses: Address[];\n },\n chainId: ChainId,\n accountId: AccountId,\n timestamp: number,\n entryByAddress: Map<string, AssetFetchEntry>,\n ): {\n balances: AssetBalance[];\n failedAddresses: Address[];\n } {\n const { balances, failedAddresses } = accumulator;\n\n for (const response of responses) {\n if (!response.success) {\n failedAddresses.push(response.tokenAddress);\n continue;\n }\n\n const lowerAddress = response.tokenAddress.toLowerCase();\n const entry = entryByAddress.get(lowerAddress);\n if (!entry) {\n continue;\n }\n\n const balance = response.balance ?? '0';\n const isNative = lowerAddress === ZERO_ADDRESS.toLowerCase();\n\n let decimals: number | undefined;\n let formattedBalance: string;\n if (isNative) {\n decimals = 18;\n formattedBalance = this.#formatBalance(balance, decimals);\n } else if (entry.decimals === undefined) {\n formattedBalance = balance;\n } else {\n decimals = entry.decimals;\n formattedBalance = this.#formatBalance(balance, decimals);\n }\n\n const balanceEntry: AssetBalance = {\n assetId: entry.assetId,\n accountId,\n chainId,\n balance,\n formattedBalance,\n timestamp,\n };\n if (typeof decimals === 'number') {\n balanceEntry.decimals = decimals;\n }\n balances.push(balanceEntry);\n }\n\n return { balances, failedAddresses };\n }\n\n #formatBalance(rawBalance: string, decimals: number): string {\n if (rawBalance === '0') {\n return '0';\n }\n\n try {\n const balanceBigInt = BigInt(rawBalance);\n const divisor = BigInt(10 ** decimals);\n\n const integerPart = balanceBigInt / divisor;\n const remainder = balanceBigInt % divisor;\n const fractionalStr = remainder.toString().padStart(decimals, '0');\n const trimmedFractional = fractionalStr.replace(/0+$/u, '');\n\n if (trimmedFractional === '') {\n return integerPart.toString();\n }\n\n return `${integerPart}.${trimmedFractional}`;\n } catch {\n return rawBalance;\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"BalanceFetcher.mjs","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/BalanceFetcher.ts"],"names":[],"mappings":"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","sourcesContent":["import { StaticIntervalPollingControllerOnly } from '@metamask/polling-controller';\nimport { parseCaipAssetType } from '@metamask/utils';\n\nimport { ZERO_ADDRESS } from '../../../utils/constants';\nimport { isNativeAsset } from '../../../utils/isNativeAsset';\nimport type { MulticallClient } from '../clients';\nimport type {\n AccountId,\n Address,\n AssetBalance,\n AssetFetchEntry,\n AssetsBalanceState,\n BalanceFetchResult,\n BalanceOfRequest,\n BalanceOfResponse,\n CaipAssetType,\n ChainId,\n} from '../types';\nimport { reduceInBatchesSerially } from '../utils';\n\nconst DEFAULT_BALANCE_INTERVAL = 30_000; // 30 seconds\n\n/**\n * Minimal messenger interface for BalanceFetcher.\n */\nexport type BalanceFetcherMessenger = {\n call: (action: 'AssetsController:getState') => AssetsBalanceState;\n};\n\nexport type BalanceFetcherConfig = {\n defaultBatchSize?: number;\n defaultTimeoutMs?: number;\n /** Polling interval in ms (default: 30s) */\n pollingInterval?: number;\n};\n\n/**\n * Polling input for BalanceFetcher - identifies what to poll for.\n */\nexport type BalancePollingInput = {\n /** Chain ID (hex format) */\n chainId: ChainId;\n /** Account ID */\n accountId: AccountId;\n /** Account address */\n accountAddress: Address;\n};\n\n/**\n * Callback type for balance updates.\n */\nexport type OnBalanceUpdateCallback = (\n result: BalanceFetchResult,\n) => void | Promise<void>;\n\n/**\n * BalanceFetcher - Fetches token balances via multicall.\n * Extends StaticIntervalPollingControllerOnly for built-in polling support.\n *\n * Callers provide CAIP-19 asset IDs; the fetcher extracts on-chain addresses\n * (or uses the zero address for native assets) and maps multicall responses\n * back to the original asset IDs. This ensures the returned balance entries\n * always carry the correct identifier regardless of chain.\n */\nexport class BalanceFetcher extends StaticIntervalPollingControllerOnly<BalancePollingInput>() {\n readonly #multicallClient: MulticallClient;\n\n readonly #messenger: BalanceFetcherMessenger;\n\n readonly #config: Required<Omit<BalanceFetcherConfig, 'pollingInterval'>>;\n\n #onBalanceUpdate: OnBalanceUpdateCallback | undefined;\n\n constructor(\n multicallClient: MulticallClient,\n messenger: BalanceFetcherMessenger,\n config?: BalanceFetcherConfig,\n ) {\n super();\n this.#multicallClient = multicallClient;\n this.#messenger = messenger;\n this.#config = {\n defaultBatchSize: config?.defaultBatchSize ?? 300,\n defaultTimeoutMs: config?.defaultTimeoutMs ?? 30000,\n };\n\n // Set the polling interval\n this.setIntervalLength(config?.pollingInterval ?? DEFAULT_BALANCE_INTERVAL);\n }\n\n /**\n * Set the callback to receive balance updates during polling.\n *\n * @param callback - Function to call with balance results.\n */\n setOnBalanceUpdate(callback: OnBalanceUpdateCallback): void {\n this.#onBalanceUpdate = callback;\n }\n\n /**\n * Execute a poll cycle (required by base class).\n * Fetches balances and calls the update callback.\n *\n * @param input - The polling input.\n */\n async _executePoll(input: BalancePollingInput): Promise<void> {\n const result = await this.#fetchBalances(\n input.chainId,\n input.accountId,\n input.accountAddress,\n );\n\n if (this.#onBalanceUpdate && result.balances.length > 0) {\n await this.#onBalanceUpdate(result);\n }\n }\n\n /**\n * Return asset fetch entries tracked in state for the given account and\n * chain. Both native (`slip44:`) and ERC-20 (`erc20:`) entries are included.\n *\n * @param chainId - Hex chain ID (e.g. \"0x1\").\n * @param accountId - Account UUID.\n * @returns Array of asset fetch entries from state for the requested chain.\n */\n #getAssetsToFetch(chainId: ChainId, accountId: AccountId): AssetFetchEntry[] {\n const state = this.#messenger.call('AssetsController:getState');\n\n const accountBalances = state?.assetsBalance?.[accountId];\n if (!accountBalances) {\n return [];\n }\n\n // Convert hex chainId to decimal for CAIP-2 matching\n // This is safe because we are filtring with an accountId that is for evm balances only\n const chainIdDecimal = parseInt(chainId, 16).toString();\n\n const assetsToFetch = new Map<string, AssetFetchEntry>();\n\n for (const assetId of Object.keys(accountBalances) as CaipAssetType[]) {\n const {\n chain: { reference: chainReference },\n assetReference,\n } = parseCaipAssetType(assetId);\n\n if (chainReference === chainIdDecimal) {\n const assetIdLowerCase = assetId.toLowerCase();\n if (assetsToFetch.has(assetIdLowerCase)) {\n continue;\n }\n\n const isNative = isNativeAsset(assetId);\n const tokenAddress = isNative\n ? ZERO_ADDRESS // BalanceFetcher requires the use of zero address even for chains in which the native token has a non-zero address\n : (assetReference.toLowerCase() as Address);\n\n assetsToFetch.set(assetIdLowerCase, {\n assetId,\n address: tokenAddress,\n });\n }\n }\n\n return Array.from(assetsToFetch.values());\n }\n\n /**\n * Fetch balances for assets already tracked in state for the given\n * account and chain.\n *\n * @param chainId - Hex chain ID.\n * @param accountId - Account UUID.\n * @param accountAddress - On-chain address of the account.\n * @returns Balance fetch result.\n */\n async #fetchBalances(\n chainId: ChainId,\n accountId: AccountId,\n accountAddress: Address,\n ): Promise<BalanceFetchResult> {\n const assets = this.#getAssetsToFetch(chainId, accountId);\n\n return this.fetchBalancesForAssets(\n chainId,\n accountId,\n accountAddress,\n assets,\n );\n }\n\n /**\n * Fetch balances for the given assets via multicall.\n *\n * Each entry bundles a CAIP-19 asset ID with its on-chain address and\n * optional decimals.\n *\n * @param chainId - Hex chain ID.\n * @param accountId - Account UUID.\n * @param accountAddress - On-chain address of the account.\n * @param assets - Asset fetch entries to fetch balances for.\n * @returns Balance fetch result.\n */\n async fetchBalancesForAssets(\n chainId: ChainId,\n accountId: AccountId,\n accountAddress: Address,\n assets: AssetFetchEntry[],\n ): Promise<BalanceFetchResult> {\n const timestamp = Date.now();\n\n // Build a single map keyed by lowercase address that holds all info\n // needed to match multicall responses back to their original entries.\n const balanceRequests: BalanceOfRequest[] = [];\n const entryByAddress = new Map<string, AssetFetchEntry>();\n\n for (const entry of assets) {\n const lowerAddress = entry.address.toLowerCase();\n if (entryByAddress.has(lowerAddress)) {\n continue; // deduplicate\n }\n\n entryByAddress.set(lowerAddress, entry);\n balanceRequests.push({ tokenAddress: entry.address, accountAddress });\n }\n\n if (balanceRequests.length === 0) {\n return {\n chainId,\n accountId,\n accountAddress,\n balances: [],\n failedAddresses: [],\n timestamp,\n };\n }\n\n type FetchAccumulator = {\n balances: AssetBalance[];\n failedAddresses: Address[];\n };\n\n const result = await reduceInBatchesSerially<\n BalanceOfRequest,\n FetchAccumulator\n >({\n values: balanceRequests,\n batchSize: this.#config.defaultBatchSize,\n initialResult: {\n balances: [],\n failedAddresses: [],\n },\n eachBatch: async (workingResult, batch) => {\n const responses = await this.#multicallClient.batchBalanceOf(\n chainId,\n batch,\n );\n\n return this.#processBalanceResponses(\n responses,\n workingResult as FetchAccumulator,\n chainId,\n accountId,\n timestamp,\n entryByAddress,\n );\n },\n });\n\n return {\n chainId,\n accountId,\n accountAddress,\n ...result,\n timestamp,\n };\n }\n\n #processBalanceResponses(\n responses: BalanceOfResponse[],\n accumulator: {\n balances: AssetBalance[];\n failedAddresses: Address[];\n },\n chainId: ChainId,\n accountId: AccountId,\n timestamp: number,\n entryByAddress: Map<string, AssetFetchEntry>,\n ): {\n balances: AssetBalance[];\n failedAddresses: Address[];\n } {\n const { balances, failedAddresses } = accumulator;\n\n for (const response of responses) {\n if (!response.success) {\n failedAddresses.push(response.tokenAddress);\n continue;\n }\n\n const lowerAddress = response.tokenAddress.toLowerCase();\n const entry = entryByAddress.get(lowerAddress);\n if (!entry) {\n continue;\n }\n\n const balance = response.balance ?? '0';\n const isNative = lowerAddress === ZERO_ADDRESS.toLowerCase();\n\n let decimals: number | undefined;\n let formattedBalance: string;\n if (isNative) {\n decimals = 18;\n formattedBalance = this.#formatBalance(balance, decimals);\n } else if (entry.decimals === undefined) {\n formattedBalance = balance;\n } else {\n decimals = entry.decimals;\n formattedBalance = this.#formatBalance(balance, decimals);\n }\n\n const balanceEntry: AssetBalance = {\n assetId: entry.assetId,\n accountId,\n chainId,\n balance,\n formattedBalance,\n timestamp,\n };\n if (typeof decimals === 'number') {\n balanceEntry.decimals = decimals;\n }\n balances.push(balanceEntry);\n }\n\n return { balances, failedAddresses };\n }\n\n #formatBalance(rawBalance: string, decimals: number): string {\n if (rawBalance === '0') {\n return '0';\n }\n\n try {\n const balanceBigInt = BigInt(rawBalance);\n const divisor = BigInt(10 ** decimals);\n\n const integerPart = balanceBigInt / divisor;\n const remainder = balanceBigInt % divisor;\n const fractionalStr = remainder.toString().padStart(decimals, '0');\n const trimmedFractional = fractionalStr.replace(/0+$/u, '');\n\n if (trimmedFractional === '') {\n return integerPart.toString();\n }\n\n return `${integerPart}.${trimmedFractional}`;\n } catch {\n return rawBalance;\n }\n }\n}\n"]}
|
|
@@ -5,6 +5,7 @@ const util_1 = require("@ethereumjs/util");
|
|
|
5
5
|
const assets_controllers_1 = require("@metamask/assets-controllers");
|
|
6
6
|
const utils_1 = require("@metamask/utils");
|
|
7
7
|
const utils_2 = require("@metamask/utils");
|
|
8
|
+
const isNativeAsset_1 = require("./isNativeAsset.cjs");
|
|
8
9
|
/**
|
|
9
10
|
* Converts AssetsController state (assetsPrice, selectedCurrency) into the
|
|
10
11
|
* same format the bridge expects from MultichainAssetsRatesController,
|
|
@@ -32,6 +33,7 @@ function formatExchangeRatesForBridge(params) {
|
|
|
32
33
|
currentCurrency: selectedCurrency,
|
|
33
34
|
};
|
|
34
35
|
}
|
|
36
|
+
const knownNativeAssetIds = new Set(Object.values(nativeAssetIdentifiers).map((id) => id.toLowerCase()));
|
|
35
37
|
const fungibleAssetsPrice = Object.entries(assetsPrice).reduce((acc, [assetId, priceData]) => {
|
|
36
38
|
if (priceData.assetPriceType === 'fungible') {
|
|
37
39
|
acc[assetId] = priceData;
|
|
@@ -47,11 +49,12 @@ function formatExchangeRatesForBridge(params) {
|
|
|
47
49
|
const expirationOffsetInSeconds = 60;
|
|
48
50
|
const expirationTime = lastUpdatedInSeconds + expirationOffsetInSeconds;
|
|
49
51
|
try {
|
|
52
|
+
const isNative = (0, isNativeAsset_1.isNativeAsset)(assetId, knownNativeAssetIds);
|
|
50
53
|
const parsed = (0, utils_2.parseCaipAssetType)(assetId);
|
|
51
54
|
const chainIdParsed = (0, utils_2.parseCaipChainId)(parsed.chainId);
|
|
52
55
|
if (chainIdParsed.namespace === utils_1.KnownCaipNamespace.Eip155) {
|
|
53
56
|
const chainIdHex = (0, utils_1.numberToHex)(parseInt(chainIdParsed.reference, 10));
|
|
54
|
-
const nativeAssetId = nativeAssetIdentifiers[parsed.chainId];
|
|
57
|
+
const nativeAssetId = (isNative ? assetId : nativeAssetIdentifiers[parsed.chainId]);
|
|
55
58
|
const nativeCurrencySymbol = networkConfigurationsByChainId[chainIdHex]?.nativeCurrency;
|
|
56
59
|
const nativeAssetUsdPrice = nativeAssetId && fungibleAssetsPrice[nativeAssetId]?.usdPrice;
|
|
57
60
|
if (!nativeAssetId ||
|
|
@@ -62,12 +65,12 @@ function formatExchangeRatesForBridge(params) {
|
|
|
62
65
|
}
|
|
63
66
|
let tokenAddress;
|
|
64
67
|
if (parsed.assetNamespace === 'erc20') {
|
|
65
|
-
tokenAddress = (0, util_1.toChecksumAddress)(
|
|
68
|
+
tokenAddress = (0, util_1.toChecksumAddress)(parsed.assetReference);
|
|
66
69
|
}
|
|
67
|
-
else if (
|
|
68
|
-
tokenAddress =
|
|
70
|
+
else if (isNative) {
|
|
71
|
+
tokenAddress = (0, util_1.toChecksumAddress)((0, assets_controllers_1.getNativeTokenAddress)(chainIdHex));
|
|
69
72
|
}
|
|
70
|
-
if (tokenAddress
|
|
73
|
+
if (tokenAddress) {
|
|
71
74
|
const priceInNative = nativeAssetUsdPrice > 0 ? usdPrice / nativeAssetUsdPrice : usdPrice;
|
|
72
75
|
if (!marketData[chainIdHex]) {
|
|
73
76
|
marketData[chainIdHex] = {};
|
|
@@ -81,7 +84,7 @@ function formatExchangeRatesForBridge(params) {
|
|
|
81
84
|
tokenAddress,
|
|
82
85
|
};
|
|
83
86
|
}
|
|
84
|
-
if (
|
|
87
|
+
if (isNative) {
|
|
85
88
|
currencyRates[nativeCurrencySymbol] = {
|
|
86
89
|
conversionDate: lastUpdatedInSeconds,
|
|
87
90
|
conversionRate: price,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formatExchangeRatesForBridge.cjs","sourceRoot":"","sources":["../../src/utils/formatExchangeRatesForBridge.ts"],"names":[],"mappings":"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","sourcesContent":["import { toChecksumAddress } from '@ethereumjs/util';\nimport {\n CurrencyRateState,\n MAP_CAIP_CURRENCIES,\n MarketDataDetails,\n MultichainAssetsRatesControllerState,\n TokenRatesControllerState,\n} from '@metamask/assets-controllers';\nimport { Hex, KnownCaipNamespace, numberToHex } from '@metamask/utils';\nimport { parseCaipAssetType, parseCaipChainId } from '@metamask/utils';\n\nimport type { AssetPrice, FungibleAssetPrice, Caip19AssetId } from '../types';\n\n/**\n * Exchange rates in the format expected by the bridge controller:\n * conversionRates (MultichainAssetsRatesController) + currencyRates (CurrencyRateController)\n * + marketData (TokenRatesController) + currentCurrency.\n */\nexport type BridgeExchangeRatesFormat = {\n conversionRates: MultichainAssetsRatesControllerState['conversionRates'];\n currencyRates: CurrencyRateState['currencyRates'];\n marketData: TokenRatesControllerState['marketData'];\n currentCurrency: string;\n};\n\n/**\n * Converts AssetsController state (assetsPrice, selectedCurrency) into the\n * same format the bridge expects from MultichainAssetsRatesController,\n * CurrencyRateController, and TokenRatesController so the bridge can use\n * a single action when useAssetsControllerForRates is true.\n *\n * @param params - Conversion parameters.\n * @param params.assetsPrice - Map of CAIP-19 asset ID to price data (must include both `price` and `usdPrice`).\n * @param params.selectedCurrency - ISO 4217 currency code (e.g. 'usd').\n * @param params.nativeAssetIdentifiers - Optional map of CAIP-2 chain ID to native asset ID (e.g. from NetworkEnablementController state). When provided, used for EVM native lookups.\n * @param params.networkConfigurationsByChainId - Optional map of Hex chain ID to network config (e.g. from NetworkController state). Used to resolve native currency symbol via `nativeCurrency`; keys are Hex (e.g. '0x1').\n * @returns Bridge-compatible conversionRates, currencyRates, marketData, currentCurrency.\n */\nexport function formatExchangeRatesForBridge(params: {\n assetsPrice: Record<string, AssetPrice>;\n selectedCurrency: string;\n nativeAssetIdentifiers?: Record<string, string>;\n networkConfigurationsByChainId?: Record<string, { nativeCurrency?: string }>;\n}): BridgeExchangeRatesFormat {\n const {\n assetsPrice,\n selectedCurrency,\n nativeAssetIdentifiers = {},\n networkConfigurationsByChainId = {},\n } = params;\n const conversionRates: MultichainAssetsRatesControllerState['conversionRates'] =\n {};\n const currencyRates: CurrencyRateState['currencyRates'] = {};\n const marketData: TokenRatesControllerState['marketData'] = {};\n\n const currencyCaip = MAP_CAIP_CURRENCIES[selectedCurrency.toLowerCase()];\n if (!currencyCaip) {\n return {\n conversionRates: {},\n currencyRates: {},\n marketData: {},\n currentCurrency: selectedCurrency,\n };\n }\n\n const fungibleAssetsPrice = Object.entries(assetsPrice).reduce<\n Record<Caip19AssetId, FungibleAssetPrice>\n >((acc, [assetId, priceData]) => {\n if (priceData.assetPriceType === 'fungible') {\n acc[assetId as Caip19AssetId] = priceData;\n }\n return acc;\n }, {});\n\n for (const [assetId, priceData] of Object.entries(fungibleAssetsPrice)) {\n const { price, usdPrice, lastUpdated } = priceData;\n if (price < 0) {\n continue;\n }\n\n const lastUpdatedInSeconds = lastUpdated / 1000;\n const expirationOffsetInSeconds = 60;\n const expirationTime = lastUpdatedInSeconds + expirationOffsetInSeconds;\n\n try {\n const parsed = parseCaipAssetType(assetId as Caip19AssetId);\n const chainIdParsed = parseCaipChainId(parsed.chainId);\n\n if (chainIdParsed.namespace === KnownCaipNamespace.Eip155) {\n const chainIdHex = numberToHex(parseInt(chainIdParsed.reference, 10));\n\n const nativeAssetId = nativeAssetIdentifiers[parsed.chainId] as\n | Caip19AssetId\n | undefined;\n\n const nativeCurrencySymbol =\n networkConfigurationsByChainId[chainIdHex]?.nativeCurrency;\n\n const nativeAssetUsdPrice =\n nativeAssetId && fungibleAssetsPrice[nativeAssetId]?.usdPrice;\n\n if (\n !nativeAssetId ||\n !nativeCurrencySymbol ||\n nativeAssetUsdPrice === undefined\n ) {\n // If we do not have a native asset for that chain or a price for it, the asset needs to be skipped\n continue;\n }\n\n let tokenAddress: Hex | undefined;\n if (parsed.assetNamespace === 'erc20') {\n tokenAddress = toChecksumAddress(String(parsed.assetReference));\n } else if (parsed.assetNamespace === 'slip44') {\n tokenAddress = '0x0000000000000000000000000000000000000000';\n }\n\n if (tokenAddress && nativeAssetId) {\n const priceInNative =\n nativeAssetUsdPrice > 0 ? usdPrice / nativeAssetUsdPrice : usdPrice;\n if (!marketData[chainIdHex]) {\n marketData[chainIdHex] = {};\n }\n marketData[chainIdHex][tokenAddress] = {\n ...priceData,\n price: priceInNative,\n currency: nativeCurrencySymbol,\n assetId,\n chainId: chainIdHex,\n tokenAddress,\n } as MarketDataDetails;\n }\n\n if (parsed.assetNamespace === 'slip44' && nativeAssetId) {\n currencyRates[nativeCurrencySymbol] = {\n conversionDate: lastUpdatedInSeconds,\n conversionRate: price,\n usdConversionRate: usdPrice,\n };\n }\n } else {\n conversionRates[assetId as Caip19AssetId] = {\n rate: String(price),\n currency: currencyCaip,\n conversionTime: lastUpdatedInSeconds,\n expirationTime,\n marketData: {\n fungible: true,\n allTimeHigh: `${priceData.allTimeHigh}`,\n allTimeLow: `${priceData.allTimeLow}`,\n circulatingSupply: `${priceData.circulatingSupply}`,\n marketCap: `${priceData.marketCap}`,\n totalVolume: `${priceData.totalVolume}`,\n pricePercentChange: {\n PT1H: priceData.pricePercentChange1h as number,\n P1D: priceData.pricePercentChange1d as number,\n P7D: priceData.pricePercentChange7d as number,\n P14D: priceData.pricePercentChange14d as number,\n P30D: priceData.pricePercentChange30d as number,\n P200D: priceData.pricePercentChange200d as number,\n P1Y: priceData.pricePercentChange1y as number,\n },\n },\n } as MultichainAssetsRatesControllerState['conversionRates'][Caip19AssetId];\n }\n } catch {\n // Skip malformed asset IDs\n }\n }\n\n return {\n conversionRates,\n currencyRates,\n marketData,\n currentCurrency: selectedCurrency,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"formatExchangeRatesForBridge.cjs","sourceRoot":"","sources":["../../src/utils/formatExchangeRatesForBridge.ts"],"names":[],"mappings":"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","sourcesContent":["import { toChecksumAddress } from '@ethereumjs/util';\nimport {\n CurrencyRateState,\n MAP_CAIP_CURRENCIES,\n MarketDataDetails,\n MultichainAssetsRatesControllerState,\n TokenRatesControllerState,\n getNativeTokenAddress,\n} from '@metamask/assets-controllers';\nimport { Hex, KnownCaipNamespace, numberToHex } from '@metamask/utils';\nimport { parseCaipAssetType, parseCaipChainId } from '@metamask/utils';\n\nimport type { AssetPrice, FungibleAssetPrice, Caip19AssetId } from '../types';\nimport { isNativeAsset } from './isNativeAsset';\n\n/**\n * Exchange rates in the format expected by the bridge controller:\n * conversionRates (MultichainAssetsRatesController) + currencyRates (CurrencyRateController)\n * + marketData (TokenRatesController) + currentCurrency.\n */\nexport type BridgeExchangeRatesFormat = {\n conversionRates: MultichainAssetsRatesControllerState['conversionRates'];\n currencyRates: CurrencyRateState['currencyRates'];\n marketData: TokenRatesControllerState['marketData'];\n currentCurrency: string;\n};\n\n/**\n * Converts AssetsController state (assetsPrice, selectedCurrency) into the\n * same format the bridge expects from MultichainAssetsRatesController,\n * CurrencyRateController, and TokenRatesController so the bridge can use\n * a single action when useAssetsControllerForRates is true.\n *\n * @param params - Conversion parameters.\n * @param params.assetsPrice - Map of CAIP-19 asset ID to price data (must include both `price` and `usdPrice`).\n * @param params.selectedCurrency - ISO 4217 currency code (e.g. 'usd').\n * @param params.nativeAssetIdentifiers - Optional map of CAIP-2 chain ID to native asset ID (e.g. from NetworkEnablementController state). When provided, used for EVM native lookups.\n * @param params.networkConfigurationsByChainId - Optional map of Hex chain ID to network config (e.g. from NetworkController state). Used to resolve native currency symbol via `nativeCurrency`; keys are Hex (e.g. '0x1').\n * @returns Bridge-compatible conversionRates, currencyRates, marketData, currentCurrency.\n */\nexport function formatExchangeRatesForBridge(params: {\n assetsPrice: Record<string, AssetPrice>;\n selectedCurrency: string;\n nativeAssetIdentifiers?: Record<string, string>;\n networkConfigurationsByChainId?: Record<string, { nativeCurrency?: string }>;\n}): BridgeExchangeRatesFormat {\n const {\n assetsPrice,\n selectedCurrency,\n nativeAssetIdentifiers = {},\n networkConfigurationsByChainId = {},\n } = params;\n const conversionRates: MultichainAssetsRatesControllerState['conversionRates'] =\n {};\n const currencyRates: CurrencyRateState['currencyRates'] = {};\n const marketData: TokenRatesControllerState['marketData'] = {};\n\n const currencyCaip = MAP_CAIP_CURRENCIES[selectedCurrency.toLowerCase()];\n if (!currencyCaip) {\n return {\n conversionRates: {},\n currencyRates: {},\n marketData: {},\n currentCurrency: selectedCurrency,\n };\n }\n\n const knownNativeAssetIds = new Set(\n Object.values(nativeAssetIdentifiers).map((id) => id.toLowerCase()),\n );\n\n const fungibleAssetsPrice = Object.entries(assetsPrice).reduce<\n Record<Caip19AssetId, FungibleAssetPrice>\n >((acc, [assetId, priceData]) => {\n if (priceData.assetPriceType === 'fungible') {\n acc[assetId as Caip19AssetId] = priceData;\n }\n return acc;\n }, {});\n\n for (const [assetId, priceData] of Object.entries(fungibleAssetsPrice)) {\n const { price, usdPrice, lastUpdated } = priceData;\n if (price < 0) {\n continue;\n }\n\n const lastUpdatedInSeconds = lastUpdated / 1000;\n const expirationOffsetInSeconds = 60;\n const expirationTime = lastUpdatedInSeconds + expirationOffsetInSeconds;\n\n try {\n const isNative = isNativeAsset(\n assetId as Caip19AssetId,\n knownNativeAssetIds,\n );\n const parsed = parseCaipAssetType(assetId as Caip19AssetId);\n const chainIdParsed = parseCaipChainId(parsed.chainId);\n\n if (chainIdParsed.namespace === KnownCaipNamespace.Eip155) {\n const chainIdHex = numberToHex(parseInt(chainIdParsed.reference, 10));\n\n const nativeAssetId = (\n isNative ? assetId : nativeAssetIdentifiers[parsed.chainId]\n ) as Caip19AssetId | undefined;\n\n const nativeCurrencySymbol =\n networkConfigurationsByChainId[chainIdHex]?.nativeCurrency;\n\n const nativeAssetUsdPrice =\n nativeAssetId && fungibleAssetsPrice[nativeAssetId]?.usdPrice;\n\n if (\n !nativeAssetId ||\n !nativeCurrencySymbol ||\n nativeAssetUsdPrice === undefined\n ) {\n // If we do not have a native asset for that chain or a price for it, the asset needs to be skipped\n continue;\n }\n\n let tokenAddress: Hex | undefined;\n if (parsed.assetNamespace === 'erc20') {\n tokenAddress = toChecksumAddress(parsed.assetReference);\n } else if (isNative) {\n tokenAddress = toChecksumAddress(getNativeTokenAddress(chainIdHex));\n }\n\n if (tokenAddress) {\n const priceInNative =\n nativeAssetUsdPrice > 0 ? usdPrice / nativeAssetUsdPrice : usdPrice;\n if (!marketData[chainIdHex]) {\n marketData[chainIdHex] = {};\n }\n marketData[chainIdHex][tokenAddress] = {\n ...priceData,\n price: priceInNative,\n currency: nativeCurrencySymbol,\n assetId,\n chainId: chainIdHex,\n tokenAddress,\n } as MarketDataDetails;\n }\n\n if (isNative) {\n currencyRates[nativeCurrencySymbol] = {\n conversionDate: lastUpdatedInSeconds,\n conversionRate: price,\n usdConversionRate: usdPrice,\n };\n }\n } else {\n conversionRates[assetId as Caip19AssetId] = {\n rate: String(price),\n currency: currencyCaip,\n conversionTime: lastUpdatedInSeconds,\n expirationTime,\n marketData: {\n fungible: true,\n allTimeHigh: `${priceData.allTimeHigh}`,\n allTimeLow: `${priceData.allTimeLow}`,\n circulatingSupply: `${priceData.circulatingSupply}`,\n marketCap: `${priceData.marketCap}`,\n totalVolume: `${priceData.totalVolume}`,\n pricePercentChange: {\n PT1H: priceData.pricePercentChange1h as number,\n P1D: priceData.pricePercentChange1d as number,\n P7D: priceData.pricePercentChange7d as number,\n P14D: priceData.pricePercentChange14d as number,\n P30D: priceData.pricePercentChange30d as number,\n P200D: priceData.pricePercentChange200d as number,\n P1Y: priceData.pricePercentChange1y as number,\n },\n },\n } as MultichainAssetsRatesControllerState['conversionRates'][Caip19AssetId];\n }\n } catch {\n // Skip malformed asset IDs\n }\n }\n\n return {\n conversionRates,\n currencyRates,\n marketData,\n currentCurrency: selectedCurrency,\n };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formatExchangeRatesForBridge.d.cts","sourceRoot":"","sources":["../../src/utils/formatExchangeRatesForBridge.ts"],"names":[],"mappings":"AACA,OAAO,EACL,iBAAiB,EAGjB,oCAAoC,EACpC,yBAAyB,
|
|
1
|
+
{"version":3,"file":"formatExchangeRatesForBridge.d.cts","sourceRoot":"","sources":["../../src/utils/formatExchangeRatesForBridge.ts"],"names":[],"mappings":"AACA,OAAO,EACL,iBAAiB,EAGjB,oCAAoC,EACpC,yBAAyB,EAE1B,qCAAqC;AAItC,OAAO,KAAK,EAAE,UAAU,EAAqC,qBAAiB;AAG9E;;;;GAIG;AACH,MAAM,MAAM,yBAAyB,GAAG;IACtC,eAAe,EAAE,oCAAoC,CAAC,iBAAiB,CAAC,CAAC;IACzE,aAAa,EAAE,iBAAiB,CAAC,eAAe,CAAC,CAAC;IAClD,UAAU,EAAE,yBAAyB,CAAC,YAAY,CAAC,CAAC;IACpD,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,4BAA4B,CAAC,MAAM,EAAE;IACnD,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACxC,gBAAgB,EAAE,MAAM,CAAC;IACzB,sBAAsB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChD,8BAA8B,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC9E,GAAG,yBAAyB,CA6I5B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formatExchangeRatesForBridge.d.mts","sourceRoot":"","sources":["../../src/utils/formatExchangeRatesForBridge.ts"],"names":[],"mappings":"AACA,OAAO,EACL,iBAAiB,EAGjB,oCAAoC,EACpC,yBAAyB,
|
|
1
|
+
{"version":3,"file":"formatExchangeRatesForBridge.d.mts","sourceRoot":"","sources":["../../src/utils/formatExchangeRatesForBridge.ts"],"names":[],"mappings":"AACA,OAAO,EACL,iBAAiB,EAGjB,oCAAoC,EACpC,yBAAyB,EAE1B,qCAAqC;AAItC,OAAO,KAAK,EAAE,UAAU,EAAqC,qBAAiB;AAG9E;;;;GAIG;AACH,MAAM,MAAM,yBAAyB,GAAG;IACtC,eAAe,EAAE,oCAAoC,CAAC,iBAAiB,CAAC,CAAC;IACzE,aAAa,EAAE,iBAAiB,CAAC,eAAe,CAAC,CAAC;IAClD,UAAU,EAAE,yBAAyB,CAAC,YAAY,CAAC,CAAC;IACpD,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,4BAA4B,CAAC,MAAM,EAAE;IACnD,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACxC,gBAAgB,EAAE,MAAM,CAAC;IACzB,sBAAsB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChD,8BAA8B,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC9E,GAAG,yBAAyB,CA6I5B"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { toChecksumAddress } from "@ethereumjs/util";
|
|
2
|
-
import { MAP_CAIP_CURRENCIES } from "@metamask/assets-controllers";
|
|
2
|
+
import { MAP_CAIP_CURRENCIES, getNativeTokenAddress } from "@metamask/assets-controllers";
|
|
3
3
|
import { KnownCaipNamespace, numberToHex } from "@metamask/utils";
|
|
4
4
|
import { parseCaipAssetType, parseCaipChainId } from "@metamask/utils";
|
|
5
|
+
import { isNativeAsset } from "./isNativeAsset.mjs";
|
|
5
6
|
/**
|
|
6
7
|
* Converts AssetsController state (assetsPrice, selectedCurrency) into the
|
|
7
8
|
* same format the bridge expects from MultichainAssetsRatesController,
|
|
@@ -29,6 +30,7 @@ export function formatExchangeRatesForBridge(params) {
|
|
|
29
30
|
currentCurrency: selectedCurrency,
|
|
30
31
|
};
|
|
31
32
|
}
|
|
33
|
+
const knownNativeAssetIds = new Set(Object.values(nativeAssetIdentifiers).map((id) => id.toLowerCase()));
|
|
32
34
|
const fungibleAssetsPrice = Object.entries(assetsPrice).reduce((acc, [assetId, priceData]) => {
|
|
33
35
|
if (priceData.assetPriceType === 'fungible') {
|
|
34
36
|
acc[assetId] = priceData;
|
|
@@ -44,11 +46,12 @@ export function formatExchangeRatesForBridge(params) {
|
|
|
44
46
|
const expirationOffsetInSeconds = 60;
|
|
45
47
|
const expirationTime = lastUpdatedInSeconds + expirationOffsetInSeconds;
|
|
46
48
|
try {
|
|
49
|
+
const isNative = isNativeAsset(assetId, knownNativeAssetIds);
|
|
47
50
|
const parsed = parseCaipAssetType(assetId);
|
|
48
51
|
const chainIdParsed = parseCaipChainId(parsed.chainId);
|
|
49
52
|
if (chainIdParsed.namespace === KnownCaipNamespace.Eip155) {
|
|
50
53
|
const chainIdHex = numberToHex(parseInt(chainIdParsed.reference, 10));
|
|
51
|
-
const nativeAssetId = nativeAssetIdentifiers[parsed.chainId];
|
|
54
|
+
const nativeAssetId = (isNative ? assetId : nativeAssetIdentifiers[parsed.chainId]);
|
|
52
55
|
const nativeCurrencySymbol = networkConfigurationsByChainId[chainIdHex]?.nativeCurrency;
|
|
53
56
|
const nativeAssetUsdPrice = nativeAssetId && fungibleAssetsPrice[nativeAssetId]?.usdPrice;
|
|
54
57
|
if (!nativeAssetId ||
|
|
@@ -59,12 +62,12 @@ export function formatExchangeRatesForBridge(params) {
|
|
|
59
62
|
}
|
|
60
63
|
let tokenAddress;
|
|
61
64
|
if (parsed.assetNamespace === 'erc20') {
|
|
62
|
-
tokenAddress = toChecksumAddress(
|
|
65
|
+
tokenAddress = toChecksumAddress(parsed.assetReference);
|
|
63
66
|
}
|
|
64
|
-
else if (
|
|
65
|
-
tokenAddress =
|
|
67
|
+
else if (isNative) {
|
|
68
|
+
tokenAddress = toChecksumAddress(getNativeTokenAddress(chainIdHex));
|
|
66
69
|
}
|
|
67
|
-
if (tokenAddress
|
|
70
|
+
if (tokenAddress) {
|
|
68
71
|
const priceInNative = nativeAssetUsdPrice > 0 ? usdPrice / nativeAssetUsdPrice : usdPrice;
|
|
69
72
|
if (!marketData[chainIdHex]) {
|
|
70
73
|
marketData[chainIdHex] = {};
|
|
@@ -78,7 +81,7 @@ export function formatExchangeRatesForBridge(params) {
|
|
|
78
81
|
tokenAddress,
|
|
79
82
|
};
|
|
80
83
|
}
|
|
81
|
-
if (
|
|
84
|
+
if (isNative) {
|
|
82
85
|
currencyRates[nativeCurrencySymbol] = {
|
|
83
86
|
conversionDate: lastUpdatedInSeconds,
|
|
84
87
|
conversionRate: price,
|