Skip to content

Commit c39c85e

Browse files
authored
Merge pull request #2021 from pyth-network/staking-app-release-batch
Staking app release batch
2 parents 46d0d2a + 0a4ee48 commit c39c85e

File tree

32 files changed

+1134
-318
lines changed

32 files changed

+1134
-318
lines changed
149 KB
Loading

apps/staking/src/api.ts

Lines changed: 59 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@ import {
55
getAmountByTargetAndState,
66
getCurrentEpoch,
77
PositionState,
8+
PythnetClient,
89
PythStakingClient,
910
type StakeAccountPositions,
1011
} from "@pythnetwork/staking-sdk";
1112
import { PublicKey } from "@solana/web3.js";
1213
import { z } from "zod";
1314

15+
import { KNOWN_PUBLISHERS } from "./known-publishers";
16+
1417
const publishersRankingSchema = z
1518
.object({
1619
publisher: z.string(),
@@ -43,8 +46,15 @@ type Data = {
4346
cooldown2: bigint;
4447
};
4548
yieldRate: bigint;
49+
m: bigint;
50+
z: bigint;
4651
integrityStakingPublishers: {
47-
name: string | undefined;
52+
identity:
53+
| {
54+
name: string;
55+
icon: string;
56+
}
57+
| undefined;
4858
publicKey: PublicKey;
4959
stakeAccount: PublicKey | undefined;
5060
selfStake: bigint;
@@ -54,7 +64,8 @@ type Data = {
5464
poolUtilizationDelta: bigint;
5565
numFeeds: number;
5666
qualityRanking: number;
57-
apyHistory: { date: Date; apy: number }[];
67+
delegationFee: bigint;
68+
apyHistory: { date: Date; apy: number; selfApy: number }[];
5869
positions?:
5970
| {
6071
warmup?: bigint | undefined;
@@ -95,18 +106,29 @@ export const getStakeAccount = async (
95106

96107
export const loadData = async (
97108
client: PythStakingClient,
109+
pythnetClient: PythnetClient,
98110
hermesClient: HermesClient,
99111
stakeAccount?: PublicKey | undefined,
100112
): Promise<Data> =>
101113
stakeAccount === undefined
102-
? loadDataNoStakeAccount(client, hermesClient)
103-
: loadDataForStakeAccount(client, hermesClient, stakeAccount);
114+
? loadDataNoStakeAccount(client, pythnetClient, hermesClient)
115+
: loadDataForStakeAccount(
116+
client,
117+
pythnetClient,
118+
hermesClient,
119+
stakeAccount,
120+
);
104121

105122
const loadDataNoStakeAccount = async (
106123
client: PythStakingClient,
124+
pythnetClient: PythnetClient,
107125
hermesClient: HermesClient,
108126
): Promise<Data> => {
109-
const { publishers, ...baseInfo } = await loadBaseInfo(client, hermesClient);
127+
const { publishers, ...baseInfo } = await loadBaseInfo(
128+
client,
129+
pythnetClient,
130+
hermesClient,
131+
);
110132

111133
return {
112134
...baseInfo,
@@ -127,6 +149,7 @@ const loadDataNoStakeAccount = async (
127149

128150
const loadDataForStakeAccount = async (
129151
client: PythStakingClient,
152+
pythnetClient: PythnetClient,
130153
hermesClient: HermesClient,
131154
stakeAccount: PublicKey,
132155
): Promise<Data> => {
@@ -137,7 +160,7 @@ const loadDataForStakeAccount = async (
137160
claimableRewards,
138161
stakeAccountPositions,
139162
] = await Promise.all([
140-
loadBaseInfo(client, hermesClient),
163+
loadBaseInfo(client, pythnetClient, hermesClient),
141164
client.getStakeAccountCustody(stakeAccount),
142165
client.getUnlockSchedule(stakeAccount),
143166
client.getClaimableRewards(stakeAccount),
@@ -196,45 +219,61 @@ const loadDataForStakeAccount = async (
196219

197220
const loadBaseInfo = async (
198221
client: PythStakingClient,
222+
pythnetClient: PythnetClient,
199223
hermesClient: HermesClient,
200224
) => {
201-
const [publishers, walletAmount, poolConfig, currentEpoch] =
225+
const [publishers, walletAmount, poolConfig, currentEpoch, parameters] =
202226
await Promise.all([
203-
loadPublisherData(client, hermesClient),
227+
loadPublisherData(client, pythnetClient, hermesClient),
204228
client.getOwnerPythBalance(),
205229
client.getPoolConfigAccount(),
206230
getCurrentEpoch(client.connection),
231+
pythnetClient.getStakeCapParameters(),
207232
]);
208233

209-
return { yieldRate: poolConfig.y, walletAmount, publishers, currentEpoch };
234+
return {
235+
yieldRate: poolConfig.y,
236+
walletAmount,
237+
publishers,
238+
currentEpoch,
239+
m: parameters.m,
240+
z: parameters.z,
241+
};
210242
};
211243

212244
const loadPublisherData = async (
213245
client: PythStakingClient,
246+
pythnetClient: PythnetClient,
214247
hermesClient: HermesClient,
215248
) => {
216-
const [poolData, publisherRankings, publisherCaps] = await Promise.all([
217-
client.getPoolDataAccount(),
218-
getPublisherRankings(),
219-
hermesClient.getLatestPublisherCaps({
220-
parsed: true,
221-
}),
222-
]);
249+
const [poolData, publisherRankings, publisherCaps, publisherNumberOfSymbols] =
250+
await Promise.all([
251+
client.getPoolDataAccount(),
252+
getPublisherRankings(),
253+
hermesClient.getLatestPublisherCaps({
254+
parsed: true,
255+
}),
256+
pythnetClient.getPublisherNumberOfSymbols(),
257+
]);
223258

224259
return extractPublisherData(poolData).map((publisher) => {
225260
const publisherPubkeyString = publisher.pubkey.toBase58();
226261
const publisherRanking = publisherRankings.find(
227262
(ranking) => ranking.publisher === publisherPubkeyString,
228263
);
229-
const apyHistory = publisher.apyHistory.map(({ epoch, apy }) => ({
264+
const numberOfSymbols = publisherNumberOfSymbols[publisherPubkeyString];
265+
const apyHistory = publisher.apyHistory.map(({ epoch, apy, selfApy }) => ({
230266
date: epochToDate(epoch + 1n),
231267
apy,
268+
selfApy,
232269
}));
233270

234271
return {
235272
apyHistory,
236-
name: undefined, // TODO
237-
numFeeds: publisherRanking?.numSymbols ?? 0,
273+
identity: (
274+
KNOWN_PUBLISHERS as Record<string, { name: string; icon: string }>
275+
)[publisher.pubkey.toBase58()],
276+
numFeeds: numberOfSymbols ?? 0,
238277
poolCapacity: getPublisherCap(publisherCaps, publisher.pubkey),
239278
poolUtilization: publisher.totalDelegation,
240279
poolUtilizationDelta: publisher.totalDelegationDelta,
@@ -243,6 +282,7 @@ const loadPublisherData = async (
243282
selfStake: publisher.selfDelegation,
244283
selfStakeDelta: publisher.selfDelegationDelta,
245284
stakeAccount: publisher.stakeAccount ?? undefined,
285+
delegationFee: publisher.delegationFee,
246286
};
247287
});
248288
};
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
"use client";
2+
3+
import type { ReactNode } from "react";
4+
import { useDateFormatter } from "react-aria";
5+
6+
import { useChangelog } from "../../hooks/use-changelog";
7+
import { Link } from "../Link";
8+
import { ModalDialog } from "../ModalDialog";
9+
10+
export const Changelog = () => {
11+
const { isOpen, toggleOpen } = useChangelog();
12+
13+
return (
14+
<ModalDialog title="Changelog" isOpen={isOpen} onOpenChange={toggleOpen}>
15+
<ul className="flex max-w-prose flex-col divide-y divide-neutral-600/50">
16+
{messages.map(({ id, message }) => (
17+
<li key={id}>{message}</li>
18+
))}
19+
</ul>
20+
</ModalDialog>
21+
);
22+
};
23+
24+
type ChangelogMessageProps = {
25+
date: Date;
26+
children: ReactNode | ReactNode[];
27+
};
28+
29+
export const ChangelogMessage = ({ date, children }: ChangelogMessageProps) => {
30+
const dateFormatter = useDateFormatter({
31+
year: "numeric",
32+
month: "short",
33+
day: "numeric",
34+
});
35+
36+
return (
37+
<section className="py-8">
38+
<h2 className="text-sm uppercase text-pythpurple-400">
39+
{dateFormatter.format(date)}
40+
</h2>
41+
{children}
42+
</section>
43+
);
44+
};
45+
46+
type ChangelogSectionProps = {
47+
title: ReactNode;
48+
children: ReactNode | ReactNode[];
49+
};
50+
51+
export const ChangelogSection = ({
52+
title,
53+
children,
54+
}: ChangelogSectionProps) => (
55+
<section className="mt-4">
56+
<h3 className="text-lg font-semibold text-white">{title}</h3>
57+
<div className="flex flex-col gap-2 pl-2 text-sm opacity-70">
58+
{children}
59+
</div>
60+
</section>
61+
);
62+
63+
export const messages = [
64+
{
65+
id: 1,
66+
message: (
67+
<ChangelogMessage date={new Date("2024-10-10")}>
68+
<ChangelogSection title="Milestones">
69+
<div>
70+
<p>
71+
We are pleased to announce the following Oracle Integrity Staking
72+
milestones:
73+
</p>
74+
<ul className="list-disc pl-8">
75+
<li>145M PYTH staked and securing DeFi.</li>
76+
<li>10K unique stakers participating.</li>
77+
<li>492K in PYTH programmatically distributed.</li>
78+
</ul>
79+
</div>
80+
<p>We’re thrilled to see so many community participants.</p>
81+
</ChangelogSection>
82+
<ChangelogSection title="New Features to the Staking Frontend">
83+
<ul className="list-disc pl-4">
84+
<li>
85+
New sort filter for publishers list. Publishers with self-stake
86+
are displayed first by default. You can sort by publisher details,
87+
pool composition, and more.
88+
</li>
89+
<li>
90+
Publishers interested in de-anonymizing themselves can have their
91+
names displayed in the publisher list.
92+
</li>
93+
<li>New OIS live stats added to navigation bar.</li>
94+
<li>
95+
New dialogue added under “Help” where you can view current program
96+
parameters.
97+
</li>
98+
<li>
99+
Option to remove PYTH from the smart contract program for parties
100+
with restricted access to the staking frontend.
101+
</li>
102+
<li>
103+
Full access to Pyth Governance for certain restricted
104+
jurisdictions.
105+
</li>
106+
<li>APYs are now shown as net of delegation fees.</li>
107+
<li>
108+
Updates to educational materials (all Guides and FAQs) for clarity
109+
and readability.
110+
</li>
111+
<li>
112+
New Oracle Integrity Staking{" "}
113+
<Link
114+
href="https://forum.pyth.network/c/oracle-integrity-staking-ois-discussion/8"
115+
className="underline"
116+
target="_blank"
117+
>
118+
discussion catalogue
119+
</Link>{" "}
120+
opened in Pyth DAO forum. Let the community know your thoughts and
121+
feedback!
122+
</li>
123+
</ul>
124+
</ChangelogSection>
125+
<ChangelogSection title="Security">
126+
<p>
127+
The Pyth contributors take security extremely seriously. The
128+
contract code is{" "}
129+
<Link
130+
href="https://github.com/pyth-network/governance/tree/main/staking/programs/staking"
131+
className="underline"
132+
target="_blank"
133+
>
134+
open source
135+
</Link>{" "}
136+
and the upgrade authority is governed by the Pyth DAO. The official{" "}
137+
<Link
138+
href="https://github.com/pyth-network/audit-reports/blob/main/2024_09_11/pyth_cip_final_report.pdf"
139+
className="underline"
140+
target="_blank"
141+
>
142+
audit report
143+
</Link>{" "}
144+
is publicly accessible. All on-chain contract codes are verified
145+
using{" "}
146+
<Link
147+
href="https://github.com/Ellipsis-Labs/solana-verifiable-build/"
148+
className="underline"
149+
target="_blank"
150+
>
151+
Solana verifiable build
152+
</Link>{" "}
153+
and the Pyth DAO governs the upgrade authority.
154+
</p>
155+
</ChangelogSection>
156+
<ChangelogSection title="Best Practices">
157+
<p>
158+
Please remember that publishers have priority for programmatic
159+
rewards distributions. By protocol design, if a pool’s stake cap is
160+
exceeded, the programmatic reward rate for other stakers
161+
participating in that pool will be lower than the Pyth DAO-set
162+
maximum reward rate.
163+
</p>
164+
</ChangelogSection>
165+
<ChangelogSection title="Acknowledgements">
166+
<p>
167+
The Pyth contributors are glad to see so many network participants
168+
getting involved with Oracle Integrity Staking to help secure the
169+
oracle and protect the wider DeFi industry. OIS wouldn’t be possible
170+
without you!
171+
</p>
172+
</ChangelogSection>
173+
<ChangelogSection title="Feedback">
174+
<p>
175+
Please reach out in the official{" "}
176+
<Link
177+
href="https://discord.com/invite/PythNetwork"
178+
className="underline"
179+
target="_blank"
180+
>
181+
Pyth Discord
182+
</Link>{" "}
183+
or the{" "}
184+
<Link
185+
href="https://forum.pyth.network"
186+
className="underline"
187+
target="_blank"
188+
>
189+
Pyth DAO Forum
190+
</Link>{" "}
191+
to share your questions, ideas, or feedback. We want to hear what
192+
you think.
193+
</p>
194+
</ChangelogSection>
195+
</ChangelogMessage>
196+
),
197+
},
198+
];

0 commit comments

Comments
 (0)