|
@@ -23,7 +23,7 @@ import {
|
|
|
getPublishers,
|
|
getPublishers,
|
|
|
} from "../../services/clickhouse";
|
|
} from "../../services/clickhouse";
|
|
|
import { getPublisherCaps } from "../../services/hermes";
|
|
import { getPublisherCaps } from "../../services/hermes";
|
|
|
-import { Cluster } from "../../services/pyth";
|
|
|
|
|
|
|
+import { Cluster, ClusterToName, parseCluster } from "../../services/pyth";
|
|
|
import { getPublisherPoolData } from "../../services/staking";
|
|
import { getPublisherPoolData } from "../../services/staking";
|
|
|
import { ChangePercent } from "../ChangePercent";
|
|
import { ChangePercent } from "../ChangePercent";
|
|
|
import { ChangeValue } from "../ChangeValue";
|
|
import { ChangeValue } from "../ChangeValue";
|
|
@@ -49,12 +49,19 @@ import { TokenIcon } from "../TokenIcon";
|
|
|
type Props = {
|
|
type Props = {
|
|
|
children: ReactNode;
|
|
children: ReactNode;
|
|
|
params: Promise<{
|
|
params: Promise<{
|
|
|
|
|
+ cluster: string;
|
|
|
key: string;
|
|
key: string;
|
|
|
}>;
|
|
}>;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
export const PublishersLayout = async ({ children, params }: Props) => {
|
|
export const PublishersLayout = async ({ children, params }: Props) => {
|
|
|
- const { key } = await params;
|
|
|
|
|
|
|
+ const { cluster, key } = await params;
|
|
|
|
|
+ const parsedCluster = parseCluster(cluster);
|
|
|
|
|
+
|
|
|
|
|
+ if (parsedCluster === undefined) {
|
|
|
|
|
+ notFound();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
const [
|
|
const [
|
|
|
rankingHistory,
|
|
rankingHistory,
|
|
|
averageScoreHistory,
|
|
averageScoreHistory,
|
|
@@ -62,11 +69,11 @@ export const PublishersLayout = async ({ children, params }: Props) => {
|
|
|
priceFeeds,
|
|
priceFeeds,
|
|
|
publishers,
|
|
publishers,
|
|
|
] = await Promise.all([
|
|
] = await Promise.all([
|
|
|
- getPublisherRankingHistory(key),
|
|
|
|
|
- getPublisherAverageScoreHistory(key),
|
|
|
|
|
|
|
+ getPublisherRankingHistory(parsedCluster, key),
|
|
|
|
|
+ getPublisherAverageScoreHistory(parsedCluster, key),
|
|
|
getOisStats(key),
|
|
getOisStats(key),
|
|
|
- getPriceFeeds(Cluster.Pythnet, key),
|
|
|
|
|
- getPublishers(),
|
|
|
|
|
|
|
+ getPriceFeeds(parsedCluster, key),
|
|
|
|
|
+ getPublishers(parsedCluster),
|
|
|
]);
|
|
]);
|
|
|
|
|
|
|
|
const currentRanking = rankingHistory.at(-1);
|
|
const currentRanking = rankingHistory.at(-1);
|
|
@@ -79,6 +86,7 @@ export const PublishersLayout = async ({ children, params }: Props) => {
|
|
|
|
|
|
|
|
return publisher && currentRanking && currentAverageScore ? (
|
|
return publisher && currentRanking && currentAverageScore ? (
|
|
|
<PriceFeedDrawerProvider
|
|
<PriceFeedDrawerProvider
|
|
|
|
|
+ cluster={parsedCluster}
|
|
|
publisherKey={key}
|
|
publisherKey={key}
|
|
|
priceFeeds={priceFeeds.map(({ feed, ranking, status }) => ({
|
|
priceFeeds={priceFeeds.map(({ feed, ranking, status }) => ({
|
|
|
symbol: feed.symbol,
|
|
symbol: feed.symbol,
|
|
@@ -94,7 +102,7 @@ export const PublishersLayout = async ({ children, params }: Props) => {
|
|
|
>
|
|
>
|
|
|
<div className={styles.publisherLayout}>
|
|
<div className={styles.publisherLayout}>
|
|
|
<section className={styles.header}>
|
|
<section className={styles.header}>
|
|
|
- <div className={styles.headerRow}>
|
|
|
|
|
|
|
+ <div className={styles.breadcrumbRow}>
|
|
|
<Breadcrumbs
|
|
<Breadcrumbs
|
|
|
className={styles.breadcrumbs ?? ""}
|
|
className={styles.breadcrumbs ?? ""}
|
|
|
label="Breadcrumbs"
|
|
label="Breadcrumbs"
|
|
@@ -105,15 +113,14 @@ export const PublishersLayout = async ({ children, params }: Props) => {
|
|
|
]}
|
|
]}
|
|
|
/>
|
|
/>
|
|
|
</div>
|
|
</div>
|
|
|
- <div className={styles.headerRow}>
|
|
|
|
|
- <PublisherTag
|
|
|
|
|
- publisherKey={key}
|
|
|
|
|
- {...(knownPublisher && {
|
|
|
|
|
- name: knownPublisher.name,
|
|
|
|
|
- icon: <PublisherIcon knownPublisher={knownPublisher} />,
|
|
|
|
|
- })}
|
|
|
|
|
- />
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <PublisherTag
|
|
|
|
|
+ cluster={parsedCluster}
|
|
|
|
|
+ publisherKey={key}
|
|
|
|
|
+ {...(knownPublisher && {
|
|
|
|
|
+ name: knownPublisher.name,
|
|
|
|
|
+ icon: <PublisherIcon knownPublisher={knownPublisher} />,
|
|
|
|
|
+ })}
|
|
|
|
|
+ />
|
|
|
<section className={styles.stats}>
|
|
<section className={styles.stats}>
|
|
|
<ChartCard
|
|
<ChartCard
|
|
|
variant="primary"
|
|
variant="primary"
|
|
@@ -152,7 +159,6 @@ export const PublishersLayout = async ({ children, params }: Props) => {
|
|
|
/>
|
|
/>
|
|
|
<ChartCard
|
|
<ChartCard
|
|
|
header="Average Score"
|
|
header="Average Score"
|
|
|
- chartClassName={styles.averageScoreChart}
|
|
|
|
|
corner={<ExplainAverage />}
|
|
corner={<ExplainAverage />}
|
|
|
data={averageScoreHistory.map(({ time, averageScore }) => ({
|
|
data={averageScoreHistory.map(({ time, averageScore }) => ({
|
|
|
x: time,
|
|
x: time,
|
|
@@ -199,7 +205,7 @@ export const PublishersLayout = async ({ children, params }: Props) => {
|
|
|
}
|
|
}
|
|
|
stat1={
|
|
stat1={
|
|
|
<Link
|
|
<Link
|
|
|
- href={`/publishers/${key}/price-feeds?status=Active`}
|
|
|
|
|
|
|
+ href={`/publishers/${ClusterToName[parsedCluster]}/${key}/price-feeds?status=Active`}
|
|
|
invert
|
|
invert
|
|
|
>
|
|
>
|
|
|
{publisher.activeFeeds}
|
|
{publisher.activeFeeds}
|
|
@@ -207,7 +213,7 @@ export const PublishersLayout = async ({ children, params }: Props) => {
|
|
|
}
|
|
}
|
|
|
stat2={
|
|
stat2={
|
|
|
<Link
|
|
<Link
|
|
|
- href={`/publishers/${key}/price-feeds?status=Inactive`}
|
|
|
|
|
|
|
+ href={`/publishers/${ClusterToName[parsedCluster]}/${key}/price-feeds?status=Inactive`}
|
|
|
invert
|
|
invert
|
|
|
>
|
|
>
|
|
|
{publisher.inactiveFeeds}
|
|
{publisher.inactiveFeeds}
|
|
@@ -238,136 +244,140 @@ export const PublishersLayout = async ({ children, params }: Props) => {
|
|
|
label="Active Feeds"
|
|
label="Active Feeds"
|
|
|
/>
|
|
/>
|
|
|
</StatCard>
|
|
</StatCard>
|
|
|
- <DrawerTrigger>
|
|
|
|
|
- <StatCard
|
|
|
|
|
- header="OIS Pool Allocation"
|
|
|
|
|
- stat={
|
|
|
|
|
- <span
|
|
|
|
|
- className={styles.oisAllocation}
|
|
|
|
|
- data-is-overallocated={
|
|
|
|
|
- Number(oisStats.poolUtilization) > oisStats.maxPoolSize
|
|
|
|
|
- ? ""
|
|
|
|
|
- : undefined
|
|
|
|
|
- }
|
|
|
|
|
- >
|
|
|
|
|
- <FormattedNumber
|
|
|
|
|
- maximumFractionDigits={2}
|
|
|
|
|
- value={
|
|
|
|
|
- (100 * Number(oisStats.poolUtilization)) /
|
|
|
|
|
- oisStats.maxPoolSize
|
|
|
|
|
|
|
+ {parsedCluster === Cluster.Pythnet && (
|
|
|
|
|
+ <DrawerTrigger>
|
|
|
|
|
+ <StatCard
|
|
|
|
|
+ header="OIS Pool Allocation"
|
|
|
|
|
+ stat={
|
|
|
|
|
+ <span
|
|
|
|
|
+ className={styles.oisAllocation}
|
|
|
|
|
+ data-is-overallocated={
|
|
|
|
|
+ Number(oisStats.poolUtilization) > oisStats.maxPoolSize
|
|
|
|
|
+ ? ""
|
|
|
|
|
+ : undefined
|
|
|
}
|
|
}
|
|
|
- />
|
|
|
|
|
- %
|
|
|
|
|
- </span>
|
|
|
|
|
- }
|
|
|
|
|
- corner={<ArrowsOutSimple />}
|
|
|
|
|
- >
|
|
|
|
|
- <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>
|
|
|
|
|
|
|
+ >
|
|
|
|
|
+ <FormattedNumber
|
|
|
|
|
+ maximumFractionDigits={2}
|
|
|
|
|
+ value={
|
|
|
|
|
+ (100 * Number(oisStats.poolUtilization)) /
|
|
|
|
|
+ oisStats.maxPoolSize
|
|
|
|
|
+ }
|
|
|
|
|
+ />
|
|
|
|
|
+ %
|
|
|
</span>
|
|
</span>
|
|
|
}
|
|
}
|
|
|
- />
|
|
|
|
|
- </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>
|
|
|
|
|
- <Button
|
|
|
|
|
- variant="outline"
|
|
|
|
|
- size="sm"
|
|
|
|
|
- href="https://docs.pyth.network/home/oracle-integrity-staking"
|
|
|
|
|
- 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"
|
|
|
|
|
|
|
+ corner={<ArrowsOutSimple />}
|
|
|
>
|
|
>
|
|
|
- <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={
|
|
|
|
|
|
|
+ <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>
|
|
</section>
|
|
|
<TabRoot>
|
|
<TabRoot>
|
|
|
<Tabs
|
|
<Tabs
|
|
|
label="Price Feed Navigation"
|
|
label="Price Feed Navigation"
|
|
|
- prefix={`/publishers/${key}`}
|
|
|
|
|
|
|
+ prefix={`/publishers/${ClusterToName[parsedCluster]}/${key}`}
|
|
|
items={[
|
|
items={[
|
|
|
{ segment: undefined, children: "Performance" },
|
|
{ segment: undefined, children: "Performance" },
|
|
|
{
|
|
{
|