From 9c8028a6da031314111fa4ede3843fd9d36a4b96 Mon Sep 17 00:00:00 2001 From: antoncoding Date: Wed, 15 Apr 2026 20:11:26 +0800 Subject: [PATCH] feat: chronical oracle --- .../MarketOracle/ChainlinkFeedTooltip.tsx | 63 ++++++++++++------- .../oracle/MarketOracle/FeedEntry.tsx | 1 + src/hooks/useOracleMetadata.ts | 3 + src/imgs/oracles/chronicle.svg | 3 + src/utils/oracle.ts | 15 +++++ 5 files changed, 64 insertions(+), 21 deletions(-) create mode 100644 src/imgs/oracles/chronicle.svg diff --git a/src/features/markets/components/oracle/MarketOracle/ChainlinkFeedTooltip.tsx b/src/features/markets/components/oracle/MarketOracle/ChainlinkFeedTooltip.tsx index a66943fe..db8132e9 100644 --- a/src/features/markets/components/oracle/MarketOracle/ChainlinkFeedTooltip.tsx +++ b/src/features/markets/components/oracle/MarketOracle/ChainlinkFeedTooltip.tsx @@ -7,7 +7,14 @@ import { useGlobalModal } from '@/contexts/GlobalModalContext'; import type { EnrichedFeed } from '@/hooks/useOracleMetadata'; import etherscanLogo from '@/imgs/etherscan.png'; import { getExplorerURL } from '@/utils/external'; -import { getChainlinkFeedUrl, OracleVendorIcons, PriceFeedVendors, type FeedFreshnessStatus } from '@/utils/oracle'; +import { + detectFeedVendorFromMetadata, + getChainlinkFeedUrl, + getChronicleFeedUrl, + OracleVendorIcons, + PriceFeedVendors, + type FeedFreshnessStatus, +} from '@/utils/oracle'; import { ChainlinkRiskTiersModal } from './ChainlinkRiskTiersModal'; import { FeedFreshnessSection } from './FeedFreshnessSection'; @@ -39,14 +46,22 @@ function getRiskTierBadge(category: string) { export function ChainlinkFeedTooltip({ feed, chainId, feedFreshness }: ChainlinkFeedTooltipProps) { const { toggleModal, closeModal } = useGlobalModal(); - const baseAsset = feed.pair[0] ?? 'Unknown'; - const quoteAsset = feed.pair[1] ?? 'Unknown'; + const { vendor, assetPair } = detectFeedVendorFromMetadata(feed); + const baseAsset = assetPair.baseAsset; + const quoteAsset = assetPair.quoteAsset; + const isChronicle = vendor === PriceFeedVendors.Chronicle; + const feedTitle = isChronicle ? 'Chronicle Feed Details' : 'Chainlink Feed Details'; + const vendorLabel = isChronicle ? 'Chronicle' : 'Chainlink'; + const intervalValue = isChronicle ? (feed.updateInterval ?? feed.heartbeat) : (feed.heartbeat ?? null); + const deviationValue = isChronicle ? (feed.updateSpread ?? feed.deviationThreshold) : (feed.deviationThreshold ?? null); + const chronicleRiskTier = isChronicle ? feed.riskTier : null; + const chainlinkTier = isChronicle ? null : feed.tier; - const vendorIcon = OracleVendorIcons[PriceFeedVendors.Chainlink]; + const vendorIcon = OracleVendorIcons[vendor] || OracleVendorIcons[PriceFeedVendors.Chainlink]; - const chainlinkUrl = feed.ens ? getChainlinkFeedUrl(chainId, feed.ens) : ''; + const vendorUrl = isChronicle ? getChronicleFeedUrl(baseAsset, quoteAsset) : feed.ens ? getChainlinkFeedUrl(chainId, feed.ens) : ''; - const hasDetails = feed.heartbeat != null || feed.tier != null || feed.deviationThreshold != null; + const hasDetails = intervalValue != null || chainlinkTier != null || chronicleRiskTier != null || deviationValue != null; return (
@@ -56,13 +71,13 @@ export function ChainlinkFeedTooltip({ feed, chainId, feedFreshness }: Chainlink
Chainlink
)} -
Chainlink Feed Details
+
{feedTitle}
{/* Feed pair name */} @@ -72,20 +87,20 @@ export function ChainlinkFeedTooltip({ feed, chainId, feedFreshness }: Chainlink - {/* Chainlink Specific Data */} + {/* Vendor specific data */} {hasDetails && (
- {feed.heartbeat != null && ( + {intervalValue != null && (
- Heartbeat: - {feed.heartbeat}s + {isChronicle ? 'Update Interval:' : 'Heartbeat:'} + {intervalValue}s
)} - {feed.tier != null && ( + {chainlinkTier != null && (
Risk Tier:
- {getRiskTierBadge(feed.tier)} + {getRiskTierBadge(chainlinkTier)}
)} - {feed.deviationThreshold != null && ( + {chronicleRiskTier != null && (
- Deviation Threshold: - {feed.deviationThreshold}% + Risk Tier: + {chronicleRiskTier} +
+ )} + {deviationValue != null && ( +
+ {isChronicle ? 'Update Spread:' : 'Deviation Threshold:'} + {deviationValue}%
)}
@@ -135,9 +156,9 @@ export function ChainlinkFeedTooltip({ feed, chainId, feedFreshness }: Chainlink /> Etherscan - {chainlinkUrl && ( + {vendorUrl && ( )} - Chainlink + {vendorLabel} )} diff --git a/src/features/markets/components/oracle/MarketOracle/FeedEntry.tsx b/src/features/markets/components/oracle/MarketOracle/FeedEntry.tsx index 27801731..df27d11b 100644 --- a/src/features/markets/components/oracle/MarketOracle/FeedEntry.tsx +++ b/src/features/markets/components/oracle/MarketOracle/FeedEntry.tsx @@ -109,6 +109,7 @@ export function FeedEntry({ feed, chainId, feedSnapshotsByAddress }: FeedEntryPr const getTooltipContent = () => { switch (vendor) { case PriceFeedVendors.Chainlink: + case PriceFeedVendors.Chronicle: return ( + + diff --git a/src/utils/oracle.ts b/src/utils/oracle.ts index 3bee975a..a9fb9186 100644 --- a/src/utils/oracle.ts +++ b/src/utils/oracle.ts @@ -39,6 +39,7 @@ export enum OracleType { export enum PriceFeedVendors { Chainlink = 'Chainlink', + Chronicle = 'Chronicle', PythNetwork = 'Pyth Network', Redstone = 'Redstone', Oval = 'Oval', @@ -52,6 +53,7 @@ export enum PriceFeedVendors { export const OracleVendorIcons: Record = { [PriceFeedVendors.Chainlink]: require('../imgs/oracles/chainlink.png') as string, + [PriceFeedVendors.Chronicle]: require('../imgs/oracles/chronicle.svg') as string, [PriceFeedVendors.PythNetwork]: require('../imgs/oracles/pyth.png') as string, [PriceFeedVendors.Redstone]: require('../imgs/oracles/redstone.svg') as string, [PriceFeedVendors.Oval]: require('../imgs/oracles/uma.png') as string, @@ -71,11 +73,13 @@ export function mapProviderToVendor(provider: OracleFeedProvider): PriceFeedVend const normalizedProvider = provider.trim().toLowerCase(); + if (normalizedProvider.includes('chronicle')) return PriceFeedVendors.Chronicle; if (normalizedProvider.includes('pendle')) return PriceFeedVendors.Pendle; if (normalizedProvider.includes('midas')) return PriceFeedVendors.Midas; const mapping: Record = { chainlink: PriceFeedVendors.Chainlink, + chronicle: PriceFeedVendors.Chronicle, redstone: PriceFeedVendors.Redstone, compound: PriceFeedVendors.Compound, lido: PriceFeedVendors.Lido, @@ -108,6 +112,17 @@ export function getChainlinkFeedUrl(chainId: number, ens: string): string { return `https://data.chain.link/feeds/${path}/${ens}`; } +/** + * Generate Chronicle dashboard URL from a feed pair. + */ +export function getChronicleFeedUrl(baseAsset: string, quoteAsset: string): string { + if (!baseAsset || !quoteAsset || baseAsset === 'Unknown' || quoteAsset === 'Unknown') { + return ''; + } + + return `https://chroniclelabs.org/dashboard/oracle/${encodeURIComponent(baseAsset)}/${encodeURIComponent(quoteAsset)}`; +} + export type FeedVendorResult = { vendor: PriceFeedVendors; assetPair: {