diff --git a/src/features/markets/components/markets-table-same-loan.tsx b/src/features/markets/components/markets-table-same-loan.tsx index 948108aa..6b1df590 100644 --- a/src/features/markets/components/markets-table-same-loan.tsx +++ b/src/features/markets/components/markets-table-same-loan.tsx @@ -15,7 +15,7 @@ import { TrustedByCell } from '@/features/autovault/components/trusted-vault-bad import { getVaultKey, type TrustedVault } from '@/constants/vaults/known_vaults'; import { useFreshMarketsState } from '@/hooks/useFreshMarketsState'; import { useModal } from '@/hooks/useModal'; -import { getStandardOracleDataFromMetadata, useAllOracleMetadata } from '@/hooks/useOracleMetadata'; +import { useAllOracleMetadata } from '@/hooks/useOracleMetadata'; import { useRateLabel } from '@/hooks/useRateLabel'; import { useTrustedVaults } from '@/stores/useTrustedVaults'; import { useMarketPreferences } from '@/stores/useMarketPreferences'; @@ -23,7 +23,7 @@ import { useAppSettings } from '@/stores/useAppSettings'; import { formatBalance, formatReadable } from '@/utils/balance'; import { filterMarkets, sortMarkets, createPropertySort } from '@/utils/marketFilters'; import { getViemChain } from '@/utils/networks'; -import { parsePriceFeedVendors, type PriceFeedVendors } from '@/utils/oracle'; +import { getOracleVendorInfo, type PriceFeedVendors } from '@/utils/oracle'; import { convertApyToApr } from '@/utils/rateMath'; import { type ERC20Token, type UnknownERC20Token, infoToKey } from '@/utils/tokens'; import type { Market } from '@/utils/types'; @@ -455,9 +455,7 @@ export function MarketsTableWithSameLoanAsset({ markets.forEach((m) => { if (!m?.market?.morphoBlue?.chain?.id) return; - const vendorInfo = parsePriceFeedVendors( - getStandardOracleDataFromMetadata(oracleMetadataMap, m.market.oracleAddress, m.market.morphoBlue.chain.id), - ); + const vendorInfo = getOracleVendorInfo(m.market.oracleAddress, m.market.morphoBlue.chain.id, oracleMetadataMap); if (vendorInfo?.coreVendors) { vendorInfo.coreVendors.forEach((vendor) => oracleSet.add(vendor)); } diff --git a/src/features/markets/components/oracle-vendor-badge.tsx b/src/features/markets/components/oracle-vendor-badge.tsx index 6fbe181d..de3016f9 100644 --- a/src/features/markets/components/oracle-vendor-badge.tsx +++ b/src/features/markets/components/oracle-vendor-badge.tsx @@ -4,15 +4,8 @@ import React from 'react'; import { Tooltip } from '@/components/ui/tooltip'; import Image from 'next/image'; import { IoWarningOutline, IoHelpCircleOutline, IoCheckmarkCircleOutline } from 'react-icons/io5'; -import { - OracleType, - OracleVendorIcons, - type PriceFeedVendors, - getOracleType, - parseMetaOracleVendors, - parsePriceFeedVendors, -} from '@/utils/oracle'; -import { getMetaOracleDataFromMetadata, getStandardOracleDataFromMetadata, useOracleMetadata } from '@/hooks/useOracleMetadata'; +import { OracleType, OracleVendorIcons, getOracleVendorInfo, type PriceFeedVendors, getOracleType } from '@/utils/oracle'; +import { getStandardOracleDataFromMetadata, useOracleMetadata } from '@/hooks/useOracleMetadata'; type OracleVendorBadgeProps = { chainId: number; @@ -39,7 +32,6 @@ const renderVendorIcon = (vendor: PriceFeedVendors) => function OracleVendorBadge({ chainId, oracleAddress, showText = false, useTooltip = true }: OracleVendorBadgeProps) { const { data: oracleMetadataMap } = useOracleMetadata(chainId); const standardOracleData = getStandardOracleDataFromMetadata(oracleMetadataMap, oracleAddress, chainId); - const metaOracleData = getMetaOracleDataFromMetadata(oracleMetadataMap, oracleAddress, chainId); const oracleType = getOracleType(oracleAddress, chainId, oracleMetadataMap); const isCustom = oracleType === OracleType.Custom; @@ -53,7 +45,7 @@ function OracleVendorBadge({ chainId, oracleAddress, showText = false, useToolti !standardOracleData?.quoteFeedTwo && (standardOracleData?.baseVault || standardOracleData?.quoteVault); - const vendorInfo = isMeta && metaOracleData ? parseMetaOracleVendors(metaOracleData) : parsePriceFeedVendors(standardOracleData); + const vendorInfo = getOracleVendorInfo(oracleAddress, chainId, oracleMetadataMap); const { coreVendors, taggedVendors, hasCompletelyUnknown, hasTaggedUnknown } = vendorInfo; const hasUnknownFeed = hasCompletelyUnknown || hasTaggedUnknown; const displayNames = hasUnknownFeed ? [...coreVendors, ...taggedVendors, 'Unverified'] : [...coreVendors, ...taggedVendors]; diff --git a/src/features/markets/components/oracle/MarketOracle/FeedEntry.tsx b/src/features/markets/components/oracle/MarketOracle/FeedEntry.tsx index a4e0dfd9..27801731 100644 --- a/src/features/markets/components/oracle/MarketOracle/FeedEntry.tsx +++ b/src/features/markets/components/oracle/MarketOracle/FeedEntry.tsx @@ -154,6 +154,7 @@ export function FeedEntry({ feed, chainId, feedSnapshotsByAddress }: FeedEntryPr ); case PriceFeedVendors.PythNetwork: + case PriceFeedVendors.Midas: case PriceFeedVendors.Oval: case PriceFeedVendors.Lido: return ( diff --git a/src/hooks/useOracleMetadata.ts b/src/hooks/useOracleMetadata.ts index 02119ef4..370fa40f 100644 --- a/src/hooks/useOracleMetadata.ts +++ b/src/hooks/useOracleMetadata.ts @@ -173,7 +173,7 @@ function transformToRecord(data: OracleMetadataFile | null | undefined): OracleM */ export function useOracleMetadata(chainId: SupportedNetworks | number | undefined) { return useQuery({ - queryKey: ['oracle-metadata', chainId], + queryKey: ['oracle-metadata', ORACLE_GIST_BASE_URL ?? 'unset', chainId], queryFn: () => (chainId ? fetchOracleMetadata(chainId) : Promise.resolve(null)), select: transformToRecord, enabled: !!chainId, @@ -246,7 +246,7 @@ export function getMetaOracleDataFromMetadata( export function useAllOracleMetadata() { const queries = useQueries({ queries: ALL_SUPPORTED_NETWORKS.map((chainId) => ({ - queryKey: ['oracle-metadata', chainId], + queryKey: ['oracle-metadata', ORACLE_GIST_BASE_URL ?? 'unset', chainId], queryFn: () => fetchOracleMetadata(chainId), staleTime: 1000 * 60 * 30, gcTime: 1000 * 60 * 60, diff --git a/src/imgs/oracles/midas.png b/src/imgs/oracles/midas.png new file mode 100644 index 00000000..128eb5c0 Binary files /dev/null and b/src/imgs/oracles/midas.png differ diff --git a/src/utils/marketFilters.ts b/src/utils/marketFilters.ts index 544221e9..edf3a50f 100644 --- a/src/utils/marketFilters.ts +++ b/src/utils/marketFilters.ts @@ -6,10 +6,10 @@ */ import { LOCKED_MARKET_APY_THRESHOLD } from '@/constants/markets'; -import { type OracleMetadataRecord, getMetaOracleDataFromMetadata, getStandardOracleDataFromMetadata } from '@/hooks/useOracleMetadata'; +import type { OracleMetadataRecord } from '@/hooks/useOracleMetadata'; import { parseNumericThreshold } from '@/utils/markets'; import type { SupportedNetworks } from '@/utils/networks'; -import { parsePriceFeedVendors, parseMetaOracleVendors, type PriceFeedVendors, getOracleType, OracleType } from '@/utils/oracle'; +import { type PriceFeedVendors, getOracleType, getOracleVendorInfo, OracleType } from '@/utils/oracle'; import type { ERC20Token } from '@/utils/tokens'; import type { Market } from '@/utils/types'; @@ -104,23 +104,13 @@ export const createUnknownOracleFilter = (showUnknownOracle: boolean, oracleMeta const chainId = market.morphoBlue.chain.id; const oracleType = getOracleType(market.oracleAddress, chainId, oracleMetadataMap); - if (oracleType === OracleType.Meta) { - const metadata = getMetaOracleDataFromMetadata(oracleMetadataMap, market.oracleAddress, chainId); - if (metadata) { - const info = parseMetaOracleVendors(metadata); - return !info.hasUnknown; - } + if (oracleType === OracleType.Custom) { return false; } - const standardOracleData = getStandardOracleDataFromMetadata(oracleMetadataMap, market.oracleAddress, chainId); - if (!standardOracleData) return false; + const info = getOracleVendorInfo(market.oracleAddress, chainId, oracleMetadataMap); - const info = parsePriceFeedVendors(standardOracleData); - const isCustom = oracleType === OracleType.Custom; - const isUnknown = isCustom || (info?.hasUnknown ?? false); - - return !isUnknown; + return !info.hasUnknown; }; }; @@ -173,9 +163,7 @@ export const createOracleFilter = (selectedOracles: PriceFeedVendors[], oracleMe return () => true; } return (market) => { - const marketOracles = parsePriceFeedVendors( - getStandardOracleDataFromMetadata(oracleMetadataMap, market.oracleAddress, market.morphoBlue.chain.id), - ).vendors; + const marketOracles = getOracleVendorInfo(market.oracleAddress, market.morphoBlue.chain.id, oracleMetadataMap).vendors; return marketOracles.some((oracle) => selectedOracles.includes(oracle)); }; }; @@ -240,9 +228,7 @@ export const createSearchFilter = (searchQuery: string, oracleMetadataMap?: Orac } const lowercaseQuery = searchQuery.toLowerCase().trim(); return (market) => { - const { vendors } = parsePriceFeedVendors( - getStandardOracleDataFromMetadata(oracleMetadataMap, market.oracleAddress, market.morphoBlue.chain.id), - ); + const { vendors } = getOracleVendorInfo(market.oracleAddress, market.morphoBlue.chain.id, oracleMetadataMap); const vendorsName = vendors.join(','); return ( market.uniqueKey.toLowerCase().includes(lowercaseQuery) || diff --git a/src/utils/oracle.ts b/src/utils/oracle.ts index 5fdfdb58..3bee975a 100644 --- a/src/utils/oracle.ts +++ b/src/utils/oracle.ts @@ -46,6 +46,7 @@ export enum PriceFeedVendors { Lido = 'Lido', Pendle = 'Pendle', API3 = 'API3', + Midas = 'Midas', Unknown = 'Unknown', } @@ -58,6 +59,7 @@ export const OracleVendorIcons: Record = { [PriceFeedVendors.Lido]: require('../imgs/oracles/lido.png') as string, [PriceFeedVendors.Pendle]: require('../imgs/oracles/pendle.png') as string, [PriceFeedVendors.API3]: require('../imgs/oracles/api3.svg') as string, + [PriceFeedVendors.Midas]: require('../imgs/oracles/midas.png') as string, [PriceFeedVendors.Unknown]: '', }; @@ -70,6 +72,7 @@ export function mapProviderToVendor(provider: OracleFeedProvider): PriceFeedVend const normalizedProvider = provider.trim().toLowerCase(); if (normalizedProvider.includes('pendle')) return PriceFeedVendors.Pendle; + if (normalizedProvider.includes('midas')) return PriceFeedVendors.Midas; const mapping: Record = { chainlink: PriceFeedVendors.Chainlink, @@ -79,6 +82,7 @@ export function mapProviderToVendor(provider: OracleFeedProvider): PriceFeedVend oval: PriceFeedVendors.Oval, pyth: PriceFeedVendors.PythNetwork, api3: PriceFeedVendors.API3, + midas: PriceFeedVendors.Midas, }; return mapping[normalizedProvider] ?? PriceFeedVendors.Unknown; @@ -182,16 +186,20 @@ export function getOracleType(oracleAddress?: string, chainId?: number, metadata return OracleType.Custom; } +function emptyVendorInfo(): VendorInfo { + return { + coreVendors: [], + taggedVendors: [], + hasCompletelyUnknown: false, + hasTaggedUnknown: false, + vendors: [], + hasUnknown: false, + }; +} + export function parsePriceFeedVendors(oracleData: OracleOutputData | null | undefined): VendorInfo { if (!oracleData) { - return { - coreVendors: [], - taggedVendors: [], - hasCompletelyUnknown: false, - hasTaggedUnknown: false, - vendors: [], - hasUnknown: false, - }; + return emptyVendorInfo(); } const feeds = [oracleData.baseFeedOne, oracleData.baseFeedTwo, oracleData.quoteFeedOne, oracleData.quoteFeedTwo]; @@ -250,6 +258,31 @@ export function parseMetaOracleVendors(metaData: MetaOracleOutputData): VendorIn return classifyEnrichedFeeds(feeds); } +export function getOracleVendorInfo( + oracleAddress: string | undefined, + chainId: number | undefined, + metadataMap: OracleMetadataRecord | undefined, +): VendorInfo { + if (!oracleAddress || !metadataMap) { + return emptyVendorInfo(); + } + + const metadata = getOracleFromMetadata(metadataMap, oracleAddress, chainId); + if (!metadata) { + return emptyVendorInfo(); + } + + if (metadata.type === 'meta') { + return parseMetaOracleVendors(metadata.data); + } + + if (metadata.type === 'standard') { + return parsePriceFeedVendors(metadata.data); + } + + return emptyVendorInfo(); +} + type CheckFeedsPathResult = { isValid: boolean; hasUnknownFeed?: boolean;