Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Staking app release batch #2021

Merged
merged 37 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
2febe1f
step 1
guibescos Oct 3, 2024
a409feb
progress
guibescos Oct 3, 2024
c9f8e1f
works
guibescos Oct 3, 2024
7f06396
add self apy
guibescos Oct 3, 2024
0da8b05
fixed
guibescos Oct 3, 2024
dcb554f
go
guibescos Oct 3, 2024
492df8d
go
guibescos Oct 3, 2024
6ba4ae3
rename
guibescos Oct 3, 2024
086c6fd
lint
guibescos Oct 3, 2024
a46053e
feat(staking): sort by publisher self-stake first
cprussin Oct 4, 2024
d72c6ef
fix: return 0 apy if pool has 0 capacity
guibescos Oct 7, 2024
0d01893
go
guibescos Oct 7, 2024
859ba58
lint
guibescos Oct 7, 2024
6f76bf8
add types
guibescos Oct 7, 2024
d3c26e9
fix types again
guibescos Oct 7, 2024
ede2629
cleanup
guibescos Oct 8, 2024
dcabdac
go
guibescos Oct 8, 2024
2d721bc
complete
guibescos Oct 8, 2024
3686fa5
Merge pull request #1997 from cprussin/sort-by-pub-stake-first
cprussin Oct 8, 2024
50c442f
Merge remote-tracking branch 'upstream/staking-app-release-batch' int…
cprussin Oct 8, 2024
1a37585
Merge pull request #1993 from pyth-network/feat/display-actual-apy
cprussin Oct 8, 2024
24c4487
Add UI popup to display params
cprussin Oct 8, 2024
498a251
Merge remote-tracking branch 'upstream/staking-app-release-batch' int…
cprussin Oct 8, 2024
7d59721
Merge remote-tracking branch 'upstream/staking-app-release-batch' int…
cprussin Oct 8, 2024
a8b08ee
Merge pull request #2005 from pyth-network/feat/add-pythnet-client
cprussin Oct 8, 2024
7254d7b
Merge remote-tracking branch 'upstream/main' into staking-app-release…
cprussin Oct 8, 2024
92efb88
feat(staking): add stats to navbar
cprussin Oct 8, 2024
71dd9f4
feat(staking): add reward custody account
keyvankhademi Oct 8, 2024
8cd4792
clean up code a bit
cprussin Oct 8, 2024
59b14c6
feat(staking): expose publisher names for pubs who wish to be public
cprussin Oct 9, 2024
5e54027
Merge pull request #2014 from cprussin/expose-publishers
cprussin Oct 9, 2024
37fb9b6
Merge remote-tracking branch 'upstream/main' into staking-app-release…
cprussin Oct 9, 2024
667c4fc
feat(staking): add changelog
cprussin Oct 9, 2024
a56fc93
fix(staking): fix last stake account when moving between devnet
cprussin Oct 9, 2024
2f7d072
Merge pull request #2019 from cprussin/add-changelog
cprussin Oct 9, 2024
854bca0
Merge pull request #2020 from cprussin/fix-last-stake-account
cprussin Oct 9, 2024
0a4ee48
chore(staking): update stats in changelog
cprussin Oct 10, 2024
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
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
Loading