|
|
@@ -17,26 +17,28 @@ import type { ReactNode } from "react";
|
|
|
|
|
|
import { ActiveFeedsCard } from "./active-feeds-card";
|
|
|
import { ChartCard } from "./chart-card";
|
|
|
+import { getPriceFeeds } from "./get-price-feeds";
|
|
|
import styles from "./layout.module.scss";
|
|
|
-import { MedianScoreHistory } from "./median-score-history";
|
|
|
import { OisApyHistory } from "./ois-apy-history";
|
|
|
+import { PriceFeedDrawerProvider } from "./price-feed-drawer-provider";
|
|
|
import {
|
|
|
- getPublishers,
|
|
|
- getPublisherFeeds,
|
|
|
getPublisherRankingHistory,
|
|
|
getPublisherMedianScoreHistory,
|
|
|
} from "../../services/clickhouse";
|
|
|
import { getPublisherCaps } from "../../services/hermes";
|
|
|
-import { getTotalFeedCount } from "../../services/pyth";
|
|
|
+import { Cluster, getTotalFeedCount } from "../../services/pyth";
|
|
|
import { getPublisherPoolData } from "../../services/staking";
|
|
|
+import { Status } from "../../status";
|
|
|
import { ChangeValue } from "../ChangeValue";
|
|
|
import { FormattedDate } from "../FormattedDate";
|
|
|
import { FormattedNumber } from "../FormattedNumber";
|
|
|
import { FormattedTokens } from "../FormattedTokens";
|
|
|
import { Meter } from "../Meter";
|
|
|
+import { PriceFeedIcon } from "../PriceFeedIcon";
|
|
|
import { PublisherIcon } from "../PublisherIcon";
|
|
|
import { PublisherKey } from "../PublisherKey";
|
|
|
import { PublisherTag } from "../PublisherTag";
|
|
|
+import { ScoreHistory } from "../ScoreHistory";
|
|
|
import { SemicircleMeter } from "../SemicircleMeter";
|
|
|
import { TabPanel, TabRoot, Tabs } from "../Tabs";
|
|
|
import { TokenIcon } from "../TokenIcon";
|
|
|
@@ -51,23 +53,19 @@ type Props = {
|
|
|
export const PublishersLayout = async ({ children, params }: Props) => {
|
|
|
const { key } = await params;
|
|
|
const [
|
|
|
- publishers,
|
|
|
rankingHistory,
|
|
|
medianScoreHistory,
|
|
|
totalFeedsCount,
|
|
|
oisStats,
|
|
|
- publisherFeeds,
|
|
|
+ priceFeeds,
|
|
|
] = await Promise.all([
|
|
|
- getPublishers(),
|
|
|
getPublisherRankingHistory(key),
|
|
|
getPublisherMedianScoreHistory(key),
|
|
|
- getTotalFeedCount(),
|
|
|
+ getTotalFeedCount(Cluster.Pythnet),
|
|
|
getOisStats(key),
|
|
|
- getPublisherFeeds(key),
|
|
|
+ getPriceFeeds(Cluster.Pythnet, key),
|
|
|
]);
|
|
|
|
|
|
- const publisher = publishers.find((publisher) => publisher.key === key);
|
|
|
-
|
|
|
const currentRanking = rankingHistory.at(-1);
|
|
|
const previousRanking = rankingHistory.at(-2);
|
|
|
|
|
|
@@ -75,307 +73,326 @@ export const PublishersLayout = async ({ children, params }: Props) => {
|
|
|
const previousMedianScore = medianScoreHistory.at(-2);
|
|
|
const knownPublisher = lookup(key);
|
|
|
|
|
|
- return currentRanking && currentMedianScore && publisher ? (
|
|
|
- <div className={styles.publisherLayout}>
|
|
|
- <section className={styles.header}>
|
|
|
- <div className={styles.headerRow}>
|
|
|
- <Breadcrumbs
|
|
|
- className={styles.breadcrumbs ?? ""}
|
|
|
- label="Breadcrumbs"
|
|
|
- items={[
|
|
|
- { href: "/", label: "Home" },
|
|
|
- { href: "/publishers", label: "Publishers" },
|
|
|
- { label: <PublisherKey size="sm" publisherKey={key} /> },
|
|
|
- ]}
|
|
|
- />
|
|
|
- </div>
|
|
|
- <div className={styles.headerRow}>
|
|
|
- <PublisherTag
|
|
|
- publisherKey={key}
|
|
|
- {...(knownPublisher && {
|
|
|
- name: knownPublisher.name,
|
|
|
- icon: <PublisherIcon knownPublisher={knownPublisher} />,
|
|
|
- })}
|
|
|
- />
|
|
|
- </div>
|
|
|
- <section className={styles.stats}>
|
|
|
- <ChartCard
|
|
|
- variant="primary"
|
|
|
- header="Publisher Ranking"
|
|
|
- lineClassName={styles.primarySparkChartLine}
|
|
|
- corner={
|
|
|
- <AlertTrigger>
|
|
|
- <Button
|
|
|
- variant="ghost"
|
|
|
- size="xs"
|
|
|
- beforeIcon={(props) => <Info weight="fill" {...props} />}
|
|
|
- rounded
|
|
|
- hideText
|
|
|
- className={styles.publisherRankingExplainButton ?? ""}
|
|
|
- >
|
|
|
- Explain Publisher Ranking
|
|
|
- </Button>
|
|
|
- <Alert title="Publisher Ranking" icon={<Lightbulb />}>
|
|
|
- <p className={styles.publisherRankingExplainDescription}>
|
|
|
- Each <b>Publisher</b> receives a <b>Ranking</b> which is
|
|
|
- derived from the number of price feeds the <b>Publisher</b>{" "}
|
|
|
- is actively publishing.
|
|
|
- </p>
|
|
|
- </Alert>
|
|
|
- </AlertTrigger>
|
|
|
- }
|
|
|
- data={rankingHistory.map(({ timestamp, rank }) => ({
|
|
|
- x: timestamp,
|
|
|
- y: rank,
|
|
|
- displayX: (
|
|
|
- <span className={styles.activeDate}>
|
|
|
- <FormattedDate value={timestamp} />
|
|
|
- </span>
|
|
|
- ),
|
|
|
- }))}
|
|
|
- stat={currentRanking.rank}
|
|
|
- {...(previousRanking && {
|
|
|
- miniStat: (
|
|
|
- <ChangeValue
|
|
|
- direction={getChangeDirection(
|
|
|
- currentRanking.rank,
|
|
|
- previousRanking.rank,
|
|
|
- )}
|
|
|
- >
|
|
|
- {Math.abs(currentRanking.rank - previousRanking.rank)}
|
|
|
- </ChangeValue>
|
|
|
- ),
|
|
|
- })}
|
|
|
- />
|
|
|
- <DrawerTrigger>
|
|
|
+ return currentRanking && currentMedianScore ? (
|
|
|
+ <PriceFeedDrawerProvider
|
|
|
+ publisherKey={key}
|
|
|
+ priceFeeds={priceFeeds.map(({ feed, ranking, status }) => ({
|
|
|
+ symbol: feed.symbol,
|
|
|
+ displaySymbol: feed.product.display_symbol,
|
|
|
+ description: feed.product.description,
|
|
|
+ icon: <PriceFeedIcon symbol={feed.product.display_symbol} />,
|
|
|
+ feedKey: feed.product.price_account,
|
|
|
+ score: ranking?.final_score,
|
|
|
+ rank: ranking?.final_rank,
|
|
|
+ status,
|
|
|
+ }))}
|
|
|
+ >
|
|
|
+ <div className={styles.publisherLayout}>
|
|
|
+ <section className={styles.header}>
|
|
|
+ <div className={styles.headerRow}>
|
|
|
+ <Breadcrumbs
|
|
|
+ className={styles.breadcrumbs ?? ""}
|
|
|
+ label="Breadcrumbs"
|
|
|
+ items={[
|
|
|
+ { href: "/", label: "Home" },
|
|
|
+ { href: "/publishers", label: "Publishers" },
|
|
|
+ { label: <PublisherKey size="sm" publisherKey={key} /> },
|
|
|
+ ]}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div className={styles.headerRow}>
|
|
|
+ <PublisherTag
|
|
|
+ publisherKey={key}
|
|
|
+ {...(knownPublisher && {
|
|
|
+ name: knownPublisher.name,
|
|
|
+ icon: <PublisherIcon knownPublisher={knownPublisher} />,
|
|
|
+ })}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <section className={styles.stats}>
|
|
|
<ChartCard
|
|
|
- header="Median Score"
|
|
|
- chartClassName={styles.medianScoreChart}
|
|
|
- lineClassName={styles.secondarySparkChartLine}
|
|
|
- corner={<Info weight="fill" />}
|
|
|
- data={medianScoreHistory.map(({ time, medianScore }) => ({
|
|
|
- x: time,
|
|
|
- y: medianScore,
|
|
|
+ variant="primary"
|
|
|
+ header="Publisher Ranking"
|
|
|
+ lineClassName={styles.primarySparkChartLine}
|
|
|
+ corner={
|
|
|
+ <AlertTrigger>
|
|
|
+ <Button
|
|
|
+ variant="ghost"
|
|
|
+ size="xs"
|
|
|
+ beforeIcon={(props) => <Info weight="fill" {...props} />}
|
|
|
+ rounded
|
|
|
+ hideText
|
|
|
+ className={styles.publisherRankingExplainButton ?? ""}
|
|
|
+ >
|
|
|
+ Explain Publisher Ranking
|
|
|
+ </Button>
|
|
|
+ <Alert title="Publisher Ranking" icon={<Lightbulb />}>
|
|
|
+ <p className={styles.publisherRankingExplainDescription}>
|
|
|
+ Each <b>Publisher</b> receives a <b>Ranking</b> which is
|
|
|
+ derived from the number of price feeds the{" "}
|
|
|
+ <b>Publisher</b> is actively publishing.
|
|
|
+ </p>
|
|
|
+ </Alert>
|
|
|
+ </AlertTrigger>
|
|
|
+ }
|
|
|
+ data={rankingHistory.map(({ timestamp, rank }) => ({
|
|
|
+ x: timestamp,
|
|
|
+ y: rank,
|
|
|
displayX: (
|
|
|
<span className={styles.activeDate}>
|
|
|
- <FormattedDate value={time} />
|
|
|
+ <FormattedDate value={timestamp} />
|
|
|
</span>
|
|
|
),
|
|
|
- displayY: (
|
|
|
- <FormattedNumber
|
|
|
- maximumSignificantDigits={5}
|
|
|
- value={medianScore}
|
|
|
- />
|
|
|
- ),
|
|
|
}))}
|
|
|
- stat={
|
|
|
- <FormattedNumber
|
|
|
- maximumSignificantDigits={5}
|
|
|
- value={currentMedianScore.medianScore}
|
|
|
- />
|
|
|
- }
|
|
|
- {...(previousMedianScore && {
|
|
|
+ stat={currentRanking.rank}
|
|
|
+ {...(previousRanking && {
|
|
|
miniStat: (
|
|
|
<ChangeValue
|
|
|
direction={getChangeDirection(
|
|
|
- previousMedianScore.medianScore,
|
|
|
- currentMedianScore.medianScore,
|
|
|
+ currentRanking.rank,
|
|
|
+ previousRanking.rank,
|
|
|
)}
|
|
|
>
|
|
|
- <FormattedNumber
|
|
|
- maximumSignificantDigits={2}
|
|
|
- value={
|
|
|
- (100 *
|
|
|
- Math.abs(
|
|
|
- currentMedianScore.medianScore -
|
|
|
- previousMedianScore.medianScore,
|
|
|
- )) /
|
|
|
- previousMedianScore.medianScore
|
|
|
- }
|
|
|
- />
|
|
|
- %
|
|
|
+ {Math.abs(currentRanking.rank - previousRanking.rank)}
|
|
|
</ChangeValue>
|
|
|
),
|
|
|
})}
|
|
|
/>
|
|
|
- <Drawer
|
|
|
- title="Median Score"
|
|
|
- className={styles.medianScoreDrawer ?? ""}
|
|
|
- bodyClassName={styles.medianScoreDrawerBody}
|
|
|
- footerClassName={styles.medianScoreDrawerFooter}
|
|
|
- footer={
|
|
|
- <Button
|
|
|
- variant="outline"
|
|
|
- size="sm"
|
|
|
- href="https://docs.pyth.network/home/oracle-integrity-staking/publisher-quality-ranking"
|
|
|
- target="_blank"
|
|
|
- beforeIcon={BookOpenText}
|
|
|
- >
|
|
|
- Documentation
|
|
|
- </Button>
|
|
|
- }
|
|
|
- >
|
|
|
- <MedianScoreHistory medianScoreHistory={medianScoreHistory} />
|
|
|
- <InfoBox icon={<Ranking />} header="Publisher Score">
|
|
|
- Each price feed a publisher provides has an associated score,
|
|
|
- which is determined by the component{"'"}s uptime, price
|
|
|
- deviation, and staleness. This panel shows the median for each
|
|
|
- score across all price feeds published by this publisher, as
|
|
|
- well as the overall median score across all those feeds.
|
|
|
- </InfoBox>
|
|
|
- </Drawer>
|
|
|
- </DrawerTrigger>
|
|
|
- <ActiveFeedsCard
|
|
|
- publisherKey={key}
|
|
|
- activeFeeds={publisher.numSymbols}
|
|
|
- totalFeeds={totalFeedsCount}
|
|
|
- />
|
|
|
- <DrawerTrigger>
|
|
|
- <StatCard
|
|
|
- header="OIS Pool Allocation"
|
|
|
- stat={
|
|
|
- <span
|
|
|
- className={styles.oisAllocation}
|
|
|
- data-is-overallocated={
|
|
|
- Number(oisStats.poolUtilization) > oisStats.maxPoolSize
|
|
|
- ? ""
|
|
|
- : undefined
|
|
|
- }
|
|
|
- >
|
|
|
+ <DrawerTrigger>
|
|
|
+ <ChartCard
|
|
|
+ header="Median Score"
|
|
|
+ chartClassName={styles.medianScoreChart}
|
|
|
+ lineClassName={styles.secondarySparkChartLine}
|
|
|
+ corner={<Info weight="fill" />}
|
|
|
+ data={medianScoreHistory.map(({ time, score }) => ({
|
|
|
+ x: time,
|
|
|
+ y: score,
|
|
|
+ displayX: (
|
|
|
+ <span className={styles.activeDate}>
|
|
|
+ <FormattedDate value={time} />
|
|
|
+ </span>
|
|
|
+ ),
|
|
|
+ displayY: (
|
|
|
+ <FormattedNumber
|
|
|
+ maximumSignificantDigits={5}
|
|
|
+ value={score}
|
|
|
+ />
|
|
|
+ ),
|
|
|
+ }))}
|
|
|
+ stat={
|
|
|
<FormattedNumber
|
|
|
- maximumFractionDigits={2}
|
|
|
- value={
|
|
|
- (100 * Number(oisStats.poolUtilization)) /
|
|
|
- oisStats.maxPoolSize
|
|
|
- }
|
|
|
+ maximumSignificantDigits={5}
|
|
|
+ value={currentMedianScore.score}
|
|
|
/>
|
|
|
- %
|
|
|
- </span>
|
|
|
- }
|
|
|
- corner={<Info weight="fill" />}
|
|
|
- >
|
|
|
- <Meter
|
|
|
- value={Number(oisStats.poolUtilization)}
|
|
|
- maxValue={oisStats.maxPoolSize}
|
|
|
- label="OIS Pool"
|
|
|
- startLabel={
|
|
|
- <span className={styles.tokens}>
|
|
|
- <TokenIcon />
|
|
|
- <span>
|
|
|
- <FormattedTokens tokens={oisStats.poolUtilization} />
|
|
|
- </span>
|
|
|
- </span>
|
|
|
- }
|
|
|
- endLabel={
|
|
|
- <span className={styles.tokens}>
|
|
|
- <TokenIcon />
|
|
|
- <span>
|
|
|
- <FormattedTokens tokens={BigInt(oisStats.maxPoolSize)} />
|
|
|
- </span>
|
|
|
- </span>
|
|
|
}
|
|
|
+ {...(previousMedianScore && {
|
|
|
+ miniStat: (
|
|
|
+ <ChangeValue
|
|
|
+ direction={getChangeDirection(
|
|
|
+ previousMedianScore.score,
|
|
|
+ currentMedianScore.score,
|
|
|
+ )}
|
|
|
+ >
|
|
|
+ <FormattedNumber
|
|
|
+ maximumSignificantDigits={2}
|
|
|
+ value={
|
|
|
+ (100 *
|
|
|
+ Math.abs(
|
|
|
+ currentMedianScore.score -
|
|
|
+ previousMedianScore.score,
|
|
|
+ )) /
|
|
|
+ previousMedianScore.score
|
|
|
+ }
|
|
|
+ />
|
|
|
+ %
|
|
|
+ </ChangeValue>
|
|
|
+ ),
|
|
|
+ })}
|
|
|
/>
|
|
|
- </StatCard>
|
|
|
- <Drawer
|
|
|
- title="OIS Pool Allocation"
|
|
|
- className={styles.oisDrawer ?? ""}
|
|
|
- bodyClassName={styles.oisDrawerBody}
|
|
|
- footerClassName={styles.oisDrawerFooter}
|
|
|
- footer={
|
|
|
- <>
|
|
|
- <Button
|
|
|
- variant="solid"
|
|
|
- size="sm"
|
|
|
- href="https://staking.pyth.network"
|
|
|
- target="_blank"
|
|
|
- beforeIcon={Browsers}
|
|
|
- >
|
|
|
- Open Staking App
|
|
|
- </Button>
|
|
|
+ <Drawer
|
|
|
+ title="Median Score"
|
|
|
+ className={styles.medianScoreDrawer ?? ""}
|
|
|
+ bodyClassName={styles.medianScoreDrawerBody}
|
|
|
+ footerClassName={styles.medianScoreDrawerFooter}
|
|
|
+ footer={
|
|
|
<Button
|
|
|
variant="outline"
|
|
|
size="sm"
|
|
|
- href="https://docs.pyth.network/home/oracle-integrity-staking"
|
|
|
+ href="https://docs.pyth.network/home/oracle-integrity-staking/publisher-quality-ranking"
|
|
|
target="_blank"
|
|
|
beforeIcon={BookOpenText}
|
|
|
>
|
|
|
Documentation
|
|
|
</Button>
|
|
|
- </>
|
|
|
- }
|
|
|
- >
|
|
|
- <SemicircleMeter
|
|
|
- width={420}
|
|
|
- height={420}
|
|
|
- value={Number(oisStats.poolUtilization)}
|
|
|
- maxValue={oisStats.maxPoolSize}
|
|
|
- className={styles.oisMeter ?? ""}
|
|
|
- aria-label="OIS Pool Utilization"
|
|
|
+ }
|
|
|
>
|
|
|
- <TokenIcon className={styles.oisMeterIcon} />
|
|
|
- <div className={styles.oisMeterLabel}>OIS Pool</div>
|
|
|
- </SemicircleMeter>
|
|
|
+ <ScoreHistory isMedian scoreHistory={medianScoreHistory} />
|
|
|
+ <InfoBox icon={<Ranking />} header="Publisher Score">
|
|
|
+ Each price feed a publisher provides has an associated score,
|
|
|
+ which is determined by the component{"'"}s uptime, price
|
|
|
+ deviation, and staleness. This panel shows the median for each
|
|
|
+ score across all price feeds published by this publisher, as
|
|
|
+ well as the overall median score across all those feeds.
|
|
|
+ </InfoBox>
|
|
|
+ </Drawer>
|
|
|
+ </DrawerTrigger>
|
|
|
+ <ActiveFeedsCard
|
|
|
+ publisherKey={key}
|
|
|
+ activeFeeds={
|
|
|
+ priceFeeds.filter((feed) => feed.status === Status.Active)
|
|
|
+ .length
|
|
|
+ }
|
|
|
+ totalFeeds={totalFeedsCount}
|
|
|
+ />
|
|
|
+ <DrawerTrigger>
|
|
|
<StatCard
|
|
|
- header="Total Staked"
|
|
|
- variant="secondary"
|
|
|
- nonInteractive
|
|
|
+ header="OIS Pool Allocation"
|
|
|
stat={
|
|
|
- <>
|
|
|
- <TokenIcon />
|
|
|
- <FormattedTokens tokens={oisStats.poolUtilization} />
|
|
|
- </>
|
|
|
+ <span
|
|
|
+ className={styles.oisAllocation}
|
|
|
+ data-is-overallocated={
|
|
|
+ Number(oisStats.poolUtilization) > oisStats.maxPoolSize
|
|
|
+ ? ""
|
|
|
+ : undefined
|
|
|
+ }
|
|
|
+ >
|
|
|
+ <FormattedNumber
|
|
|
+ maximumFractionDigits={2}
|
|
|
+ value={
|
|
|
+ (100 * Number(oisStats.poolUtilization)) /
|
|
|
+ oisStats.maxPoolSize
|
|
|
+ }
|
|
|
+ />
|
|
|
+ %
|
|
|
+ </span>
|
|
|
}
|
|
|
- />
|
|
|
- <StatCard
|
|
|
- header="Pool Capacity"
|
|
|
- variant="secondary"
|
|
|
- nonInteractive
|
|
|
- stat={
|
|
|
+ corner={<Info weight="fill" />}
|
|
|
+ >
|
|
|
+ <Meter
|
|
|
+ value={Number(oisStats.poolUtilization)}
|
|
|
+ maxValue={oisStats.maxPoolSize}
|
|
|
+ label="OIS Pool"
|
|
|
+ startLabel={
|
|
|
+ <span className={styles.tokens}>
|
|
|
+ <TokenIcon />
|
|
|
+ <span>
|
|
|
+ <FormattedTokens tokens={oisStats.poolUtilization} />
|
|
|
+ </span>
|
|
|
+ </span>
|
|
|
+ }
|
|
|
+ endLabel={
|
|
|
+ <span className={styles.tokens}>
|
|
|
+ <TokenIcon />
|
|
|
+ <span>
|
|
|
+ <FormattedTokens
|
|
|
+ tokens={BigInt(oisStats.maxPoolSize)}
|
|
|
+ />
|
|
|
+ </span>
|
|
|
+ </span>
|
|
|
+ }
|
|
|
+ />
|
|
|
+ </StatCard>
|
|
|
+ <Drawer
|
|
|
+ title="OIS Pool Allocation"
|
|
|
+ className={styles.oisDrawer ?? ""}
|
|
|
+ bodyClassName={styles.oisDrawerBody}
|
|
|
+ footerClassName={styles.oisDrawerFooter}
|
|
|
+ footer={
|
|
|
<>
|
|
|
- <TokenIcon />
|
|
|
- <FormattedTokens tokens={BigInt(oisStats.maxPoolSize)} />
|
|
|
+ <Button
|
|
|
+ variant="solid"
|
|
|
+ size="sm"
|
|
|
+ href="https://staking.pyth.network"
|
|
|
+ target="_blank"
|
|
|
+ beforeIcon={Browsers}
|
|
|
+ >
|
|
|
+ Open Staking App
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ variant="outline"
|
|
|
+ size="sm"
|
|
|
+ href="https://docs.pyth.network/home/oracle-integrity-staking"
|
|
|
+ target="_blank"
|
|
|
+ beforeIcon={BookOpenText}
|
|
|
+ >
|
|
|
+ Documentation
|
|
|
+ </Button>
|
|
|
</>
|
|
|
}
|
|
|
- />
|
|
|
- <OisApyHistory apyHistory={oisStats.apyHistory ?? []} />
|
|
|
- <InfoBox
|
|
|
- icon={<ShieldChevron />}
|
|
|
- header="Oracle Integrity Staking (OIS)"
|
|
|
>
|
|
|
- OIS allows anyone to help secure Pyth and protect DeFi. Through
|
|
|
- decentralized staking rewards and slashing, OIS incentivizes
|
|
|
- Pyth publishers to maintain high-quality data contributions.
|
|
|
- PYTH holders can stake to publishers to further reinforce oracle
|
|
|
- security. Rewards are programmatically distributed to high
|
|
|
- quality publishers and the stakers supporting them to strengthen
|
|
|
- oracle integrity.
|
|
|
- </InfoBox>
|
|
|
- </Drawer>
|
|
|
- </DrawerTrigger>
|
|
|
+ <SemicircleMeter
|
|
|
+ width={420}
|
|
|
+ height={420}
|
|
|
+ value={Number(oisStats.poolUtilization)}
|
|
|
+ maxValue={oisStats.maxPoolSize}
|
|
|
+ className={styles.oisMeter ?? ""}
|
|
|
+ aria-label="OIS Pool Utilization"
|
|
|
+ >
|
|
|
+ <TokenIcon className={styles.oisMeterIcon} />
|
|
|
+ <div className={styles.oisMeterLabel}>OIS Pool</div>
|
|
|
+ </SemicircleMeter>
|
|
|
+ <StatCard
|
|
|
+ header="Total Staked"
|
|
|
+ variant="secondary"
|
|
|
+ nonInteractive
|
|
|
+ stat={
|
|
|
+ <>
|
|
|
+ <TokenIcon />
|
|
|
+ <FormattedTokens tokens={oisStats.poolUtilization} />
|
|
|
+ </>
|
|
|
+ }
|
|
|
+ />
|
|
|
+ <StatCard
|
|
|
+ header="Pool Capacity"
|
|
|
+ variant="secondary"
|
|
|
+ nonInteractive
|
|
|
+ stat={
|
|
|
+ <>
|
|
|
+ <TokenIcon />
|
|
|
+ <FormattedTokens tokens={BigInt(oisStats.maxPoolSize)} />
|
|
|
+ </>
|
|
|
+ }
|
|
|
+ />
|
|
|
+ <OisApyHistory apyHistory={oisStats.apyHistory ?? []} />
|
|
|
+ <InfoBox
|
|
|
+ icon={<ShieldChevron />}
|
|
|
+ header="Oracle Integrity Staking (OIS)"
|
|
|
+ >
|
|
|
+ OIS allows anyone to help secure Pyth and protect DeFi.
|
|
|
+ Through decentralized staking rewards and slashing, OIS
|
|
|
+ incentivizes Pyth publishers to maintain high-quality data
|
|
|
+ contributions. PYTH holders can stake to publishers to further
|
|
|
+ reinforce oracle security. Rewards are programmatically
|
|
|
+ distributed to high quality publishers and the stakers
|
|
|
+ supporting them to strengthen oracle integrity.
|
|
|
+ </InfoBox>
|
|
|
+ </Drawer>
|
|
|
+ </DrawerTrigger>
|
|
|
+ </section>
|
|
|
</section>
|
|
|
- </section>
|
|
|
- <TabRoot>
|
|
|
- <Tabs
|
|
|
- label="Price Feed Navigation"
|
|
|
- prefix={`/publishers/${key}`}
|
|
|
- items={[
|
|
|
- { segment: undefined, children: "Performance" },
|
|
|
- {
|
|
|
- segment: "price-feeds",
|
|
|
- children: (
|
|
|
- <div className={styles.priceFeedsTabLabel}>
|
|
|
- <span>Price Feeds</span>
|
|
|
- <Badge size="xs" style="filled" variant="neutral">
|
|
|
- {publisherFeeds.length}
|
|
|
- </Badge>
|
|
|
- </div>
|
|
|
- ),
|
|
|
- },
|
|
|
- ]}
|
|
|
- />
|
|
|
- <TabPanel className={styles.body ?? ""}>{children}</TabPanel>
|
|
|
- </TabRoot>
|
|
|
- </div>
|
|
|
+ <TabRoot>
|
|
|
+ <Tabs
|
|
|
+ label="Price Feed Navigation"
|
|
|
+ prefix={`/publishers/${key}`}
|
|
|
+ items={[
|
|
|
+ { segment: undefined, children: "Performance" },
|
|
|
+ {
|
|
|
+ segment: "price-feeds",
|
|
|
+ children: (
|
|
|
+ <div className={styles.priceFeedsTabLabel}>
|
|
|
+ <span>Price Feeds</span>
|
|
|
+ <Badge size="xs" style="filled" variant="neutral">
|
|
|
+ {priceFeeds.length}
|
|
|
+ </Badge>
|
|
|
+ </div>
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ ]}
|
|
|
+ />
|
|
|
+ <TabPanel className={styles.body ?? ""}>{children}</TabPanel>
|
|
|
+ </TabRoot>
|
|
|
+ </div>
|
|
|
+ </PriceFeedDrawerProvider>
|
|
|
) : (
|
|
|
notFound()
|
|
|
);
|