diff --git a/src/features/markets/components/oracle-vendor-badge.tsx b/src/features/markets/components/oracle-vendor-badge.tsx index 2a98e833..c1127fee 100644 --- a/src/features/markets/components/oracle-vendor-badge.tsx +++ b/src/features/markets/components/oracle-vendor-badge.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { Tooltip } from '@/components/ui/tooltip'; import Image from 'next/image'; -import { IoWarningOutline, IoHelpCircleOutline } from 'react-icons/io5'; +import { IoWarningOutline, IoHelpCircleOutline, IoCheckmarkCircleOutline } from 'react-icons/io5'; import { OracleType, OracleVendorIcons, @@ -50,6 +50,17 @@ function OracleVendorBadge({ oracleData, chainId, oracleAddress, showText = fals const isCustom = oracleType === OracleType.Custom; const isMeta = oracleType === OracleType.Meta; + // Check if this is a vault-only oracle (no feeds, only vault conversion) + const oracleMetadata = oracleMetadataMap && oracleAddress ? getOracleFromMetadata(oracleMetadataMap, oracleAddress) : undefined; + const oracleMetadataData = oracleMetadata?.data && !isMetaOracleData(oracleMetadata.data) ? oracleMetadata.data : undefined; + const isVaultOnly = + oracleType === OracleType.Standard && + !oracleMetadataData?.baseFeedOne && + !oracleMetadataData?.baseFeedTwo && + !oracleMetadataData?.quoteFeedOne && + !oracleMetadataData?.quoteFeedTwo && + (oracleMetadataData?.baseVault || oracleMetadataData?.quoteVault); + const vendorInfo = (() => { if (isMeta && oracleMetadataMap && oracleAddress) { const metadata = getOracleFromMetadata(oracleMetadataMap, oracleAddress); @@ -72,6 +83,12 @@ function OracleVendorBadge({ oracleData, chainId, oracleAddress, showText = fals className="text-secondary" size={16} /> + ) : isVaultOnly ? ( + // Vault-only oracle - show checkmark icon + ) : hasCompletelyUnknown || hasTaggedUnknown ? ( // Show core vendor icons plus question mark for any unknown types <> @@ -101,6 +118,16 @@ function OracleVendorBadge({ oracleData, chainId, oracleAddress, showText = fals ); } + // Vault-only oracle - special case + if (isVaultOnly) { + return ( +
+

Standard Oracle

+

Uses onchain vault contract for price conversion.

+
+ ); + } + const oracleLabel = isMeta ? 'Meta Oracle' : 'Standard Oracle'; if (hasCompletelyUnknown || hasTaggedUnknown) { diff --git a/src/hooks/useMarketWarnings.ts b/src/hooks/useMarketWarnings.ts index 5ba02b62..fd83267d 100644 --- a/src/hooks/useMarketWarnings.ts +++ b/src/hooks/useMarketWarnings.ts @@ -1,23 +1,41 @@ import { useMemo } from 'react'; import { useOracleMetadata, type OracleMetadataRecord } from '@/hooks/useOracleMetadata'; +import { useTokensQuery } from '@/hooks/queries/useTokensQuery'; import type { Market, WarningWithDetail } from '@/utils/types'; import { getMarketWarningsWithDetail } from '@/utils/warnings'; /** * Hook to compute market warnings with details on-demand * Uses oracle metadata when available for accurate feed detection + * Uses dynamic token list to filter out false "unrecognized asset" warnings */ export const useMarketWarnings = (market: Market | null | undefined): WarningWithDetail[] => { const chainId = market?.morphoBlue?.chain?.id; const { data: oracleMetadataMap } = useOracleMetadata(chainId); + const { findToken } = useTokensQuery(); return useMemo(() => { if (!market) return []; - return getMarketWarningsWithDetail(market, { + + const warnings = getMarketWarningsWithDetail(market, { considerWhitelist: true, oracleMetadataMap, }); - }, [market, oracleMetadataMap]); + + // Filter out false "unrecognized asset" warnings + // The subgraph fetcher only checks static token list, but we have dynamic tokens too (Pendle, etc.) + return warnings.filter((warning) => { + if (warning.code === 'unrecognized_loan_asset' && market.loanAsset?.address) { + const found = findToken(market.loanAsset.address, chainId ?? 0); + if (found) return false; // Token found in dynamic list, remove warning + } + if (warning.code === 'unrecognized_collateral_asset' && market.collateralAsset?.address) { + const found = findToken(market.collateralAsset.address, chainId ?? 0); + if (found) return false; // Token found in dynamic list, remove warning + } + return true; + }); + }, [market, oracleMetadataMap, findToken, chainId]); }; /** diff --git a/src/utils/markets.ts b/src/utils/markets.ts index 9a030dce..6fed0c14 100644 --- a/src/utils/markets.ts +++ b/src/utils/markets.ts @@ -40,10 +40,6 @@ export const monarchWhitelistedMarkets: WhitelistMarketData[] = [ id: '0x74918a8744b4a48d233e66d0f6a318ef847cc4da2910357897f94a33c3481280', // sPinto/USDC by Pinto offsetWarnings: ['unrecognized_collateral_asset'], }, - { - id: '0x9bc98c2f20ac58287ef2c860eea53a2fdc27c17a7817ff1206c0b7840cc7cd79', // Morpho API stopped tracking PT markets - offsetWarnings: ['unrecognized_collateral_asset'], - }, ]; // Market override rules - group multiple markets under the same rule diff --git a/src/utils/oracle.ts b/src/utils/oracle.ts index 619dd22c..b8a962c8 100644 --- a/src/utils/oracle.ts +++ b/src/utils/oracle.ts @@ -245,10 +245,11 @@ export function getOracleType( chainId?: number, metadataMap?: OracleMetadataRecord, ) { - // Check scanner metadata for meta oracle type + // Check scanner metadata for oracle type (meta or standard with vault-only) if (metadataMap && oracleAddress) { const metadata = getOracleFromMetadata(metadataMap, oracleAddress); if (metadata?.type === 'meta') return OracleType.Meta; + if (metadata?.type === 'standard') return OracleType.Standard; } // Morpho API only contains oracleData if it follows the standard MorphoOracle structure with feeds @@ -289,6 +290,24 @@ export function parsePriceFeedVendors( } if (!oracleData.baseFeedOne && !oracleData.baseFeedTwo && !oracleData.quoteFeedOne && !oracleData.quoteFeedTwo) { + // Check if this is a vault-only oracle (no feeds but has vault conversion) + const oracleMetadata = + options?.metadataMap && options.oracleAddress ? getOracleFromMetadata(options.metadataMap, options.oracleAddress) : undefined; + const oracleMetadataData = oracleMetadata?.data && !isMetaOracleData(oracleMetadata.data) ? oracleMetadata.data : undefined; + const hasVault = oracleMetadataData?.baseVault || oracleMetadataData?.quoteVault; + + // Vault-only oracles are valid — don't mark as unknown + if (hasVault) { + return { + coreVendors: [], + taggedVendors: [], + hasCompletelyUnknown: false, + hasTaggedUnknown: false, + vendors: [], + hasUnknown: false, + }; + } + return { coreVendors: [], taggedVendors: [],