@metamask-previews/assets-controller 16.1.0-preview-b1e33e9f1 → 16.1.0-preview-d2ea3cc
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +0 -28
- package/dist/AssetsController-method-action-types.d.ts +1 -5
- package/dist/AssetsController-method-action-types.d.ts.map +1 -1
- package/dist/AssetsController-method-action-types.js.map +1 -1
- package/dist/AssetsController.d.ts +40 -8
- package/dist/AssetsController.d.ts.map +1 -1
- package/dist/AssetsController.js +235 -570
- package/dist/AssetsController.js.map +1 -1
- package/dist/data-sources/AbstractDataSource.d.ts +6 -1
- package/dist/data-sources/AbstractDataSource.d.ts.map +1 -1
- package/dist/data-sources/AbstractDataSource.js.map +1 -1
- package/dist/data-sources/AccountsApiDataSource.d.ts +3 -17
- package/dist/data-sources/AccountsApiDataSource.d.ts.map +1 -1
- package/dist/data-sources/AccountsApiDataSource.js +125 -275
- package/dist/data-sources/AccountsApiDataSource.js.map +1 -1
- package/dist/data-sources/PriceDataSource.d.ts +6 -7
- package/dist/data-sources/PriceDataSource.d.ts.map +1 -1
- package/dist/data-sources/PriceDataSource.js +20 -15
- package/dist/data-sources/PriceDataSource.js.map +1 -1
- package/dist/data-sources/RpcDataSource.d.ts +1 -15
- package/dist/data-sources/RpcDataSource.d.ts.map +1 -1
- package/dist/data-sources/RpcDataSource.js +107 -398
- package/dist/data-sources/RpcDataSource.js.map +1 -1
- package/dist/data-sources/SnapDataSource.d.ts +0 -5
- package/dist/data-sources/SnapDataSource.d.ts.map +1 -1
- package/dist/data-sources/SnapDataSource.js +0 -114
- package/dist/data-sources/SnapDataSource.js.map +1 -1
- package/dist/data-sources/TokenDataSource.d.ts +1 -3
- package/dist/data-sources/TokenDataSource.d.ts.map +1 -1
- package/dist/data-sources/TokenDataSource.js +2 -4
- package/dist/data-sources/TokenDataSource.js.map +1 -1
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.ts +0 -8
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.ts.map +1 -1
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.js +2 -47
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/middlewares/CustomAssetGraduationMiddleware.d.ts +1 -3
- package/dist/middlewares/CustomAssetGraduationMiddleware.d.ts.map +1 -1
- package/dist/middlewares/CustomAssetGraduationMiddleware.js +1 -3
- package/dist/middlewares/CustomAssetGraduationMiddleware.js.map +1 -1
- package/dist/middlewares/DetectionMiddleware.d.ts +2 -10
- package/dist/middlewares/DetectionMiddleware.d.ts.map +1 -1
- package/dist/middlewares/DetectionMiddleware.js +23 -5
- package/dist/middlewares/DetectionMiddleware.js.map +1 -1
- package/dist/middlewares/ParallelMiddleware.d.ts.map +1 -1
- package/dist/middlewares/ParallelMiddleware.js +3 -0
- package/dist/middlewares/ParallelMiddleware.js.map +1 -1
- package/dist/middlewares/RpcFallbackMiddleware.d.ts +15 -16
- package/dist/middlewares/RpcFallbackMiddleware.d.ts.map +1 -1
- package/dist/middlewares/RpcFallbackMiddleware.js +107 -125
- package/dist/middlewares/RpcFallbackMiddleware.js.map +1 -1
- package/dist/middlewares/index.d.ts +0 -1
- package/dist/middlewares/index.d.ts.map +1 -1
- package/dist/middlewares/index.js.map +1 -1
- package/dist/migrations/healAssetsInfoMetadata.d.ts +5 -5
- package/dist/migrations/healAssetsInfoMetadata.d.ts.map +1 -1
- package/dist/migrations/healAssetsInfoMetadata.js.map +1 -1
- package/dist/pipeline/buildFastFetchSources.d.ts +0 -5
- package/dist/pipeline/buildFastFetchSources.d.ts.map +1 -1
- package/dist/pipeline/buildFastFetchSources.js +1 -7
- package/dist/pipeline/buildFastFetchSources.js.map +1 -1
- package/dist/pipeline/executeAssetsPipeline.d.ts +3 -1
- package/dist/pipeline/executeAssetsPipeline.d.ts.map +1 -1
- package/dist/pipeline/executeAssetsPipeline.js +2 -1
- package/dist/pipeline/executeAssetsPipeline.js.map +1 -1
- package/dist/selectors/balance.js +1 -1
- package/dist/selectors/balance.js.map +1 -1
- package/dist/types.d.ts +23 -29
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/dist/utils/assetVisibility.d.ts +0 -29
- package/dist/utils/assetVisibility.d.ts.map +0 -1
- package/dist/utils/assetVisibility.js +0 -80
- package/dist/utils/assetVisibility.js.map +0 -1
- package/dist/utils/filterFailedChainBalances.d.ts +0 -11
- package/dist/utils/filterFailedChainBalances.d.ts.map +0 -1
- package/dist/utils/filterFailedChainBalances.js +0 -22
- package/dist/utils/filterFailedChainBalances.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TokenDataSource.js","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.js';\nimport { projectLogger, createModuleLogger } from '../logger.js';\nimport { forDataTypes } from '../types.js';\nimport type {\n Caip19AssetId,\n AssetMetadata,\n AssetsControllerState,\n Middleware,\n FungibleAssetMetadata,\n DataResponse,\n} from '../types.js';\nimport { fetchWithTimeout } from '../utils/index.js';\nimport {\n isStakingContractAssetId,\n reduceInBatchesSerially,\n} from './evm-rpc-services/index.js';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst CONTROLLER_NAME = 'TokenDataSource';\nconst DEFAULT_FETCH_TIMEOUT_MS = 15_000;\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 * Fallback minimum aggregator occurrences for EVM ERC-20 spam filtering when\n * Token API `/v1/suggestedOccurrenceFloors` has no entry for the chain (or\n * the floors request fails). Non-EVM tokens are filtered via Blockaid instead.\n */\nconst DEFAULT_OCCURRENCE_FLOOR = 3;\n\n/** CAIP-19 `assetNamespace` segments used across filtering logic. */\nexport enum CaipAssetNamespace {\n Slip44 = 'slip44',\n Erc20 = 'erc20',\n Token = 'token',\n}\n\nconst MUSD_ADDRESS_LOWERCASE = '0xaca92e438df0b2401ff60da7e4337b687a2435da';\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 /** Returns the asset type ('native' | 'erc20' | 'spl') for a given CAIP-19 asset ID */\n getAssetType: (assetId: Caip19AssetId) => 'native' | 'erc20' | 'spl';\n /**\n * Timeout in ms for a single Tokens API call (default: 15000). When it\n * fires, the batch rejects so metadata enrichment proceeds without it.\n */\n fetchTimeoutMs?: number;\n /** Current AssetsController state. Used to skip already-known metadata and custom assets. */\n getAssetsState: () => AssetsControllerState;\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 * @param getAssetType - Returns the asset type for a given CAIP-19 asset ID.\n * @returns FungibleAssetMetadata for state storage.\n */\nfunction transformV3AssetResponseToMetadata(\n assetId: Caip19AssetId,\n assetData: V3AssetResponse,\n getAssetType: (id: Caip19AssetId) => 'native' | 'erc20' | 'spl',\n): AssetMetadata {\n const metadata: FungibleAssetMetadata = {\n type: getAssetType(assetId),\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 * Resolve the occurrence floor for an EVM asset from suggested floors keyed by\n * decimal chain ID (e.g. `{ \"1\": 3, \"143\": 1 }`).\n *\n * @param assetId - CAIP-19 asset ID.\n * @param floors - Map of decimal chain ID → suggested floor.\n * @returns Floor to apply, or {@link DEFAULT_OCCURRENCE_FLOOR} when missing.\n */\nfunction getOccurrenceFloorForAsset(\n assetId: string,\n floors: Record<string, number>,\n): number {\n try {\n const { chain } = parseCaipAssetType(assetId as CaipAssetType);\n return floors[chain.reference] ?? DEFAULT_OCCURRENCE_FLOOR;\n } catch {\n return DEFAULT_OCCURRENCE_FLOOR;\n }\n}\n\nfunction cleanResponseSpam(\n spamAssetIds: Set<string>,\n response: DataResponse,\n): void {\n const spamLowerIds = new Set([...spamAssetIds].map((id) => id.toLowerCase()));\n\n // Correctly clean assetsBalance by its own Ids\n if (response.assetsBalance) {\n for (const accountBalances of Object.values(response.assetsBalance)) {\n for (const assetId of Object.keys(accountBalances)) {\n if (spamLowerIds.has(assetId.toLowerCase())) {\n delete (accountBalances as Record<string, unknown>)[assetId];\n }\n }\n }\n }\n\n // Correctly clean assetsInfo by its own Ids\n if (response.assetsInfo) {\n for (const assetId of Object.keys(response.assetsInfo)) {\n if (spamLowerIds.has(assetId.toLowerCase())) {\n delete response.assetsInfo[assetId as Caip19AssetId];\n }\n }\n }\n\n // Correctly clean detectedAssets by its own Ids\n if (response.detectedAssets) {\n for (const [accountId, assetIds] of Object.entries(\n response.detectedAssets,\n )) {\n response.detectedAssets[accountId] = assetIds.filter(\n (id) => !spamLowerIds.has(id.toLowerCase()),\n );\n }\n }\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 * - Also checks `assetsBalance` entries missing metadata/images (heal path)\n * - Fetches metadata from Tokens API v3 for assets needing enrichment\n * - Filters EVM ERC-20 spam using per-chain floors from Token API\n * `/v1/suggestedOccurrenceFloors` (default floor 3) for newly detected\n * assets only; balance-only heals are enriched without spam-filtering\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 /** Returns the asset type for a given CAIP-19 asset ID */\n readonly #getAssetType: (\n assetId: Caip19AssetId,\n ) => 'native' | 'erc20' | 'spl';\n\n /** Shared controller messenger — used for `PhishingController:bulkScanTokens`. */\n readonly #messenger: AssetsControllerMessenger;\n\n readonly #fetchTimeoutMs: number;\n\n readonly #getAssetsState: () => AssetsControllerState;\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 this.#getAssetType = options.getAssetType;\n this.#fetchTimeoutMs = options.fetchTimeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;\n this.#getAssetsState = options.getAssetsState;\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 = await fetchWithTimeout(\n () => this.#apiClient.tokens.fetchTokenV2SupportedNetworks(),\n this.#fetchTimeoutMs,\n );\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 * Fetches per-chain suggested occurrence floors from Token API\n * (`GET /v1/suggestedOccurrenceFloors`). Caching is handled by\n * ApiPlatformClient. Fails open to an empty map so callers fall back to\n * {@link DEFAULT_OCCURRENCE_FLOOR}.\n *\n * @returns Map of decimal chain ID → suggested occurrence floor.\n */\n async #getSuggestedOccurrenceFloors(): Promise<Record<string, number>> {\n try {\n return await fetchWithTimeout(\n () => this.#apiClient.token.fetchV1SuggestedOccurrenceFloors(),\n this.#fetchTimeoutMs,\n );\n } catch (error) {\n log('Failed to fetch suggested occurrence floors', { error });\n return {};\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 * Middleware that runs BEFORE DetectionMiddleware for account-activity\n * (websocket) updates. New-to-state EVM ERC-20 balances are enriched with\n * their Token API occurrence counts first, and tokens below the per-chain\n * suggested occurrence floor are dropped from the response (balances and\n * stub metadata) so spam airdrops never reach detection, metadata\n * enrichment, pricing, or state. Custom assets and mUSD are exempt, and\n * assets unknown to the Token API are kept (fail open), mirroring\n * {@link TokenDataSource.assetsMiddleware} filtering semantics.\n *\n * @returns The middleware function for the assets pipeline.\n */\n get occurrenceFilterMiddleware(): Middleware {\n return forDataTypes(['balance'], async (ctx, next) => {\n const { response } = ctx;\n const {\n assetsBalance: stateBalances,\n assetsInfo: stateMetadata,\n customAssets,\n } = this.#getAssetsState();\n\n const customAssetIds = new Set(\n Object.values(customAssets ?? {})\n .flat()\n .map((id) => id.toLowerCase()),\n );\n\n // State keys are checksummed, but AccountActivity delivers lower-case\n // ERC-20 IDs, so every lookup below compares lower-cased IDs. Matching\n // case-sensitively would classify existing holdings as new and delete\n // the very balance update this pipeline pass is meant to persist.\n const knownMetadataIds = new Set(\n Object.keys(stateMetadata).map((id) => id.toLowerCase()),\n );\n\n // Candidates: EVM ERC-20s that are genuinely new (absent from state\n // balances and metadata) — the same assets DetectionMiddleware would\n // mark as newly detected right after this middleware.\n const candidateByLowerId = new Map<string, string>();\n for (const [accountId, accountBalances] of Object.entries(\n response.assetsBalance ?? {},\n )) {\n const knownBalanceIds = new Set(\n Object.keys(stateBalances[accountId] ?? {}).map((id) =>\n id.toLowerCase(),\n ),\n );\n for (const assetId of Object.keys(accountBalances)) {\n const caipAssetId = assetId as Caip19AssetId;\n const lowerId = assetId.toLowerCase();\n if (\n knownBalanceIds.has(lowerId) ||\n knownMetadataIds.has(lowerId) ||\n customAssetIds.has(lowerId) ||\n lowerId.includes(`/erc20:${MUSD_ADDRESS_LOWERCASE}`)\n ) {\n continue;\n }\n try {\n const { assetNamespace, chain } = parseCaipAssetType(caipAssetId);\n if (\n assetNamespace === CaipAssetNamespace.Erc20 &&\n chain.namespace === KnownCaipNamespace.Eip155\n ) {\n candidateByLowerId.set(lowerId, assetId);\n }\n } catch {\n // Unparseable IDs are left for downstream middleware to handle.\n }\n }\n }\n\n if (candidateByLowerId.size === 0) {\n return next(ctx);\n }\n\n try {\n const [occurrenceResponse, suggestedOccurrenceFloors] =\n await Promise.all([\n reduceInBatchesSerially<string, V3AssetResponse[]>({\n values: [...candidateByLowerId.values()],\n batchSize: TOKENS_API_BATCH_SIZE,\n eachBatch: async (workingResult, batch) => {\n const batchResponse = await fetchWithTimeout(\n () =>\n this.#apiClient.tokens.fetchV3Assets(batch, {\n includeOccurrences: true,\n }),\n this.#fetchTimeoutMs,\n );\n return [\n ...(workingResult as V3AssetResponse[]),\n ...batchResponse,\n ];\n },\n initialResult: [],\n }),\n this.#getSuggestedOccurrenceFloors(),\n ]);\n\n // Only assets the API knows can be judged; missing ones are kept.\n const spamAssetIds = new Set<string>();\n for (const assetData of occurrenceResponse) {\n const candidateId = candidateByLowerId.get(\n assetData.assetId.toLowerCase(),\n );\n if (\n candidateId !== undefined &&\n (assetData.occurrences ?? 0) <\n getOccurrenceFloorForAsset(candidateId, suggestedOccurrenceFloors)\n ) {\n spamAssetIds.add(candidateId);\n }\n }\n\n if (spamAssetIds.size > 0) {\n cleanResponseSpam(spamAssetIds, response);\n log('Filtered low-occurrence websocket assets', {\n assetIds: [...spamAssetIds],\n });\n }\n } catch (error) {\n // Fail open — keep all assets when occurrences cannot be fetched.\n log('Failed to fetch occurrences for websocket update', { error });\n }\n\n return next(ctx);\n });\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 and balances missing metadata\n * 3. Enriches the response with fetched metadata\n * 4. Calls next() at the end to continue the middleware chain\n *\n * Spam filtering (EVM occurrence floors / non-EVM Blockaid) applies only to\n * newly `detectedAssets`. Balance-only heals — assets already present in\n * `assetsBalance` but missing `assetsInfo`, which DetectionMiddleware skips —\n * are enriched without being filtered out of balances.\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 } =\n this.#getAssetsState();\n const assetIdsNeedingMetadata = new Set<string>();\n // Newly detected asset IDs (lowercase) — subject to spam filtering.\n const detectedAssetIds = new Set<string>();\n // Balance-only heals (in assetsBalance, not newly detected) — enrich\n // metadata but do not spam-filter / delete holdings.\n const balanceHealAssetIds = new Set<string>();\n\n // Custom assets are user-imported — exempt from spam filtering.\n // State stores asset IDs in their normalized (checksummed) form, but the\n // V3 Tokens API can return them lower-cased. Lowercase both sides so the\n // bypass is robust to address-case differences across data sources.\n const customAssetIds = new Set<string>(\n Object.values(customAssets ?? {})\n .flat()\n .map((id) => id.toLowerCase()),\n );\n\n // Always include native asset IDs from NetworkEnablementController\n const nativeAssetIdsList = this.#getNativeAssetIds();\n const nativeAssetIds = new Set(\n nativeAssetIdsList.map((id) => id.toLowerCase()),\n );\n for (const nativeAssetId of nativeAssetIdsList) {\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 detectedAssetIds.add(assetId.toLowerCase());\n assetIdsNeedingMetadata.add(assetId);\n }\n }\n }\n\n // Also fetch metadata for balances that are missing it\n if (response.assetsBalance) {\n for (const accountBalances of Object.values(response.assetsBalance)) {\n for (const assetId of Object.keys(\n accountBalances,\n ) as Caip19AssetId[]) {\n if (response.assetsInfo?.[assetId]?.image) {\n continue;\n }\n if (stateMetadata[assetId]?.image) {\n continue;\n }\n if (isStakingContractAssetId(assetId)) {\n continue;\n }\n assetIdsNeedingMetadata.add(assetId);\n // Only treat as a heal when DetectionMiddleware did not queue it\n // as newly detected — those remain subject to spam filtering.\n if (!detectedAssetIds.has(assetId.toLowerCase())) {\n balanceHealAssetIds.add(assetId.toLowerCase());\n }\n }\n }\n }\n\n if (assetIdsNeedingMetadata.size === 0) {\n return next(ctx);\n }\n\n // Filter asset IDs to only include supported networks; load per-chain\n // occurrence floors in parallel (Token API suggested floors).\n const [supportedNetworks, suggestedOccurrenceFloors] = await Promise.all([\n this.#getSupportedNetworks(),\n this.#getSuggestedOccurrenceFloors(),\n ]);\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 fetchWithTimeout(\n () => this.#apiClient.tokens.fetchV3Assets(batch, fetchOptions),\n this.#fetchTimeoutMs,\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 assets are 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 (nativeAssetIds.has(assetId.toLowerCase())) {\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 per-chain suggested occurrence floor (from Token API\n // `/v1/suggestedOccurrenceFloors`, default {@link DEFAULT_OCCURRENCE_FLOOR})\n // to suppress low-signal tokens. Tokens with no occurrence data\n // (undefined) are treated the same as zero occurrences and filtered out.\n // Custom assets (user-imported) bypass the occurrence filter — users\n // can import whatever they want and we must keep their metadata even\n // if the API has fewer aggregator hits than the floor.\n // Balance-only heals also bypass — see `balanceHealAssetIds` below.\n const allowedEvmIds = new Set(\n evmErc20Ids.filter(\n (id) =>\n customAssetIds.has(id.toLowerCase()) ||\n balanceHealAssetIds.has(id.toLowerCase()) ||\n (occurrencesByAssetId.get(id) ?? 0) >=\n getOccurrenceFloorForAsset(id, suggestedOccurrenceFloors) ||\n id.includes(`/erc20:${MUSD_ADDRESS_LOWERCASE}`),\n ),\n );\n\n // Non-EVM: Blockaid bulk scan.\n // Custom assets and balance-only heals bypass Blockaid filtering.\n const nonEvmToScan = nonEvmTokenIds.filter(\n (id) =>\n !customAssetIds.has(id.toLowerCase()) &&\n !balanceHealAssetIds.has(id.toLowerCase()),\n );\n const allowedNonEvmIds = new Set([\n ...nonEvmTokenIds.filter(\n (id) =>\n customAssetIds.has(id.toLowerCase()) ||\n balanceHealAssetIds.has(id.toLowerCase()),\n ),\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 this.#getAssetType,\n );\n }\n\n if (filteredOutAssets.size > 0) {\n cleanResponseSpam(filteredOutAssets, response);\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.js","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.js';\nimport { projectLogger, createModuleLogger } from '../logger.js';\nimport { forDataTypes } from '../types.js';\nimport type {\n Caip19AssetId,\n AssetMetadata,\n Middleware,\n FungibleAssetMetadata,\n DataResponse,\n} from '../types.js';\nimport { fetchWithTimeout } from '../utils/index.js';\nimport {\n isStakingContractAssetId,\n reduceInBatchesSerially,\n} from './evm-rpc-services/index.js';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst CONTROLLER_NAME = 'TokenDataSource';\nconst DEFAULT_FETCH_TIMEOUT_MS = 15_000;\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 * Fallback minimum aggregator occurrences for EVM ERC-20 spam filtering when\n * Token API `/v1/suggestedOccurrenceFloors` has no entry for the chain (or\n * the floors request fails). Non-EVM tokens are filtered via Blockaid instead.\n */\nconst DEFAULT_OCCURRENCE_FLOOR = 3;\n\n/** CAIP-19 `assetNamespace` segments used across filtering logic. */\nexport enum CaipAssetNamespace {\n Slip44 = 'slip44',\n Erc20 = 'erc20',\n Token = 'token',\n}\n\nconst MUSD_ADDRESS_LOWERCASE = '0xaca92e438df0b2401ff60da7e4337b687a2435da';\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 /** Returns the asset type ('native' | 'erc20' | 'spl') for a given CAIP-19 asset ID */\n getAssetType: (assetId: Caip19AssetId) => 'native' | 'erc20' | 'spl';\n /**\n * Timeout in ms for a single Tokens API call (default: 15000). When it\n * fires, the batch rejects so metadata enrichment proceeds without it.\n */\n fetchTimeoutMs?: number;\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 * @param getAssetType - Returns the asset type for a given CAIP-19 asset ID.\n * @returns FungibleAssetMetadata for state storage.\n */\nfunction transformV3AssetResponseToMetadata(\n assetId: Caip19AssetId,\n assetData: V3AssetResponse,\n getAssetType: (id: Caip19AssetId) => 'native' | 'erc20' | 'spl',\n): AssetMetadata {\n const metadata: FungibleAssetMetadata = {\n type: getAssetType(assetId),\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 * Resolve the occurrence floor for an EVM asset from suggested floors keyed by\n * decimal chain ID (e.g. `{ \"1\": 3, \"143\": 1 }`).\n *\n * @param assetId - CAIP-19 asset ID.\n * @param floors - Map of decimal chain ID → suggested floor.\n * @returns Floor to apply, or {@link DEFAULT_OCCURRENCE_FLOOR} when missing.\n */\nfunction getOccurrenceFloorForAsset(\n assetId: string,\n floors: Record<string, number>,\n): number {\n try {\n const { chain } = parseCaipAssetType(assetId as CaipAssetType);\n return floors[chain.reference] ?? DEFAULT_OCCURRENCE_FLOOR;\n } catch {\n return DEFAULT_OCCURRENCE_FLOOR;\n }\n}\n\nfunction cleanResponseSpam(\n spamAssetIds: Set<string>,\n response: DataResponse,\n): void {\n const spamLowerIds = new Set([...spamAssetIds].map((id) => id.toLowerCase()));\n\n // Correctly clean assetsBalance by its own Ids\n if (response.assetsBalance) {\n for (const accountBalances of Object.values(response.assetsBalance)) {\n for (const assetId of Object.keys(accountBalances)) {\n if (spamLowerIds.has(assetId.toLowerCase())) {\n delete (accountBalances as Record<string, unknown>)[assetId];\n }\n }\n }\n }\n\n // Correctly clean assetsInfo by its own Ids\n if (response.assetsInfo) {\n for (const assetId of Object.keys(response.assetsInfo)) {\n if (spamLowerIds.has(assetId.toLowerCase())) {\n delete response.assetsInfo[assetId as Caip19AssetId];\n }\n }\n }\n\n // Correctly clean detectedAssets by its own Ids\n if (response.detectedAssets) {\n for (const [accountId, assetIds] of Object.entries(\n response.detectedAssets,\n )) {\n response.detectedAssets[accountId] = assetIds.filter(\n (id) => !spamLowerIds.has(id.toLowerCase()),\n );\n }\n }\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 * - Also checks `assetsBalance` entries missing metadata/images (heal path)\n * - Fetches metadata from Tokens API v3 for assets needing enrichment\n * - Filters EVM ERC-20 spam using per-chain floors from Token API\n * `/v1/suggestedOccurrenceFloors` (default floor 3) for newly detected\n * assets only; balance-only heals are enriched without spam-filtering\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 /** Returns the asset type for a given CAIP-19 asset ID */\n readonly #getAssetType: (\n assetId: Caip19AssetId,\n ) => 'native' | 'erc20' | 'spl';\n\n /** Shared controller messenger — used for `PhishingController:bulkScanTokens`. */\n readonly #messenger: AssetsControllerMessenger;\n\n readonly #fetchTimeoutMs: number;\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 this.#getAssetType = options.getAssetType;\n this.#fetchTimeoutMs = options.fetchTimeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;\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 = await fetchWithTimeout(\n () => this.#apiClient.tokens.fetchTokenV2SupportedNetworks(),\n this.#fetchTimeoutMs,\n );\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 * Fetches per-chain suggested occurrence floors from Token API\n * (`GET /v1/suggestedOccurrenceFloors`). Caching is handled by\n * ApiPlatformClient. Fails open to an empty map so callers fall back to\n * {@link DEFAULT_OCCURRENCE_FLOOR}.\n *\n * @returns Map of decimal chain ID → suggested occurrence floor.\n */\n async #getSuggestedOccurrenceFloors(): Promise<Record<string, number>> {\n try {\n return await fetchWithTimeout(\n () => this.#apiClient.token.fetchV1SuggestedOccurrenceFloors(),\n this.#fetchTimeoutMs,\n );\n } catch (error) {\n log('Failed to fetch suggested occurrence floors', { error });\n return {};\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 * Middleware that runs BEFORE DetectionMiddleware for account-activity\n * (websocket) updates. New-to-state EVM ERC-20 balances are enriched with\n * their Token API occurrence counts first, and tokens below the per-chain\n * suggested occurrence floor are dropped from the response (balances and\n * stub metadata) so spam airdrops never reach detection, metadata\n * enrichment, pricing, or state. Custom assets and mUSD are exempt, and\n * assets unknown to the Token API are kept (fail open), mirroring\n * {@link TokenDataSource.assetsMiddleware} filtering semantics.\n *\n * @returns The middleware function for the assets pipeline.\n */\n get occurrenceFilterMiddleware(): Middleware {\n return forDataTypes(['balance'], async (ctx, next) => {\n const { response } = ctx;\n const {\n assetsBalance: stateBalances,\n assetsInfo: stateMetadata,\n customAssets,\n } = ctx.getAssetsState();\n\n const customAssetIds = new Set(\n Object.values(customAssets ?? {})\n .flat()\n .map((id) => id.toLowerCase()),\n );\n\n // State keys are checksummed, but AccountActivity delivers lower-case\n // ERC-20 IDs, so every lookup below compares lower-cased IDs. Matching\n // case-sensitively would classify existing holdings as new and delete\n // the very balance update this pipeline pass is meant to persist.\n const knownMetadataIds = new Set(\n Object.keys(stateMetadata).map((id) => id.toLowerCase()),\n );\n\n // Candidates: EVM ERC-20s that are genuinely new (absent from state\n // balances and metadata) — the same assets DetectionMiddleware would\n // mark as newly detected right after this middleware.\n const candidateByLowerId = new Map<string, string>();\n for (const [accountId, accountBalances] of Object.entries(\n response.assetsBalance ?? {},\n )) {\n const knownBalanceIds = new Set(\n Object.keys(stateBalances[accountId] ?? {}).map((id) =>\n id.toLowerCase(),\n ),\n );\n for (const assetId of Object.keys(accountBalances)) {\n const caipAssetId = assetId as Caip19AssetId;\n const lowerId = assetId.toLowerCase();\n if (\n knownBalanceIds.has(lowerId) ||\n knownMetadataIds.has(lowerId) ||\n customAssetIds.has(lowerId) ||\n lowerId.includes(`/erc20:${MUSD_ADDRESS_LOWERCASE}`)\n ) {\n continue;\n }\n try {\n const { assetNamespace, chain } = parseCaipAssetType(caipAssetId);\n if (\n assetNamespace === CaipAssetNamespace.Erc20 &&\n chain.namespace === KnownCaipNamespace.Eip155\n ) {\n candidateByLowerId.set(lowerId, assetId);\n }\n } catch {\n // Unparseable IDs are left for downstream middleware to handle.\n }\n }\n }\n\n if (candidateByLowerId.size === 0) {\n return next(ctx);\n }\n\n try {\n const [occurrenceResponse, suggestedOccurrenceFloors] =\n await Promise.all([\n reduceInBatchesSerially<string, V3AssetResponse[]>({\n values: [...candidateByLowerId.values()],\n batchSize: TOKENS_API_BATCH_SIZE,\n eachBatch: async (workingResult, batch) => {\n const batchResponse = await fetchWithTimeout(\n () =>\n this.#apiClient.tokens.fetchV3Assets(batch, {\n includeOccurrences: true,\n }),\n this.#fetchTimeoutMs,\n );\n return [\n ...(workingResult as V3AssetResponse[]),\n ...batchResponse,\n ];\n },\n initialResult: [],\n }),\n this.#getSuggestedOccurrenceFloors(),\n ]);\n\n // Only assets the API knows can be judged; missing ones are kept.\n const spamAssetIds = new Set<string>();\n for (const assetData of occurrenceResponse) {\n const candidateId = candidateByLowerId.get(\n assetData.assetId.toLowerCase(),\n );\n if (\n candidateId !== undefined &&\n (assetData.occurrences ?? 0) <\n getOccurrenceFloorForAsset(candidateId, suggestedOccurrenceFloors)\n ) {\n spamAssetIds.add(candidateId);\n }\n }\n\n if (spamAssetIds.size > 0) {\n cleanResponseSpam(spamAssetIds, response);\n log('Filtered low-occurrence websocket assets', {\n assetIds: [...spamAssetIds],\n });\n }\n } catch (error) {\n // Fail open — keep all assets when occurrences cannot be fetched.\n log('Failed to fetch occurrences for websocket update', { error });\n }\n\n return next(ctx);\n });\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 and balances missing metadata\n * 3. Enriches the response with fetched metadata\n * 4. Calls next() at the end to continue the middleware chain\n *\n * Spam filtering (EVM occurrence floors / non-EVM Blockaid) applies only to\n * newly `detectedAssets`. Balance-only heals — assets already present in\n * `assetsBalance` but missing `assetsInfo`, which DetectionMiddleware skips —\n * are enriched without being filtered out of balances.\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 // Newly detected asset IDs (lowercase) — subject to spam filtering.\n const detectedAssetIds = new Set<string>();\n // Balance-only heals (in assetsBalance, not newly detected) — enrich\n // metadata but do not spam-filter / delete holdings.\n const balanceHealAssetIds = new Set<string>();\n\n // Custom assets are user-imported — exempt from spam filtering.\n // State stores asset IDs in their normalized (checksummed) form, but the\n // V3 Tokens API can return them lower-cased. Lowercase both sides so the\n // bypass is robust to address-case differences across data sources.\n const customAssetIds = new Set<string>(\n Object.values(customAssets ?? {})\n .flat()\n .map((id) => id.toLowerCase()),\n );\n\n // Always include native asset IDs from NetworkEnablementController\n const nativeAssetIdsList = this.#getNativeAssetIds();\n const nativeAssetIds = new Set(\n nativeAssetIdsList.map((id) => id.toLowerCase()),\n );\n for (const nativeAssetId of nativeAssetIdsList) {\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 detectedAssetIds.add(assetId.toLowerCase());\n assetIdsNeedingMetadata.add(assetId);\n }\n }\n }\n\n // Also fetch metadata for balances that are missing it\n if (response.assetsBalance) {\n for (const accountBalances of Object.values(response.assetsBalance)) {\n for (const assetId of Object.keys(\n accountBalances,\n ) as Caip19AssetId[]) {\n if (response.assetsInfo?.[assetId]?.image) {\n continue;\n }\n if (stateMetadata[assetId]?.image) {\n continue;\n }\n if (isStakingContractAssetId(assetId)) {\n continue;\n }\n assetIdsNeedingMetadata.add(assetId);\n // Only treat as a heal when DetectionMiddleware did not queue it\n // as newly detected — those remain subject to spam filtering.\n if (!detectedAssetIds.has(assetId.toLowerCase())) {\n balanceHealAssetIds.add(assetId.toLowerCase());\n }\n }\n }\n }\n\n if (assetIdsNeedingMetadata.size === 0) {\n return next(ctx);\n }\n\n // Filter asset IDs to only include supported networks; load per-chain\n // occurrence floors in parallel (Token API suggested floors).\n const [supportedNetworks, suggestedOccurrenceFloors] = await Promise.all([\n this.#getSupportedNetworks(),\n this.#getSuggestedOccurrenceFloors(),\n ]);\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 fetchWithTimeout(\n () => this.#apiClient.tokens.fetchV3Assets(batch, fetchOptions),\n this.#fetchTimeoutMs,\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 assets are 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 (nativeAssetIds.has(assetId.toLowerCase())) {\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 per-chain suggested occurrence floor (from Token API\n // `/v1/suggestedOccurrenceFloors`, default {@link DEFAULT_OCCURRENCE_FLOOR})\n // to suppress low-signal tokens. Tokens with no occurrence data\n // (undefined) are treated the same as zero occurrences and filtered out.\n // Custom assets (user-imported) bypass the occurrence filter — users\n // can import whatever they want and we must keep their metadata even\n // if the API has fewer aggregator hits than the floor.\n // Balance-only heals also bypass — see `balanceHealAssetIds` below.\n const allowedEvmIds = new Set(\n evmErc20Ids.filter(\n (id) =>\n customAssetIds.has(id.toLowerCase()) ||\n balanceHealAssetIds.has(id.toLowerCase()) ||\n (occurrencesByAssetId.get(id) ?? 0) >=\n getOccurrenceFloorForAsset(id, suggestedOccurrenceFloors) ||\n id.includes(`/erc20:${MUSD_ADDRESS_LOWERCASE}`),\n ),\n );\n\n // Non-EVM: Blockaid bulk scan.\n // Custom assets and balance-only heals bypass Blockaid filtering.\n const nonEvmToScan = nonEvmTokenIds.filter(\n (id) =>\n !customAssetIds.has(id.toLowerCase()) &&\n !balanceHealAssetIds.has(id.toLowerCase()),\n );\n const allowedNonEvmIds = new Set([\n ...nonEvmTokenIds.filter(\n (id) =>\n customAssetIds.has(id.toLowerCase()) ||\n balanceHealAssetIds.has(id.toLowerCase()),\n ),\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 this.#getAssetType,\n );\n }\n\n if (filteredOutAssets.size > 0) {\n cleanResponseSpam(filteredOutAssets, response);\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,4 +1,3 @@
|
|
|
1
|
-
import type { GetAssetVisibility } from '../../../utils/assetVisibility.js';
|
|
2
1
|
import type { MulticallClient } from '../clients/index.js';
|
|
3
2
|
import type { AccountId, Address, AssetFetchEntry, AssetsBalanceState, BalanceFetchResult, CaipAssetType, ChainId } from '../types/index.js';
|
|
4
3
|
/**
|
|
@@ -14,13 +13,6 @@ export type BalanceFetcherConfig = {
|
|
|
14
13
|
pollingInterval?: number;
|
|
15
14
|
/** Determines whether a CAIP-19 asset ID represents a native asset. */
|
|
16
15
|
isNativeAsset: (assetId: CaipAssetType) => boolean;
|
|
17
|
-
/**
|
|
18
|
-
* When true, polls default tracked assets (e.g. mUSD) in addition to
|
|
19
|
-
* `assetsBalance` and `customAssets`. v5 omits this and stays pins + balances.
|
|
20
|
-
*/
|
|
21
|
-
isBalanceV6Enabled: () => boolean;
|
|
22
|
-
/** Returns shared visible/hidden assets for an account/chain scope. */
|
|
23
|
-
getAssetVisibility: GetAssetVisibility;
|
|
24
16
|
};
|
|
25
17
|
/**
|
|
26
18
|
* Polling input for BalanceFetcher - identifies what to poll for.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BalanceFetcher.d.ts","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/BalanceFetcher.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"BalanceFetcher.d.ts","sourceRoot":"","sources":["../../../../src/data-sources/evm-rpc-services/services/BalanceFetcher.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,KAAK,EACV,SAAS,EACT,OAAO,EAEP,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAGlB,aAAa,EACb,OAAO,EACR,MAAM,mBAAmB,CAAC;AAK3B;;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;IACzB,uEAAuE;IACvE,aAAa,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,OAAO,CAAC;CACpD,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;IACxB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,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;;IAa5F,YACE,eAAe,EAAE,eAAe,EAChC,SAAS,EAAE,uBAAuB,EAClC,MAAM,EAAE,oBAAoB,EAa7B;IAED;;;;OAIG;IACH,kBAAkB,CAAC,QAAQ,EAAE,uBAAuB,GAAG,IAAI,CAE1D;IAED;;;;;OAKG;IACG,YAAY,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAW5D;IAmGD;;;;;;;;;;;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,CAqE7B;CAqFF"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { StaticIntervalPollingControllerOnly } from '@metamask/polling-controller';
|
|
2
2
|
import { parseCaipAssetType } from '@metamask/utils';
|
|
3
3
|
import { ZERO_ADDRESS } from '../../../utils/constants.js';
|
|
4
|
-
import {
|
|
4
|
+
import { reduceInBatchesSerially } from '../utils/index.js';
|
|
5
5
|
const DEFAULT_BALANCE_INTERVAL = 30_000; // 30 seconds
|
|
6
6
|
/**
|
|
7
7
|
* BalanceFetcher - Fetches token balances via multicall.
|
|
@@ -17,8 +17,6 @@ export class BalanceFetcher extends StaticIntervalPollingControllerOnly() {
|
|
|
17
17
|
#messenger;
|
|
18
18
|
#config;
|
|
19
19
|
#isNativeAsset;
|
|
20
|
-
#isBalanceV6Enabled;
|
|
21
|
-
#getAssetVisibility;
|
|
22
20
|
#onBalanceUpdate;
|
|
23
21
|
constructor(multicallClient, messenger, config) {
|
|
24
22
|
super();
|
|
@@ -29,8 +27,6 @@ export class BalanceFetcher extends StaticIntervalPollingControllerOnly() {
|
|
|
29
27
|
defaultTimeoutMs: config.defaultTimeoutMs ?? 30000,
|
|
30
28
|
};
|
|
31
29
|
this.#isNativeAsset = config.isNativeAsset;
|
|
32
|
-
this.#isBalanceV6Enabled = config.isBalanceV6Enabled;
|
|
33
|
-
this.#getAssetVisibility = config.getAssetVisibility;
|
|
34
30
|
// Set the polling interval
|
|
35
31
|
this.setIntervalLength(config?.pollingInterval ?? DEFAULT_BALANCE_INTERVAL);
|
|
36
32
|
}
|
|
@@ -109,44 +105,6 @@ export class BalanceFetcher extends StaticIntervalPollingControllerOnly() {
|
|
|
109
105
|
}
|
|
110
106
|
return Array.from(assetsToFetch.values());
|
|
111
107
|
}
|
|
112
|
-
/**
|
|
113
|
-
* v6 replacement for {@link BalanceFetcher.getAssetsToFetch}: the shared
|
|
114
|
-
* visible set (native, pins, default tracked) plus anything already tracked
|
|
115
|
-
* with a balance entry, minus hidden assets and staking vault share tokens.
|
|
116
|
-
* Share-token `balanceOf` is not the converted staked amount; publishing it
|
|
117
|
-
* as `updateMode: 'full'` would overwrite the value from StakedBalanceFetcher.
|
|
118
|
-
*
|
|
119
|
-
* @param chainId - Hex chain ID (e.g. "0x1").
|
|
120
|
-
* @param accountId - Account UUID.
|
|
121
|
-
* @returns Array of asset fetch entries for the requested chain.
|
|
122
|
-
*/
|
|
123
|
-
#getAssetsToFetchV6(chainId, accountId) {
|
|
124
|
-
const state = this.#messenger.call('AssetsController:getState');
|
|
125
|
-
const chainIdDecimal = parseInt(chainId, 16).toString();
|
|
126
|
-
const { visibleAssetIds, hiddenAssetIds } = this.#getAssetVisibility([accountId], [`eip155:${chainIdDecimal}`]);
|
|
127
|
-
const hidden = new Set(hiddenAssetIds.map((assetId) => assetId.toLowerCase()));
|
|
128
|
-
const tracked = Object.keys(state?.assetsBalance?.[accountId] ?? {});
|
|
129
|
-
const assetsToFetch = new Map();
|
|
130
|
-
for (const assetId of [...visibleAssetIds, ...tracked]) {
|
|
131
|
-
const normalizedAssetId = assetId.toLowerCase();
|
|
132
|
-
if (assetsToFetch.has(normalizedAssetId) ||
|
|
133
|
-
hidden.has(normalizedAssetId) ||
|
|
134
|
-
isStakingContractAssetId(assetId)) {
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
const parsed = parseCaipAssetType(assetId);
|
|
138
|
-
if (parsed.chain.reference !== chainIdDecimal) {
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
assetsToFetch.set(normalizedAssetId, {
|
|
142
|
-
assetId,
|
|
143
|
-
address: this.#isNativeAsset(assetId)
|
|
144
|
-
? ZERO_ADDRESS
|
|
145
|
-
: parsed.assetReference.toLowerCase(),
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
return Array.from(assetsToFetch.values());
|
|
149
|
-
}
|
|
150
108
|
/**
|
|
151
109
|
* Fetch balances for assets already tracked in state for the given
|
|
152
110
|
* account and chain.
|
|
@@ -158,10 +116,7 @@ export class BalanceFetcher extends StaticIntervalPollingControllerOnly() {
|
|
|
158
116
|
* @returns Balance fetch result.
|
|
159
117
|
*/
|
|
160
118
|
async #fetchBalances(chainId, accountId, accountAddress, customAssetsOnly) {
|
|
161
|
-
|
|
162
|
-
const assets = this.#isBalanceV6Enabled() && !customAssetsOnly
|
|
163
|
-
? this.#getAssetsToFetchV6(chainId, accountId)
|
|
164
|
-
: this.#getAssetsToFetch(chainId, accountId, customAssetsOnly);
|
|
119
|
+
const assets = this.#getAssetsToFetch(chainId, accountId, customAssetsOnly);
|
|
165
120
|
return this.fetchBalancesForAssets(chainId, accountId, accountAddress, assets);
|
|
166
121
|
}
|
|
167
122
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BalanceFetcher.js","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 type { ChainId as Caip2ChainId } from '../../../types.js';\nimport type { GetAssetVisibility } from '../../../utils/assetVisibility.js';\nimport { ZERO_ADDRESS } from '../../../utils/constants.js';\nimport type { MulticallClient } from '../clients/index.js';\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/index.js';\nimport {\n isStakingContractAssetId,\n reduceInBatchesSerially,\n} from '../utils/index.js';\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 /** Determines whether a CAIP-19 asset ID represents a native asset. */\n isNativeAsset: (assetId: CaipAssetType) => boolean;\n /**\n * When true, polls default tracked assets (e.g. mUSD) in addition to\n * `assetsBalance` and `customAssets`. v5 omits this and stays pins + balances.\n */\n isBalanceV6Enabled: () => boolean;\n /** Returns shared visible/hidden assets for an account/chain scope. */\n getAssetVisibility: GetAssetVisibility;\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 * When true, only fetch balances for entries in `state.customAssets`,\n * skipping `state.assetsBalance`. Used by the supplemental RPC\n * subscription on chains that another data source is already covering\n * for regular balance refreshes.\n */\n customAssetsOnly?: boolean;\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<\n Omit<\n BalanceFetcherConfig,\n | 'pollingInterval'\n | 'isNativeAsset'\n | 'isBalanceV6Enabled'\n | 'getAssetVisibility'\n >\n >;\n\n readonly #isNativeAsset: (assetId: CaipAssetType) => boolean;\n\n readonly #isBalanceV6Enabled: () => boolean;\n\n readonly #getAssetVisibility: GetAssetVisibility;\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 this.#isNativeAsset = config.isNativeAsset;\n this.#isBalanceV6Enabled = config.isBalanceV6Enabled;\n this.#getAssetVisibility = config.getAssetVisibility;\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 input.customAssetsOnly === true,\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 * @param customAssetsOnly - When true, skip `assetsBalance` and only include `customAssets`.\n * @returns Array of asset fetch entries from state for the requested chain.\n */\n #getAssetsToFetch(\n chainId: ChainId,\n accountId: AccountId,\n customAssetsOnly: boolean,\n ): AssetFetchEntry[] {\n const state = this.#messenger.call('AssetsController:getState');\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 const collect = (assetId: CaipAssetType): void => {\n const parsed = parseCaipAssetType(assetId);\n\n if (parsed.chain.reference !== chainIdDecimal) {\n return;\n }\n\n const normalizedAssetId = assetId.toLowerCase();\n\n if (assetsToFetch.has(normalizedAssetId)) {\n return;\n }\n\n const isNative = this.#isNativeAsset(assetId);\n\n assetsToFetch.set(normalizedAssetId, {\n assetId,\n address: isNative\n ? ZERO_ADDRESS\n : (parsed.assetReference.toLowerCase() as Address),\n });\n };\n\n // 1. Assets already tracked with a balance entry — only when not in\n // \"customAssetsOnly\" mode (otherwise another data source is already\n // refreshing them and we'd double-poll).\n if (!customAssetsOnly) {\n const accountBalances = state?.assetsBalance?.[accountId];\n if (accountBalances) {\n for (const assetId of Object.keys(accountBalances) as CaipAssetType[]) {\n collect(assetId);\n }\n }\n }\n\n // 2. User-added custom assets — RPC is the sole balance fetcher for\n // these, so they must be polled even if they have no balance entry\n // yet (e.g. zero balance, first fetch failed, or state was hydrated\n // from disk before any successful fetch).\n const customAssets = state?.customAssets?.[accountId];\n if (customAssets) {\n for (const assetId of customAssets as CaipAssetType[]) {\n collect(assetId);\n }\n }\n\n return Array.from(assetsToFetch.values());\n }\n\n /**\n * v6 replacement for {@link BalanceFetcher.getAssetsToFetch}: the shared\n * visible set (native, pins, default tracked) plus anything already tracked\n * with a balance entry, minus hidden assets and staking vault share tokens.\n * Share-token `balanceOf` is not the converted staked amount; publishing it\n * as `updateMode: 'full'` would overwrite the value from StakedBalanceFetcher.\n *\n * @param chainId - Hex chain ID (e.g. \"0x1\").\n * @param accountId - Account UUID.\n * @returns Array of asset fetch entries for the requested chain.\n */\n #getAssetsToFetchV6(\n chainId: ChainId,\n accountId: AccountId,\n ): AssetFetchEntry[] {\n const state = this.#messenger.call('AssetsController:getState');\n const chainIdDecimal = parseInt(chainId, 16).toString();\n\n const { visibleAssetIds, hiddenAssetIds } = this.#getAssetVisibility(\n [accountId],\n [`eip155:${chainIdDecimal}` as Caip2ChainId],\n );\n const hidden = new Set(\n hiddenAssetIds.map((assetId) => assetId.toLowerCase()),\n );\n const tracked = Object.keys(\n state?.assetsBalance?.[accountId] ?? {},\n ) as CaipAssetType[];\n\n const assetsToFetch = new Map<string, AssetFetchEntry>();\n\n for (const assetId of [...visibleAssetIds, ...tracked]) {\n const normalizedAssetId = assetId.toLowerCase();\n\n if (\n assetsToFetch.has(normalizedAssetId) ||\n hidden.has(normalizedAssetId) ||\n isStakingContractAssetId(assetId)\n ) {\n continue;\n }\n\n const parsed = parseCaipAssetType(assetId);\n\n if (parsed.chain.reference !== chainIdDecimal) {\n continue;\n }\n\n assetsToFetch.set(normalizedAssetId, {\n assetId,\n address: this.#isNativeAsset(assetId)\n ? ZERO_ADDRESS\n : (parsed.assetReference.toLowerCase() as Address),\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 * @param customAssetsOnly - When true, skip `assetsBalance` and only include `customAssets`.\n * @returns Balance fetch result.\n */\n async #fetchBalances(\n chainId: ChainId,\n accountId: AccountId,\n accountAddress: Address,\n customAssetsOnly: boolean,\n ): Promise<BalanceFetchResult> {\n // The supplemental `customAssetsOnly` subscription only exists on v5.\n const assets =\n this.#isBalanceV6Enabled() && !customAssetsOnly\n ? this.#getAssetsToFetchV6(chainId, accountId)\n : this.#getAssetsToFetch(chainId, accountId, customAssetsOnly);\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 { fallbackToSingleCalls: true },\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.js","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.js';\nimport type { MulticallClient } from '../clients/index.js';\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/index.js';\nimport { reduceInBatchesSerially } from '../utils/index.js';\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 /** Determines whether a CAIP-19 asset ID represents a native asset. */\n isNativeAsset: (assetId: CaipAssetType) => boolean;\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 * When true, only fetch balances for entries in `state.customAssets`,\n * skipping `state.assetsBalance`. Used by the supplemental RPC\n * subscription on chains that another data source is already covering\n * for regular balance refreshes.\n */\n customAssetsOnly?: boolean;\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<\n Omit<BalanceFetcherConfig, 'pollingInterval' | 'isNativeAsset'>\n >;\n\n readonly #isNativeAsset: (assetId: CaipAssetType) => boolean;\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 this.#isNativeAsset = config.isNativeAsset;\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 input.customAssetsOnly === true,\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 * @param customAssetsOnly - When true, skip `assetsBalance` and only include `customAssets`.\n * @returns Array of asset fetch entries from state for the requested chain.\n */\n #getAssetsToFetch(\n chainId: ChainId,\n accountId: AccountId,\n customAssetsOnly: boolean,\n ): AssetFetchEntry[] {\n const state = this.#messenger.call('AssetsController:getState');\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 const collect = (assetId: CaipAssetType): void => {\n const parsed = parseCaipAssetType(assetId);\n\n if (parsed.chain.reference !== chainIdDecimal) {\n return;\n }\n\n const normalizedAssetId = assetId.toLowerCase();\n\n if (assetsToFetch.has(normalizedAssetId)) {\n return;\n }\n\n const isNative = this.#isNativeAsset(assetId);\n\n assetsToFetch.set(normalizedAssetId, {\n assetId,\n address: isNative\n ? ZERO_ADDRESS\n : (parsed.assetReference.toLowerCase() as Address),\n });\n };\n\n // 1. Assets already tracked with a balance entry — only when not in\n // \"customAssetsOnly\" mode (otherwise another data source is already\n // refreshing them and we'd double-poll).\n if (!customAssetsOnly) {\n const accountBalances = state?.assetsBalance?.[accountId];\n if (accountBalances) {\n for (const assetId of Object.keys(accountBalances) as CaipAssetType[]) {\n collect(assetId);\n }\n }\n }\n\n // 2. User-added custom assets — RPC is the sole balance fetcher for\n // these, so they must be polled even if they have no balance entry\n // yet (e.g. zero balance, first fetch failed, or state was hydrated\n // from disk before any successful fetch).\n const customAssets = state?.customAssets?.[accountId];\n if (customAssets) {\n for (const assetId of customAssets as CaipAssetType[]) {\n collect(assetId);\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 * @param customAssetsOnly - When true, skip `assetsBalance` and only include `customAssets`.\n * @returns Balance fetch result.\n */\n async #fetchBalances(\n chainId: ChainId,\n accountId: AccountId,\n accountAddress: Address,\n customAssetsOnly: boolean,\n ): Promise<BalanceFetchResult> {\n const assets = this.#getAssetsToFetch(chainId, accountId, customAssetsOnly);\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 { fallbackToSingleCalls: true },\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"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export type { SnapDataSourceState, SnapDataSourceOptions, } from './data-sources
|
|
|
18
18
|
export { TokenDataSource, PriceDataSource } from './data-sources/index.js';
|
|
19
19
|
export type { TokenDataSourceOptions, PriceDataSourceConfig, PriceDataSourceOptions, } from './data-sources/index.js';
|
|
20
20
|
export { CustomAssetGraduationMiddleware, DetectionMiddleware, RpcFallbackMiddleware, } from './middlewares/index.js';
|
|
21
|
-
export type { CustomAssetGraduationMiddlewareOptions,
|
|
21
|
+
export type { CustomAssetGraduationMiddlewareOptions, RpcFallbackMiddlewareOptions, } from './middlewares/index.js';
|
|
22
22
|
export { normalizeAssetId, formatExchangeRatesForBridge, formatStateForTransactionPay, } from './utils/index.js';
|
|
23
23
|
export type { AccountForLegacyFormat, BridgeExchangeRatesFormat, FormatExchangeRatesForBridgeParams, FormatStateForTransactionPayParams, LegacyToken, TransactionPayLegacyFormat, } from './utils/index.js';
|
|
24
24
|
export { calculateBalanceForAllWallets, calculateBalanceChangeForAccountGroup, getAggregatedBalanceForAccount, getGroupIdForAccount, getInternalAccountsForGroup, } from './selectors/balance.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,yBAAyB;AACzB,OAAO,EACL,gBAAgB,EAChB,+BAA+B,GAChC,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EACL,+BAA+B,EAC/B,kCAAkC,EAClC,sBAAsB,EACtB,sBAAsB,EACtB,+BAA+B,EAC/B,uBAAuB,GACxB,MAAM,eAAe,CAAC;AAGvB,4CAA4C;AAC5C,OAAO,EACL,gCAAgC,EAChC,iCAAiC,EACjC,yBAAyB,EACzB,gCAAgC,EAChC,sBAAsB,EACtB,+BAA+B,EAC/B,8BAA8B,GAC/B,MAAM,mCAAmC,CAAC;AAwF3C,sCAAsC;AACtC,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAO7D,6BAA6B;AAC7B,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAQhE,qBAAqB;AACrB,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAS7E,gFAAgF;AAChF,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,qBAAqB;AACrB,YAAY;AACZ,kBAAkB;AAClB,oBAAoB;AACpB,iBAAiB,EACjB,uBAAuB,GACxB,MAAM,yBAAyB,CAAC;AAOjC,0BAA0B;AAC1B,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAQ3E,cAAc;AACd,OAAO,EACL,+BAA+B,EAC/B,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,yBAAyB;AACzB,OAAO,EACL,gBAAgB,EAChB,+BAA+B,GAChC,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EACL,+BAA+B,EAC/B,kCAAkC,EAClC,sBAAsB,EACtB,sBAAsB,EACtB,+BAA+B,EAC/B,uBAAuB,GACxB,MAAM,eAAe,CAAC;AAGvB,4CAA4C;AAC5C,OAAO,EACL,gCAAgC,EAChC,iCAAiC,EACjC,yBAAyB,EACzB,gCAAgC,EAChC,sBAAsB,EACtB,+BAA+B,EAC/B,8BAA8B,GAC/B,MAAM,mCAAmC,CAAC;AAwF3C,sCAAsC;AACtC,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAO7D,6BAA6B;AAC7B,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAQhE,qBAAqB;AACrB,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAS7E,gFAAgF;AAChF,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,qBAAqB;AACrB,YAAY;AACZ,kBAAkB;AAClB,oBAAoB;AACpB,iBAAiB,EACjB,uBAAuB,GACxB,MAAM,yBAAyB,CAAC;AAOjC,0BAA0B;AAC1B,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAQ3E,cAAc;AACd,OAAO,EACL,+BAA+B,EAC/B,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAMhC,YAAY;AACZ,OAAO,EACL,gBAAgB,EAChB,4BAA4B,EAC5B,4BAA4B,GAC7B,MAAM,kBAAkB,CAAC;AAU1B,YAAY;AACZ,OAAO,EACL,6BAA6B,EAC7B,qCAAqC,EACrC,8BAA8B,EAC9B,oBAAoB,EACpB,2BAA2B,GAC5B,MAAM,wBAAwB,CAAC","sourcesContent":["// Main controller export\nexport {\n AssetsController,\n getDefaultAssetsControllerState,\n} from './AssetsController.js';\nexport { AssetsDataSourceError } from './errors.js';\nexport {\n DEFAULT_TRACKED_ASSETS_BY_CHAIN,\n CHAINS_WITH_DEFAULT_TRACKED_ASSETS,\n DEFAULT_ASSET_METADATA,\n buildDefaultAssetsInfo,\n getDefaultTrackedAssetsForChain,\n getDefaultAssetMetadata,\n} from './defaults.js';\nexport type { PendingTokenMetadata } from './AssetsController.js';\n\n// Snaps → AssetsController assets migration\nexport {\n SNAPS_ASSETS_MIGRATION_FLAG_KEYS,\n SNAPS_ASSETS_MIGRATION_NAMESPACES,\n SnapsAssetsMigrationStage,\n getSnapsAssetsMigrationNamespace,\n isMigrationStageActive,\n isSnapsAssetsMigrationNamespace,\n parseSnapsAssetsMigrationStage,\n} from './utils/snaps-assets-migration.js';\nexport type { SnapsAssetsMigrationNamespace } from './utils/snaps-assets-migration.js';\n\n// State and messenger types\nexport type {\n AssetsControllerState,\n AssetsControllerMessenger,\n AssetsControllerOptions,\n AssetsControllerGetStateAction,\n AssetsControllerActions,\n AssetsControllerStateChangeEvent,\n AssetsControllerBalanceChangedEvent,\n AssetsControllerPriceChangedEvent,\n AssetsControllerAssetsDetectedEvent,\n AssetsControllerEvents,\n} from './AssetsController.js';\nexport type {\n AssetsControllerGetAssetsAction,\n AssetsControllerGetAssetsBalanceAction,\n AssetsControllerGetAssetMetadataAction,\n AssetsControllerGetAccountAssetByIDAction,\n AssetsControllerGetAccountAssetsByIDsAction,\n AssetsControllerGetAccountAssetsByScopeAction,\n AssetsControllerGetAssetsPriceAction,\n AssetsControllerAddCustomAssetAction,\n AssetsControllerRemoveCustomAssetAction,\n AssetsControllerGetCustomAssetsAction,\n AssetsControllerHideAssetAction,\n AssetsControllerUnhideAssetAction,\n AssetsControllerGetExchangeRatesForBridgeAction,\n AssetsControllerGetStateForTransactionPayAction,\n AssetsControllerSetSelectedCurrencyAction,\n} from './AssetsController-method-action-types.js';\n\n// Core types\nexport type {\n // CAIP types\n Caip19AssetId,\n AccountId,\n ChainId,\n // Asset types\n AssetType,\n TokenStandard,\n // Contract data types\n TokenFees,\n HoneypotStatus,\n StorageSlots,\n LocalizedDescription,\n // Metadata types\n BaseAssetMetadata,\n FungibleAssetMetadata,\n ERC721AssetMetadata,\n ERC1155AssetMetadata,\n AssetMetadata,\n // Price types\n BaseAssetPrice,\n FungibleAssetPrice,\n NFTAssetPrice,\n AssetPrice,\n // Balance types\n FungibleAssetBalance,\n ERC721AssetBalance,\n ERC1155AssetBalance,\n AssetBalance,\n // Data source types\n AccountWithSupportedChains,\n DataType,\n DataRequest,\n DataResponse,\n AssetsUpdateMode,\n // Middleware types\n Context,\n NextFunction,\n Middleware,\n AssetsDataSource,\n FetchContext,\n FetchNextFunction,\n FetchMiddleware,\n SubscriptionResponse,\n // Combined asset type\n Asset,\n // Event types\n BalanceChangeEvent,\n PriceChangeEvent,\n MetadataChangeEvent,\n AssetsDetectedEvent,\n} from './types.js';\n\n// Data sources - base class and types\nexport { AbstractDataSource } from './data-sources/index.js';\n\nexport type {\n DataSourceState,\n SubscriptionRequest,\n} from './data-sources/index.js';\n\n// Data sources - AccountsApi\nexport { AccountsApiDataSource } from './data-sources/index.js';\n\nexport type {\n AccountsApiDataSourceConfig,\n AccountsApiDataSourceOptions,\n AccountsApiDataSourceState,\n} from './data-sources/index.js';\n\n// Data sources - RPC\nexport { RpcDataSource, createRpcDataSource } from './data-sources/index.js';\n\nexport type {\n RpcDataSourceConfig,\n RpcDataSourceOptions,\n RpcDataSourceState,\n ChainStatus,\n} from './data-sources/index.js';\n\n// Data sources - Unified Snap Data Source (dynamically discovers keyring snaps)\nexport {\n SnapDataSource,\n createSnapDataSource,\n SNAP_DATA_SOURCE_NAME,\n // Constants\n KEYRING_PERMISSION,\n // Utility functions\n getChainIdsCaveat,\n extractChainFromAssetId,\n} from './data-sources/index.js';\n\nexport type {\n SnapDataSourceState,\n SnapDataSourceOptions,\n} from './data-sources/index.js';\n\n// Enrichment data sources\nexport { TokenDataSource, PriceDataSource } from './data-sources/index.js';\n\nexport type {\n TokenDataSourceOptions,\n PriceDataSourceConfig,\n PriceDataSourceOptions,\n} from './data-sources/index.js';\n\n// Middlewares\nexport {\n CustomAssetGraduationMiddleware,\n DetectionMiddleware,\n RpcFallbackMiddleware,\n} from './middlewares/index.js';\nexport type {\n CustomAssetGraduationMiddlewareOptions,\n RpcFallbackMiddlewareOptions,\n} from './middlewares/index.js';\n\n// Utilities\nexport {\n normalizeAssetId,\n formatExchangeRatesForBridge,\n formatStateForTransactionPay,\n} from './utils/index.js';\nexport type {\n AccountForLegacyFormat,\n BridgeExchangeRatesFormat,\n FormatExchangeRatesForBridgeParams,\n FormatStateForTransactionPayParams,\n LegacyToken,\n TransactionPayLegacyFormat,\n} from './utils/index.js';\n\n// Selectors\nexport {\n calculateBalanceForAllWallets,\n calculateBalanceChangeForAccountGroup,\n getAggregatedBalanceForAccount,\n getGroupIdForAccount,\n getInternalAccountsForGroup,\n} from './selectors/balance.js';\n\nexport type {\n AccountGroupBalance,\n AccountsById,\n AggregatedBalanceEntry,\n AggregatedBalanceForAccount,\n AllWalletsBalance,\n BalanceChangePeriod,\n BalanceChangeResult,\n EnabledNetworkMap,\n WalletBalance,\n} from './selectors/balance.js';\n"]}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import type { AccountId,
|
|
1
|
+
import type { AccountId, Caip19AssetId, Middleware } from '../types.js';
|
|
2
2
|
export type CustomAssetGraduationMiddlewareOptions = {
|
|
3
3
|
getSelectedAccountId: () => AccountId | undefined;
|
|
4
4
|
removeCustomAsset: (accountId: AccountId, assetId: Caip19AssetId) => void;
|
|
5
|
-
/** Current AssetsController state. Used to read customAssets for the selected account. */
|
|
6
|
-
getAssetsState: () => AssetsControllerState;
|
|
7
5
|
};
|
|
8
6
|
/**
|
|
9
7
|
* CustomAssetGraduationMiddleware removes EVM assets from `customAssets` when
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CustomAssetGraduationMiddleware.d.ts","sourceRoot":"","sources":["../../src/middlewares/CustomAssetGraduationMiddleware.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,SAAS,EAET,
|
|
1
|
+
{"version":3,"file":"CustomAssetGraduationMiddleware.d.ts","sourceRoot":"","sources":["../../src/middlewares/CustomAssetGraduationMiddleware.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,SAAS,EAET,aAAa,EACb,UAAU,EACX,MAAM,aAAa,CAAC;AAOrB,MAAM,MAAM,sCAAsC,GAAG;IACnD,oBAAoB,EAAE,MAAM,SAAS,GAAG,SAAS,CAAC;IAClD,iBAAiB,EAAE,CAAC,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;CAC3E,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,+BAA+B;;IAC1C,QAAQ,CAAC,IAAI,qCAAmB;IAShC,YAAY,OAAO,EAAE,sCAAsC,EAG1D;IAED,OAAO,IAAI,MAAM,CAEhB;IAED,IAAI,gBAAgB,IAAI,UAAU,CAkDjC;CACF"}
|
|
@@ -27,11 +27,9 @@ export class CustomAssetGraduationMiddleware {
|
|
|
27
27
|
name = CONTROLLER_NAME;
|
|
28
28
|
#getSelectedAccountId;
|
|
29
29
|
#removeCustomAsset;
|
|
30
|
-
#getAssetsState;
|
|
31
30
|
constructor(options) {
|
|
32
31
|
this.#getSelectedAccountId = options.getSelectedAccountId;
|
|
33
32
|
this.#removeCustomAsset = options.removeCustomAsset;
|
|
34
|
-
this.#getAssetsState = options.getAssetsState;
|
|
35
33
|
}
|
|
36
34
|
getName() {
|
|
37
35
|
return this.name;
|
|
@@ -47,7 +45,7 @@ export class CustomAssetGraduationMiddleware {
|
|
|
47
45
|
if (!accountId) {
|
|
48
46
|
return next(ctx);
|
|
49
47
|
}
|
|
50
|
-
const state =
|
|
48
|
+
const state = ctx.getAssetsState();
|
|
51
49
|
const customForAccount = state.customAssets?.[accountId] ?? [];
|
|
52
50
|
if (customForAccount.length === 0) {
|
|
53
51
|
return next(ctx);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CustomAssetGraduationMiddleware.js","sourceRoot":"","sources":["../../src/middlewares/CustomAssetGraduationMiddleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAErD,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"CustomAssetGraduationMiddleware.js","sourceRoot":"","sources":["../../src/middlewares/CustomAssetGraduationMiddleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAErD,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAO3C,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAEzD,MAAM,eAAe,GAAG,iCAAiC,CAAC;AAE1D,MAAM,GAAG,GAAG,kBAAkB,CAAC,aAAa,EAAE,eAAe,CAAC,CAAC;AAO/D;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,OAAO,+BAA+B;IACjC,IAAI,GAAG,eAAe,CAAC;IAEvB,qBAAqB,CAA8B;IAEnD,kBAAkB,CAGjB;IAEV,YAAY,OAA+C;QACzD,IAAI,CAAC,qBAAqB,GAAG,OAAO,CAAC,oBAAoB,CAAC;QAC1D,IAAI,CAAC,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IACtD,CAAC;IAED,OAAO;QACL,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;IAED,IAAI,gBAAgB;QAClB,OAAO,YAAY,CAAC,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;YACnD,iEAAiE;YACjE,uEAAuE;YACvE,oEAAoE;YACpE,kEAAkE;YAClE,4BAA4B;YAC5B,MAAM,SAAS,GAAG,IAAI,CAAC,qBAAqB,EAAE,CAAC;YAC/C,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;YACnB,CAAC;YAED,MAAM,KAAK,GAAG,GAAG,CAAC,cAAc,EAAE,CAAC;YACnC,MAAM,gBAAgB,GAAG,KAAK,CAAC,YAAY,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;YAC/D,IAAI,gBAAgB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAClC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;YACnB,CAAC;YAED,MAAM,gBAAgB,GAAG,GAAG,CAAC,QAAQ,CAAC,aAAa,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;YACvE,MAAM,gBAAgB,GAAG,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAoB,CAAC;YAC1E,IAAI,gBAAgB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAClC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;YACnB,CAAC;YAED,sEAAsE;YACtE,yDAAyD;YACzD,oEAAoE;YACpE,kEAAkE;YAClE,gCAAgC;YAChC,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,gBAAgB,CAAC,CAAC;YAC5C,KAAK,MAAM,UAAU,IAAI,gBAAgB,EAAE,CAAC;gBAC1C,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,EAAE,CAAC;oBAC9B,SAAS;gBACX,CAAC;gBACD,IAAI,CAAC,kBAAkB,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC;oBACtD,SAAS;gBACX,CAAC;gBACD,MAAM,iBAAiB,GAAG,oBAAoB,CAAC,UAAU,CAAC,CAAC;gBAC3D,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,iBAAiB,CAAC,EAAE,CAAC;oBACtC,SAAS;gBACX,CAAC;gBACD,GAAG,CAAC,yBAAyB,EAAE;oBAC7B,SAAS;oBACT,OAAO,EAAE,iBAAiB;iBAC3B,CAAC,CAAC;gBACH,IAAI,CAAC,kBAAkB,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAC;YACxD,CAAC;YAED,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;QACnB,CAAC,CAAC,CAAC;IACL,CAAC;CACF;AAED;;;;;GAKG;AACH,SAAS,YAAY,CAAC,OAAsB;IAC1C,qEAAqE;IACrE,oEAAoE;IACpE,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACxC,OAAO,SAAS,KAAK,kBAAkB,CAAC,MAAM,CAAC;AACjD,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,SAAS,kBAAkB,CAAC,OAAiC;IAC3D,OAAO,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;AACrC,CAAC","sourcesContent":["import { KnownCaipNamespace } from '@metamask/utils';\n\nimport { projectLogger, createModuleLogger } from '../logger.js';\nimport { forDataTypes } from '../types.js';\nimport type {\n AccountId,\n AssetBalance,\n Caip19AssetId,\n Middleware,\n} from '../types.js';\nimport { safeNormalizeAssetId } from '../utils/index.js';\n\nconst CONTROLLER_NAME = 'CustomAssetGraduationMiddleware';\n\nconst log = createModuleLogger(projectLogger, CONTROLLER_NAME);\n\nexport type CustomAssetGraduationMiddlewareOptions = {\n getSelectedAccountId: () => AccountId | undefined;\n removeCustomAsset: (accountId: AccountId, assetId: Caip19AssetId) => void;\n};\n\n/**\n * CustomAssetGraduationMiddleware removes EVM assets from `customAssets` when\n * an upstream balance source (AccountsAPI / Websocket) reports a non-zero\n * balance for them. Once a detector sees the asset with a real balance, it\n * no longer needs to be tracked as \"custom\" — the regular detection flow\n * will keep it fresh.\n *\n * Rules:\n * - Only the selected account's custom assets are considered. Switching the\n * selected account triggers a fresh fetch, which re-runs graduation\n * against the new account's balances.\n * - Only EVM (CAIP-2 namespace `eip155`) assets graduate. Non-EVM custom\n * assets (Solana, BTC, Tron, etc. — served by Snap data sources) are left\n * alone.\n * - Only positive balances graduate. A zero balance from AccountsAPI means\n * the API knows about the token but the user does not currently hold it;\n * keeping it in `customAssets` ensures RPC keeps polling so a future\n * incoming transfer is reflected promptly.\n */\nexport class CustomAssetGraduationMiddleware {\n readonly name = CONTROLLER_NAME;\n\n readonly #getSelectedAccountId: () => AccountId | undefined;\n\n readonly #removeCustomAsset: (\n accountId: AccountId,\n assetId: Caip19AssetId,\n ) => void;\n\n constructor(options: CustomAssetGraduationMiddlewareOptions) {\n this.#getSelectedAccountId = options.getSelectedAccountId;\n this.#removeCustomAsset = options.removeCustomAsset;\n }\n\n getName(): string {\n return this.name;\n }\n\n get assetsMiddleware(): Middleware {\n return forDataTypes(['balance'], async (ctx, next) => {\n // Inspect the response BEFORE calling next() so we only consider\n // balances populated by upstream middleware (AccountsApi / Websocket /\n // Staked). This middleware is positioned in the pipeline before the\n // RPC fallback — RPC intentionally carries custom assets and must\n // never trigger graduation.\n const accountId = this.#getSelectedAccountId();\n if (!accountId) {\n return next(ctx);\n }\n\n const state = ctx.getAssetsState();\n const customForAccount = state.customAssets?.[accountId] ?? [];\n if (customForAccount.length === 0) {\n return next(ctx);\n }\n\n const returnedBalances = ctx.response.assetsBalance?.[accountId] ?? {};\n const returnedAssetIds = Object.keys(returnedBalances) as Caip19AssetId[];\n if (returnedAssetIds.length === 0) {\n return next(ctx);\n }\n\n // customAssets state is stored with checksummed/normalized asset IDs.\n // AccountsApiDataSource normalizes its response IDs, but\n // AccountActivityDataSource does not — so we normalize the response\n // side here to make the comparison robust to lower-case addresses\n // delivered over the websocket.\n const customSet = new Set(customForAccount);\n for (const rawAssetId of returnedAssetIds) {\n if (!isEvmAssetId(rawAssetId)) {\n continue;\n }\n if (!hasPositiveBalance(returnedBalances[rawAssetId])) {\n continue;\n }\n const normalizedAssetId = safeNormalizeAssetId(rawAssetId);\n if (!customSet.has(normalizedAssetId)) {\n continue;\n }\n log('Graduating custom asset', {\n accountId,\n assetId: normalizedAssetId,\n });\n this.#removeCustomAsset(accountId, normalizedAssetId);\n }\n\n return next(ctx);\n });\n }\n}\n\n/**\n * Check whether a CAIP-19 asset ID belongs to an EVM chain.\n *\n * @param assetId - The CAIP-19 asset ID to inspect.\n * @returns `true` when the asset's chain namespace is `eip155`.\n */\nfunction isEvmAssetId(assetId: Caip19AssetId): boolean {\n // CAIP-19 format: <namespace>:<chainRef>/<assetNamespace>:<assetRef>\n // The chain namespace is always the segment before the first colon.\n const namespace = assetId.split(':')[0];\n return namespace === KnownCaipNamespace.Eip155;\n}\n\n/**\n * Whether a balance entry reports a strictly positive amount. AccountsAPI\n * may return zero for tokens it indexes but the user no longer holds; we\n * treat those as non-graduating so RPC keeps polling and surfaces any\n * future incoming transfer immediately.\n *\n * `AssetBalance.amount` is already a human-readable decimal string from\n * both AccountsApi (e.g. \"0.283549083429656057\") and the websocket data\n * source (which divides by `decimals` before emitting), so a `Number()`\n * sign check is safe: `NaN`, `undefined`, empty strings, and zero all\n * fail the comparison.\n *\n * @param balance - The balance entry from the response.\n * @returns `true` when the balance amount represents a value greater than 0.\n */\nfunction hasPositiveBalance(balance: AssetBalance | undefined): boolean {\n return Number(balance?.amount) > 0;\n}\n"]}
|