Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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 (
<div className="flex w-fit max-w-[22rem] flex-col gap-3">
Expand All @@ -56,13 +71,13 @@ export function ChainlinkFeedTooltip({ feed, chainId, feedFreshness }: Chainlink
<div className="flex-shrink-0">
<Image
src={vendorIcon}
alt="Chainlink"
alt={vendorLabel}
width={16}
height={16}
/>
</div>
)}
<div className="font-zen font-bold">Chainlink Feed Details</div>
<div className="font-zen font-bold">{feedTitle}</div>
</div>

{/* Feed pair name */}
Expand All @@ -72,20 +87,20 @@ export function ChainlinkFeedTooltip({ feed, chainId, feedFreshness }: Chainlink
</div>
</div>

{/* Chainlink Specific Data */}
{/* Vendor specific data */}
{hasDetails && (
<div className="space-y-2 border-t border-gray-200/30 pt-3 dark:border-gray-600/20">
{feed.heartbeat != null && (
{intervalValue != null && (
<div className="flex justify-between font-zen text-sm">
<span className="text-gray-600 dark:text-gray-400">Heartbeat:</span>
<span className="font-medium">{feed.heartbeat}s</span>
<span className="text-gray-600 dark:text-gray-400">{isChronicle ? 'Update Interval:' : 'Heartbeat:'}</span>
<span className="font-medium">{intervalValue}s</span>
</div>
)}
{feed.tier != null && (
{chainlinkTier != null && (
<div className="flex items-center justify-between font-zen text-sm">
<span className="text-gray-600 dark:text-gray-400">Risk Tier:</span>
<div className="flex items-center gap-1">
{getRiskTierBadge(feed.tier)}
{getRiskTierBadge(chainlinkTier)}
<button
onClick={(e) => {
e.preventDefault();
Expand All @@ -106,10 +121,16 @@ export function ChainlinkFeedTooltip({ feed, chainId, feedFreshness }: Chainlink
</div>
</div>
)}
{feed.deviationThreshold != null && (
{chronicleRiskTier != null && (
<div className="flex justify-between font-zen text-sm">
<span className="text-gray-600 dark:text-gray-400">Deviation Threshold:</span>
<span className="font-medium">{feed.deviationThreshold}%</span>
<span className="text-gray-600 dark:text-gray-400">Risk Tier:</span>
<span className="font-medium">{chronicleRiskTier}</span>
</div>
)}
{deviationValue != null && (
<div className="flex justify-between font-zen text-sm">
<span className="text-gray-600 dark:text-gray-400">{isChronicle ? 'Update Spread:' : 'Deviation Threshold:'}</span>
<span className="font-medium">{deviationValue}%</span>
</div>
)}
</div>
Expand All @@ -135,22 +156,22 @@ export function ChainlinkFeedTooltip({ feed, chainId, feedFreshness }: Chainlink
/>
Etherscan
</Link>
{chainlinkUrl && (
{vendorUrl && (
<Link
href={chainlinkUrl}
href={vendorUrl}
target="_blank"
rel="noopener noreferrer"
className="bg-hovered flex items-center gap-1 rounded-sm px-3 py-2 text-xs font-medium text-primary no-underline transition-all duration-200 hover:bg-opacity-80"
>
{vendorIcon && (
<Image
src={vendorIcon}
alt="Chainlink"
alt={vendorLabel}
width={12}
height={12}
/>
)}
Chainlink
{vendorLabel}
</Link>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ export function FeedEntry({ feed, chainId, feedSnapshotsByAddress }: FeedEntryPr
const getTooltipContent = () => {
switch (vendor) {
case PriceFeedVendors.Chainlink:
case PriceFeedVendors.Chronicle:
return (
<ChainlinkFeedTooltip
feed={feed}
Expand Down
3 changes: 3 additions & 0 deletions src/hooks/useOracleMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ export type EnrichedFeed = {
heartbeat?: number;
deviationThreshold?: number;
ens?: string; // Chainlink ENS name for feed URL (e.g. "eth-usd")
riskTier?: number; // Chronicle dashboard risk tier
updateInterval?: number; // Chronicle update cadence in seconds
updateSpread?: number; // Chronicle deviation threshold percentage
feedType?: string; // Redstone feed type: "market" or "fundamental"
baseDiscountPerYear?: string; // Pendle base discount per year (raw 18-decimal value)
innerOracle?: string; // Pendle inner oracle address
Expand Down
3 changes: 3 additions & 0 deletions src/imgs/oracles/chronicle.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions src/utils/oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export enum OracleType {

export enum PriceFeedVendors {
Chainlink = 'Chainlink',
Chronicle = 'Chronicle',
PythNetwork = 'Pyth Network',
Redstone = 'Redstone',
Oval = 'Oval',
Expand All @@ -52,6 +53,7 @@ export enum PriceFeedVendors {

export const OracleVendorIcons: Record<PriceFeedVendors, string> = {
[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,
Expand All @@ -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<string, PriceFeedVendors> = {
chainlink: PriceFeedVendors.Chainlink,
chronicle: PriceFeedVendors.Chronicle,
redstone: PriceFeedVendors.Redstone,
compound: PriceFeedVendors.Compound,
lido: PriceFeedVendors.Lido,
Expand Down Expand Up @@ -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: {
Expand Down