Skip to content
Closed
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
27 changes: 18 additions & 9 deletions src/components/shared/kleros-tag-badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type KlerosTagBadgeProps = {
publicNote?: string;
className?: string;
labelClassName?: string;
showTooltip?: boolean;
};

function KlerosBadgeLogo({ size = 14 }: { size?: number }) {
Expand All @@ -33,7 +34,22 @@ function KlerosBadgeLogo({ size = 14 }: { size?: number }) {
);
}

export function KlerosTagBadge({ label, publicNote, className, labelClassName }: KlerosTagBadgeProps) {
export function KlerosTagBadge({ label, publicNote, className, labelClassName, showTooltip = true }: KlerosTagBadgeProps) {
const badge = (
<span
className={clsx('inline-flex min-w-0 items-center gap-1.5', className)}
aria-label={`${label}, tagged by Kleros`}
tabIndex={showTooltip ? 0 : undefined}
>
<KlerosBadgeLogo />
<span className={clsx('min-w-0 truncate', labelClassName)}>{label}</span>
</span>
);

if (!showTooltip) {
return badge;
}

return (
<Tooltip
content={
Expand All @@ -44,14 +60,7 @@ export function KlerosTagBadge({ label, publicNote, className, labelClassName }:
/>
}
>
<span
className={clsx('inline-flex min-w-0 items-center gap-1.5', className)}
aria-label={`${label}, tagged by Kleros`}
tabIndex={0}
>
<KlerosBadgeLogo />
<span className={clsx('min-w-0 truncate', labelClassName)}>{label}</span>
</span>
{badge}
</Tooltip>
);
}
23 changes: 21 additions & 2 deletions src/features/markets/components/oracle/MarketOracle/FeedEntry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import Link from 'next/link';
import type { Address } from 'viem';
import { useReadContracts } from 'wagmi';
import { chainlinkAggregatorV3Abi } from '@/abis/chainlink-aggregator-v3';
import { KlerosTagBadge } from '@/components/shared/kleros-tag-badge';
import { Tooltip } from '@/components/ui/tooltip';
import { formatKlerosAddressTagLabel, type KlerosAddressTag } from '@/data-sources/kleros/address-tags';
import Image from 'next/image';
import { IoIosSwap } from 'react-icons/io';
import { IoHelpCircleOutline } from 'react-icons/io5';
Expand All @@ -29,9 +31,10 @@ type FeedEntryProps = {
feed: EnrichedFeed | null;
chainId: number;
feedSnapshotsByAddress?: FeedSnapshotByAddress;
klerosTag?: KlerosAddressTag | null;
};

export function FeedEntry({ feed, chainId, feedSnapshotsByAddress }: FeedEntryProps): JSX.Element | null {
export function FeedEntry({ feed, chainId, feedSnapshotsByAddress, klerosTag }: FeedEntryProps): JSX.Element | null {
const feedVendorResult = useMemo(() => {
return detectFeedVendorFromMetadata(feed);
}, [feed]);
Expand Down Expand Up @@ -84,6 +87,8 @@ export function FeedEntry({ feed, chainId, feedSnapshotsByAddress }: FeedEntryPr

// Don't show asset pair if it's unknown
const showAssetPair = !(assetPair.baseAsset === 'Unknown' && assetPair.quoteAsset === 'Unknown');
const klerosLabel = formatKlerosAddressTagLabel(klerosTag);
const klerosFallbackLabel = showAssetPair ? undefined : klerosLabel;

const vendorIcon = OracleVendorIcons[vendor];
const hasKnownVendorIcon = vendor !== PriceFeedVendors.Unknown && Boolean(vendorIcon);
Expand Down Expand Up @@ -164,6 +169,7 @@ export function FeedEntry({ feed, chainId, feedSnapshotsByAddress }: FeedEntryPr
feed={feed}
chainId={chainId}
feedFreshness={freshness}
klerosTag={klerosTag}
/>
);

Expand All @@ -174,6 +180,7 @@ export function FeedEntry({ feed, chainId, feedSnapshotsByAddress }: FeedEntryPr
feed={feed}
chainId={chainId}
feedFreshness={freshness}
klerosTag={klerosTag}
/>
);
}
Expand All @@ -182,6 +189,7 @@ export function FeedEntry({ feed, chainId, feedSnapshotsByAddress }: FeedEntryPr
feed={feed}
chainId={chainId}
feedFreshness={freshness}
klerosTag={klerosTag}
/>
);

Expand All @@ -191,6 +199,7 @@ export function FeedEntry({ feed, chainId, feedSnapshotsByAddress }: FeedEntryPr
feed={feed}
chainId={chainId}
feedFreshness={freshness}
klerosTag={klerosTag}
/>
);
}
Expand All @@ -217,7 +226,17 @@ export function FeedEntry({ feed, chainId, feedSnapshotsByAddress }: FeedEntryPr
</div>
) : (
<div className="flex min-w-0 flex-1 items-center gap-1">
<span className="text-xs font-medium text-gray-500">Unknown Feed</span>
{klerosFallbackLabel ? (
<KlerosTagBadge
label={klerosFallbackLabel}
publicNote={klerosTag?.publicNote}
className="text-gray-600 dark:text-gray-400"
labelClassName="max-w-[8rem] text-xs font-medium"
showTooltip={false}
/>
) : (
<span className="text-xs font-medium text-gray-500">Unknown Feed</span>
)}
</div>
)}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Image from 'next/image';
import Link from 'next/link';
import type { Address } from 'viem';
import { formatKlerosAddressTagLabel, type KlerosAddressTag } from '@/data-sources/kleros/address-tags';
import type { EnrichedFeed } from '@/hooks/useOracleMetadata';
import etherscanLogo from '@/imgs/etherscan.png';
import { getExplorerURL } from '@/utils/external';
Expand All @@ -12,14 +13,18 @@ type GeneralFeedTooltipProps = {
feed: EnrichedFeed;
chainId: number;
feedFreshness?: FeedFreshnessStatus;
klerosTag?: KlerosAddressTag | null;
};

export function GeneralFeedTooltip({ feed, chainId, feedFreshness }: GeneralFeedTooltipProps) {
export function GeneralFeedTooltip({ feed, chainId, feedFreshness, klerosTag }: GeneralFeedTooltipProps) {
const baseAsset = feed.pair[0] ?? 'Unknown';
const quoteAsset = feed.pair[1] ?? 'Unknown';
const showAssetPair = !(baseAsset === 'Unknown' && quoteAsset === 'Unknown');

const vendor = feed.provider ? mapProviderToVendor(feed.provider) : PriceFeedVendors.Unknown;
const vendorIcon = OracleVendorIcons[vendor] || OracleVendorIcons[PriceFeedVendors.Unknown];
const klerosLabel = formatKlerosAddressTagLabel(klerosTag);
const showKlerosFallback = vendor === PriceFeedVendors.Unknown && Boolean(klerosLabel);

return (
<div className="flex w-fit max-w-[22rem] flex-col gap-3">
Expand All @@ -35,15 +40,20 @@ export function GeneralFeedTooltip({ feed, chainId, feedFreshness }: GeneralFeed
/>
</div>
)}
<div className="font-zen font-bold">{feed.provider ?? 'Price'} Feed</div>
<div>
<div className="font-zen font-bold">{showKlerosFallback ? klerosLabel : `${feed.provider ?? 'Price'} Feed`}</div>
{showKlerosFallback && <div className="font-zen text-xs text-secondary">Name tag from Kleros Scout</div>}
</div>
</div>

{/* Feed pair name */}
<div className="flex items-center gap-2">
<div className="font-zen text-base font-semibold text-gray-800 dark:text-gray-200">
{baseAsset} / {quoteAsset}
{showAssetPair && (
<div className="flex items-center gap-2">
<div className="font-zen text-base font-semibold text-gray-800 dark:text-gray-200">
{baseAsset} / {quoteAsset}
</div>
</div>
</div>
)}

<FeedTypeSection feed={feed} />

Expand Down Expand Up @@ -75,6 +85,16 @@ export function GeneralFeedTooltip({ feed, chainId, feedFreshness }: GeneralFeed
/>
Explorer
</Link>
{showKlerosFallback && klerosTag?.dataOriginLink && (
<Link
href={klerosTag.dataOriginLink}
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"
>
View Tag Source
</Link>
)}
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
'use client';

import { useMemo } from 'react';
import { getKlerosAddressTagKey } from '@/data-sources/kleros/address-tags';
import { useKlerosAddressTagsQuery } from '@/hooks/queries/useKlerosAddressTagsQuery';
import { useFeedLastUpdatedByChain } from '@/hooks/useFeedLastUpdatedByChain';
import { getStandardOracleDataFromMetadata, useOracleMetadata } from '@/hooks/useOracleMetadata';
import { FeedEntry } from './FeedEntry';
Expand All @@ -21,6 +24,15 @@ export function MarketOracleFeedInfo({ chainId, oracleAddress }: MarketOracleFee
const baseFeedTwo = oracleData?.baseFeedTwo ?? null;
const quoteFeedOne = oracleData?.quoteFeedOne ?? null;
const quoteFeedTwo = oracleData?.quoteFeedTwo ?? null;
const feedAddresses = useMemo(
() =>
[baseFeedOne?.address, baseFeedTwo?.address, quoteFeedOne?.address, quoteFeedTwo?.address].filter(
(address): address is string => Boolean(address),
),
[baseFeedOne?.address, baseFeedTwo?.address, quoteFeedOne?.address, quoteFeedTwo?.address],
);
// Batch Kleros tags for the visible oracle feeds; FeedEntry only renders them as fallback identity for unclassified feeds.
const { data: klerosAddressTags } = useKlerosAddressTagsQuery(chainId, feedAddresses);

const hasAnyFeed = baseFeedOne || baseFeedTwo || quoteFeedOne || quoteFeedTwo;
const hasAnyVault = baseVault || quoteVault;
Expand All @@ -46,13 +58,15 @@ export function MarketOracleFeedInfo({ chainId, oracleAddress }: MarketOracleFee
feed={baseFeedOne}
chainId={chainId}
feedSnapshotsByAddress={feedSnapshotsByAddress}
klerosTag={klerosAddressTags?.[getKlerosAddressTagKey(chainId, baseFeedOne.address)]}
/>
)}
{baseFeedTwo && (
<FeedEntry
feed={baseFeedTwo}
chainId={chainId}
feedSnapshotsByAddress={feedSnapshotsByAddress}
klerosTag={klerosAddressTags?.[getKlerosAddressTagKey(chainId, baseFeedTwo.address)]}
/>
)}
</div>
Expand All @@ -74,13 +88,15 @@ export function MarketOracleFeedInfo({ chainId, oracleAddress }: MarketOracleFee
feed={quoteFeedOne}
chainId={chainId}
feedSnapshotsByAddress={feedSnapshotsByAddress}
klerosTag={klerosAddressTags?.[getKlerosAddressTagKey(chainId, quoteFeedOne.address)]}
/>
)}
{quoteFeedTwo && (
<FeedEntry
feed={quoteFeedTwo}
chainId={chainId}
feedSnapshotsByAddress={feedSnapshotsByAddress}
klerosTag={klerosAddressTags?.[getKlerosAddressTagKey(chainId, quoteFeedTwo.address)]}
/>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
'use client';

import { useMemo } from 'react';
import { useFeedLastUpdatedByChain, type FeedSnapshotByAddress } from '@/hooks/useFeedLastUpdatedByChain';
import { getMetaOracleDataFromMetadata, type OracleOutputData, useOracleMetadata } from '@/hooks/useOracleMetadata';
import { AddressIdentity } from '@/components/shared/address-identity';
import { KlerosTagBadge } from '@/components/shared/kleros-tag-badge';
import { formatKlerosAddressTagLabel, getKlerosAddressTagKey, type KlerosAddressTagsByKey } from '@/data-sources/kleros/address-tags';
import { formatOracleDuration } from '@/utils/oracle';
import { useKlerosAddressTagsQuery } from '@/hooks/queries/useKlerosAddressTagsQuery';
import { FeedEntry } from './FeedEntry';
import { VaultEntry } from './VaultEntry';

Expand All @@ -13,16 +17,24 @@ type MetaOracleInfoProps = {
variant?: 'summary' | 'detail';
};

function getOracleFeedAddresses(oracleData: OracleOutputData | null): string[] {
return [oracleData?.baseFeedOne?.address, oracleData?.baseFeedTwo?.address, oracleData?.quoteFeedOne?.address, oracleData?.quoteFeedTwo?.address].filter(
(address): address is string => Boolean(address),
);
}

function OracleFeedSection({
oracleData,
chainId,
label,
feedSnapshotsByAddress,
klerosAddressTags,
}: {
oracleData: OracleOutputData | null;
chainId: number;
label: string;
feedSnapshotsByAddress: FeedSnapshotByAddress;
klerosAddressTags?: KlerosAddressTagsByKey;
}) {
if (!oracleData) return null;

Expand Down Expand Up @@ -57,6 +69,7 @@ function OracleFeedSection({
feed={enrichedFeed}
chainId={chainId}
feedSnapshotsByAddress={feedSnapshotsByAddress}
klerosTag={klerosAddressTags?.[getKlerosAddressTagKey(chainId, enrichedFeed.address)]}
/>
);
})}
Expand All @@ -73,8 +86,24 @@ export function MetaOracleInfo({ oracleAddress, chainId, variant = 'summary' }:
const { data: feedSnapshotsByAddress } = useFeedLastUpdatedByChain(chainId);

const metaData = getMetaOracleDataFromMetadata(oracleMetadataMap, oracleAddress, chainId);
const isPrimaryActive = Boolean(metaData?.currentOracle?.toLowerCase() === metaData?.primaryOracle?.toLowerCase());
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for isPrimaryActive is fragile when metaData is null or undefined. Due to optional chaining, undefined === undefined evaluates to true, which means isPrimaryActive incorrectly defaults to true while metadata is still loading. Additionally, the Boolean() wrapper is redundant as the comparison already returns a boolean value. It's clearer to explicitly check for metaData.

Suggested change
const isPrimaryActive = Boolean(metaData?.currentOracle?.toLowerCase() === metaData?.primaryOracle?.toLowerCase());
const isPrimaryActive = metaData ? metaData.currentOracle?.toLowerCase() === metaData.primaryOracle?.toLowerCase() : false;

const feedAddresses = useMemo(() => {
if (!metaData) return [];

const activeOracleData = isPrimaryActive ? metaData.oracleSources.primary : metaData.oracleSources.backup;
const oracleSources = variant === 'detail' ? [metaData.oracleSources.primary, metaData.oracleSources.backup] : [activeOracleData];
const oracleContractAddresses = variant === 'detail' ? [metaData.primaryOracle, metaData.backupOracle] : [];

return [...oracleContractAddresses, ...oracleSources.flatMap(getOracleFeedAddresses)];
}, [isPrimaryActive, metaData, variant]);
// Batch Kleros tags for the meta oracle contracts and feeds currently rendered in this view.
const { data: klerosAddressTags } = useKlerosAddressTagsQuery(chainId, feedAddresses);

if (!metaData) return null;
const isPrimaryActive = metaData.currentOracle?.toLowerCase() === metaData.primaryOracle?.toLowerCase();
const primaryOracleTag = klerosAddressTags?.[getKlerosAddressTagKey(chainId, metaData.primaryOracle)];
const primaryOracleLabel = formatKlerosAddressTagLabel(primaryOracleTag);
const backupOracleTag = klerosAddressTags?.[getKlerosAddressTagKey(chainId, metaData.backupOracle)];
const backupOracleLabel = formatKlerosAddressTagLabel(backupOracleTag);

if (variant === 'detail') {
const deviationPct = (Number(metaData.deviationThreshold) / 1e18) * 100;
Expand All @@ -89,6 +118,14 @@ export function MetaOracleInfo({ oracleAddress, chainId, variant = 'summary' }:
address={metaData.primaryOracle}
chainId={chainId}
/>
{primaryOracleLabel && (
<span className="inline-flex max-w-[12rem] rounded-sm bg-hovered px-2 py-1 font-zen text-xs text-secondary">
<KlerosTagBadge
label={primaryOracleLabel}
publicNote={primaryOracleTag?.publicNote}
/>
</span>
)}
{isPrimaryActive && (
<span className="rounded-sm bg-green-100 px-1.5 py-0.5 text-[10px] font-medium text-green-800 dark:bg-green-400/10 dark:text-green-300">
Active
Expand All @@ -100,6 +137,7 @@ export function MetaOracleInfo({ oracleAddress, chainId, variant = 'summary' }:
chainId={chainId}
label="primary"
feedSnapshotsByAddress={feedSnapshotsByAddress}
klerosAddressTags={klerosAddressTags}
/>
</div>

Expand All @@ -111,6 +149,14 @@ export function MetaOracleInfo({ oracleAddress, chainId, variant = 'summary' }:
address={metaData.backupOracle}
chainId={chainId}
/>
{backupOracleLabel && (
<span className="inline-flex max-w-[12rem] rounded-sm bg-hovered px-2 py-1 font-zen text-xs text-secondary">
<KlerosTagBadge
label={backupOracleLabel}
publicNote={backupOracleTag?.publicNote}
/>
</span>
)}
{!isPrimaryActive && (
<span className="rounded-sm bg-green-100 px-1.5 py-0.5 text-[10px] font-medium text-green-800 dark:bg-green-400/10 dark:text-green-300">
Active
Expand All @@ -122,6 +168,7 @@ export function MetaOracleInfo({ oracleAddress, chainId, variant = 'summary' }:
chainId={chainId}
label="backup"
feedSnapshotsByAddress={feedSnapshotsByAddress}
klerosAddressTags={klerosAddressTags}
/>
</div>

Expand Down Expand Up @@ -154,6 +201,7 @@ export function MetaOracleInfo({ oracleAddress, chainId, variant = 'summary' }:
chainId={chainId}
label="current"
feedSnapshotsByAddress={feedSnapshotsByAddress}
klerosAddressTags={klerosAddressTags}
/>
);
}
Loading