From 0be15d9a08b59ebe5578ced865637ea63203e980 Mon Sep 17 00:00:00 2001 From: antoncoding Date: Tue, 7 Apr 2026 18:16:50 +0800 Subject: [PATCH] feat; midas oracles --- .../components/markets-table-same-loan.tsx | 8 ++- .../components/oracle-vendor-badge.tsx | 14 ++--- .../oracle/MarketOracle/FeedEntry.tsx | 1 + src/hooks/useOracleMetadata.ts | 4 +- src/imgs/oracles/midas.png | Bin 0 -> 4974 bytes src/utils/marketFilters.ts | 28 +++------- src/utils/oracle.ts | 49 +++++++++++++++--- 7 files changed, 57 insertions(+), 47 deletions(-) create mode 100644 src/imgs/oracles/midas.png 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 0000000000000000000000000000000000000000..128eb5c0f6eefd598a6faaa948383f9bdbbc4eeb GIT binary patch literal 4974 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 nYj342@Q7<)p3=5e#GF&!7H~y`>@YvSe7(|A*H 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;