Skip to content

Commit

Permalink
Merge pull request #2021 from pyth-network/staking-app-release-batch
Browse files Browse the repository at this point in the history
Staking app release batch
  • Loading branch information
cprussin authored Oct 10, 2024
2 parents 46d0d2a + 0a4ee48 commit c39c85e
Show file tree
Hide file tree
Showing 32 changed files with 1,134 additions and 318 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
78 changes: 59 additions & 19 deletions apps/staking/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ import {
getAmountByTargetAndState,
getCurrentEpoch,
PositionState,
PythnetClient,
PythStakingClient,
type StakeAccountPositions,
} from "@pythnetwork/staking-sdk";
import { PublicKey } from "@solana/web3.js";
import { z } from "zod";

import { KNOWN_PUBLISHERS } from "./known-publishers";

const publishersRankingSchema = z
.object({
publisher: z.string(),
Expand Down Expand Up @@ -43,8 +46,15 @@ type Data = {
cooldown2: bigint;
};
yieldRate: bigint;
m: bigint;
z: bigint;
integrityStakingPublishers: {
name: string | undefined;
identity:
| {
name: string;
icon: string;
}
| undefined;
publicKey: PublicKey;
stakeAccount: PublicKey | undefined;
selfStake: bigint;
Expand All @@ -54,7 +64,8 @@ type Data = {
poolUtilizationDelta: bigint;
numFeeds: number;
qualityRanking: number;
apyHistory: { date: Date; apy: number }[];
delegationFee: bigint;
apyHistory: { date: Date; apy: number; selfApy: number }[];
positions?:
| {
warmup?: bigint | undefined;
Expand Down Expand Up @@ -95,18 +106,29 @@ export const getStakeAccount = async (

export const loadData = async (
client: PythStakingClient,
pythnetClient: PythnetClient,
hermesClient: HermesClient,
stakeAccount?: PublicKey | undefined,
): Promise<Data> =>
stakeAccount === undefined
? loadDataNoStakeAccount(client, hermesClient)
: loadDataForStakeAccount(client, hermesClient, stakeAccount);
? loadDataNoStakeAccount(client, pythnetClient, hermesClient)
: loadDataForStakeAccount(
client,
pythnetClient,
hermesClient,
stakeAccount,
);

const loadDataNoStakeAccount = async (
client: PythStakingClient,
pythnetClient: PythnetClient,
hermesClient: HermesClient,
): Promise<Data> => {
const { publishers, ...baseInfo } = await loadBaseInfo(client, hermesClient);
const { publishers, ...baseInfo } = await loadBaseInfo(
client,
pythnetClient,
hermesClient,
);

return {
...baseInfo,
Expand All @@ -127,6 +149,7 @@ const loadDataNoStakeAccount = async (

const loadDataForStakeAccount = async (
client: PythStakingClient,
pythnetClient: PythnetClient,
hermesClient: HermesClient,
stakeAccount: PublicKey,
): Promise<Data> => {
Expand All @@ -137,7 +160,7 @@ const loadDataForStakeAccount = async (
claimableRewards,
stakeAccountPositions,
] = await Promise.all([
loadBaseInfo(client, hermesClient),
loadBaseInfo(client, pythnetClient, hermesClient),
client.getStakeAccountCustody(stakeAccount),
client.getUnlockSchedule(stakeAccount),
client.getClaimableRewards(stakeAccount),
Expand Down Expand Up @@ -196,45 +219,61 @@ const loadDataForStakeAccount = async (

const loadBaseInfo = async (
client: PythStakingClient,
pythnetClient: PythnetClient,
hermesClient: HermesClient,
) => {
const [publishers, walletAmount, poolConfig, currentEpoch] =
const [publishers, walletAmount, poolConfig, currentEpoch, parameters] =
await Promise.all([
loadPublisherData(client, hermesClient),
loadPublisherData(client, pythnetClient, hermesClient),
client.getOwnerPythBalance(),
client.getPoolConfigAccount(),
getCurrentEpoch(client.connection),
pythnetClient.getStakeCapParameters(),
]);

return { yieldRate: poolConfig.y, walletAmount, publishers, currentEpoch };
return {
yieldRate: poolConfig.y,
walletAmount,
publishers,
currentEpoch,
m: parameters.m,
z: parameters.z,
};
};

const loadPublisherData = async (
client: PythStakingClient,
pythnetClient: PythnetClient,
hermesClient: HermesClient,
) => {
const [poolData, publisherRankings, publisherCaps] = await Promise.all([
client.getPoolDataAccount(),
getPublisherRankings(),
hermesClient.getLatestPublisherCaps({
parsed: true,
}),
]);
const [poolData, publisherRankings, publisherCaps, publisherNumberOfSymbols] =
await Promise.all([
client.getPoolDataAccount(),
getPublisherRankings(),
hermesClient.getLatestPublisherCaps({
parsed: true,
}),
pythnetClient.getPublisherNumberOfSymbols(),
]);

return extractPublisherData(poolData).map((publisher) => {
const publisherPubkeyString = publisher.pubkey.toBase58();
const publisherRanking = publisherRankings.find(
(ranking) => ranking.publisher === publisherPubkeyString,
);
const apyHistory = publisher.apyHistory.map(({ epoch, apy }) => ({
const numberOfSymbols = publisherNumberOfSymbols[publisherPubkeyString];
const apyHistory = publisher.apyHistory.map(({ epoch, apy, selfApy }) => ({
date: epochToDate(epoch + 1n),
apy,
selfApy,
}));

return {
apyHistory,
name: undefined, // TODO
numFeeds: publisherRanking?.numSymbols ?? 0,
identity: (
KNOWN_PUBLISHERS as Record<string, { name: string; icon: string }>
)[publisher.pubkey.toBase58()],
numFeeds: numberOfSymbols ?? 0,
poolCapacity: getPublisherCap(publisherCaps, publisher.pubkey),
poolUtilization: publisher.totalDelegation,
poolUtilizationDelta: publisher.totalDelegationDelta,
Expand All @@ -243,6 +282,7 @@ const loadPublisherData = async (
selfStake: publisher.selfDelegation,
selfStakeDelta: publisher.selfDelegationDelta,
stakeAccount: publisher.stakeAccount ?? undefined,
delegationFee: publisher.delegationFee,
};
});
};
Expand Down
198 changes: 198 additions & 0 deletions apps/staking/src/components/Changelog/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
"use client";

import type { ReactNode } from "react";
import { useDateFormatter } from "react-aria";

import { useChangelog } from "../../hooks/use-changelog";
import { Link } from "../Link";
import { ModalDialog } from "../ModalDialog";

export const Changelog = () => {
const { isOpen, toggleOpen } = useChangelog();

return (
<ModalDialog title="Changelog" isOpen={isOpen} onOpenChange={toggleOpen}>
<ul className="flex max-w-prose flex-col divide-y divide-neutral-600/50">
{messages.map(({ id, message }) => (
<li key={id}>{message}</li>
))}
</ul>
</ModalDialog>
);
};

type ChangelogMessageProps = {
date: Date;
children: ReactNode | ReactNode[];
};

export const ChangelogMessage = ({ date, children }: ChangelogMessageProps) => {
const dateFormatter = useDateFormatter({
year: "numeric",
month: "short",
day: "numeric",
});

return (
<section className="py-8">
<h2 className="text-sm uppercase text-pythpurple-400">
{dateFormatter.format(date)}
</h2>
{children}
</section>
);
};

type ChangelogSectionProps = {
title: ReactNode;
children: ReactNode | ReactNode[];
};

export const ChangelogSection = ({
title,
children,
}: ChangelogSectionProps) => (
<section className="mt-4">
<h3 className="text-lg font-semibold text-white">{title}</h3>
<div className="flex flex-col gap-2 pl-2 text-sm opacity-70">
{children}
</div>
</section>
);

export const messages = [
{
id: 1,
message: (
<ChangelogMessage date={new Date("2024-10-10")}>
<ChangelogSection title="Milestones">
<div>
<p>
We are pleased to announce the following Oracle Integrity Staking
milestones:
</p>
<ul className="list-disc pl-8">
<li>145M PYTH staked and securing DeFi.</li>
<li>10K unique stakers participating.</li>
<li>492K in PYTH programmatically distributed.</li>
</ul>
</div>
<p>We’re thrilled to see so many community participants.</p>
</ChangelogSection>
<ChangelogSection title="New Features to the Staking Frontend">
<ul className="list-disc pl-4">
<li>
New sort filter for publishers list. Publishers with self-stake
are displayed first by default. You can sort by publisher details,
pool composition, and more.
</li>
<li>
Publishers interested in de-anonymizing themselves can have their
names displayed in the publisher list.
</li>
<li>New OIS live stats added to navigation bar.</li>
<li>
New dialogue added under “Help” where you can view current program
parameters.
</li>
<li>
Option to remove PYTH from the smart contract program for parties
with restricted access to the staking frontend.
</li>
<li>
Full access to Pyth Governance for certain restricted
jurisdictions.
</li>
<li>APYs are now shown as net of delegation fees.</li>
<li>
Updates to educational materials (all Guides and FAQs) for clarity
and readability.
</li>
<li>
New Oracle Integrity Staking{" "}
<Link
href="https://forum.pyth.network/c/oracle-integrity-staking-ois-discussion/8"
className="underline"
target="_blank"
>
discussion catalogue
</Link>{" "}
opened in Pyth DAO forum. Let the community know your thoughts and
feedback!
</li>
</ul>
</ChangelogSection>
<ChangelogSection title="Security">
<p>
The Pyth contributors take security extremely seriously. The
contract code is{" "}
<Link
href="https://github.com/pyth-network/governance/tree/main/staking/programs/staking"
className="underline"
target="_blank"
>
open source
</Link>{" "}
and the upgrade authority is governed by the Pyth DAO. The official{" "}
<Link
href="https://github.com/pyth-network/audit-reports/blob/main/2024_09_11/pyth_cip_final_report.pdf"
className="underline"
target="_blank"
>
audit report
</Link>{" "}
is publicly accessible. All on-chain contract codes are verified
using{" "}
<Link
href="https://github.com/Ellipsis-Labs/solana-verifiable-build/"
className="underline"
target="_blank"
>
Solana verifiable build
</Link>{" "}
and the Pyth DAO governs the upgrade authority.
</p>
</ChangelogSection>
<ChangelogSection title="Best Practices">
<p>
Please remember that publishers have priority for programmatic
rewards distributions. By protocol design, if a pool’s stake cap is
exceeded, the programmatic reward rate for other stakers
participating in that pool will be lower than the Pyth DAO-set
maximum reward rate.
</p>
</ChangelogSection>
<ChangelogSection title="Acknowledgements">
<p>
The Pyth contributors are glad to see so many network participants
getting involved with Oracle Integrity Staking to help secure the
oracle and protect the wider DeFi industry. OIS wouldn’t be possible
without you!
</p>
</ChangelogSection>
<ChangelogSection title="Feedback">
<p>
Please reach out in the official{" "}
<Link
href="https://discord.com/invite/PythNetwork"
className="underline"
target="_blank"
>
Pyth Discord
</Link>{" "}
or the{" "}
<Link
href="https://forum.pyth.network"
className="underline"
target="_blank"
>
Pyth DAO Forum
</Link>{" "}
to share your questions, ideas, or feedback. We want to hear what
you think.
</p>
</ChangelogSection>
</ChangelogMessage>
),
},
];
Loading

0 comments on commit c39c85e

Please sign in to comment.