Skip to content

Commit a8b08ee

Browse files
authored
Merge pull request #2005 from pyth-network/feat/add-pythnet-client
feat: fetch data from pythnet
2 parents 1a37585 + 7d59721 commit a8b08ee

File tree

14 files changed

+454
-171
lines changed

14 files changed

+454
-171
lines changed

apps/staking/src/api.ts

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
getAmountByTargetAndState,
66
getCurrentEpoch,
77
PositionState,
8+
PythnetClient,
89
PythStakingClient,
910
type StakeAccountPositions,
1011
} from "@pythnetwork/staking-sdk";
@@ -43,6 +44,8 @@ type Data = {
4344
cooldown2: bigint;
4445
};
4546
yieldRate: bigint;
47+
m: bigint;
48+
z: bigint;
4649
integrityStakingPublishers: {
4750
name: string | undefined;
4851
publicKey: PublicKey;
@@ -96,18 +99,29 @@ export const getStakeAccount = async (
9699

97100
export const loadData = async (
98101
client: PythStakingClient,
102+
pythnetClient: PythnetClient,
99103
hermesClient: HermesClient,
100104
stakeAccount?: PublicKey | undefined,
101105
): Promise<Data> =>
102106
stakeAccount === undefined
103-
? loadDataNoStakeAccount(client, hermesClient)
104-
: loadDataForStakeAccount(client, hermesClient, stakeAccount);
107+
? loadDataNoStakeAccount(client, pythnetClient, hermesClient)
108+
: loadDataForStakeAccount(
109+
client,
110+
pythnetClient,
111+
hermesClient,
112+
stakeAccount,
113+
);
105114

106115
const loadDataNoStakeAccount = async (
107116
client: PythStakingClient,
117+
pythnetClient: PythnetClient,
108118
hermesClient: HermesClient,
109119
): Promise<Data> => {
110-
const { publishers, ...baseInfo } = await loadBaseInfo(client, hermesClient);
120+
const { publishers, ...baseInfo } = await loadBaseInfo(
121+
client,
122+
pythnetClient,
123+
hermesClient,
124+
);
111125

112126
return {
113127
...baseInfo,
@@ -128,6 +142,7 @@ const loadDataNoStakeAccount = async (
128142

129143
const loadDataForStakeAccount = async (
130144
client: PythStakingClient,
145+
pythnetClient: PythnetClient,
131146
hermesClient: HermesClient,
132147
stakeAccount: PublicKey,
133148
): Promise<Data> => {
@@ -138,7 +153,7 @@ const loadDataForStakeAccount = async (
138153
claimableRewards,
139154
stakeAccountPositions,
140155
] = await Promise.all([
141-
loadBaseInfo(client, hermesClient),
156+
loadBaseInfo(client, pythnetClient, hermesClient),
142157
client.getStakeAccountCustody(stakeAccount),
143158
client.getUnlockSchedule(stakeAccount),
144159
client.getClaimableRewards(stakeAccount),
@@ -197,36 +212,49 @@ const loadDataForStakeAccount = async (
197212

198213
const loadBaseInfo = async (
199214
client: PythStakingClient,
215+
pythnetClient: PythnetClient,
200216
hermesClient: HermesClient,
201217
) => {
202-
const [publishers, walletAmount, poolConfig, currentEpoch] =
218+
const [publishers, walletAmount, poolConfig, currentEpoch, parameters] =
203219
await Promise.all([
204-
loadPublisherData(client, hermesClient),
220+
loadPublisherData(client, pythnetClient, hermesClient),
205221
client.getOwnerPythBalance(),
206222
client.getPoolConfigAccount(),
207223
getCurrentEpoch(client.connection),
224+
pythnetClient.getStakeCapParameters(),
208225
]);
209226

210-
return { yieldRate: poolConfig.y, walletAmount, publishers, currentEpoch };
227+
return {
228+
yieldRate: poolConfig.y,
229+
walletAmount,
230+
publishers,
231+
currentEpoch,
232+
m: parameters.m,
233+
z: parameters.z,
234+
};
211235
};
212236

213237
const loadPublisherData = async (
214238
client: PythStakingClient,
239+
pythnetClient: PythnetClient,
215240
hermesClient: HermesClient,
216241
) => {
217-
const [poolData, publisherRankings, publisherCaps] = await Promise.all([
218-
client.getPoolDataAccount(),
219-
getPublisherRankings(),
220-
hermesClient.getLatestPublisherCaps({
221-
parsed: true,
222-
}),
223-
]);
242+
const [poolData, publisherRankings, publisherCaps, publisherNumberOfSymbols] =
243+
await Promise.all([
244+
client.getPoolDataAccount(),
245+
getPublisherRankings(),
246+
hermesClient.getLatestPublisherCaps({
247+
parsed: true,
248+
}),
249+
pythnetClient.getPublisherNumberOfSymbols(),
250+
]);
224251

225252
return extractPublisherData(poolData).map((publisher) => {
226253
const publisherPubkeyString = publisher.pubkey.toBase58();
227254
const publisherRanking = publisherRankings.find(
228255
(ranking) => ranking.publisher === publisherPubkeyString,
229256
);
257+
const numberOfSymbols = publisherNumberOfSymbols[publisherPubkeyString];
230258
const apyHistory = publisher.apyHistory.map(({ epoch, apy, selfApy }) => ({
231259
date: epochToDate(epoch + 1n),
232260
apy,
@@ -236,7 +264,7 @@ const loadPublisherData = async (
236264
return {
237265
apyHistory,
238266
name: undefined, // TODO
239-
numFeeds: publisherRanking?.numSymbols ?? 0,
267+
numFeeds: numberOfSymbols ?? 0,
240268
poolCapacity: getPublisherCap(publisherCaps, publisher.pubkey),
241269
poolUtilization: publisher.totalDelegation,
242270
poolUtilizationDelta: publisher.totalDelegationDelta,

apps/staking/src/components/Header/help-menu.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@ import {
77
import { useState, useCallback } from "react";
88
import { MenuTrigger, Button } from "react-aria-components";
99

10+
import { ProgramParameters } from "./program-pramaeters";
11+
import { StateType, useApi } from "../../hooks/use-api";
1012
import { GeneralFaq } from "../GeneralFaq";
1113
import { GovernanceGuide } from "../GovernanceGuide";
1214
import { Menu, MenuItem, Section, Separator } from "../Menu";
1315
import { OracleIntegrityStakingGuide } from "../OracleIntegrityStakingGuide";
1416
import { PublisherFaq } from "../PublisherFaq";
1517

1618
export const HelpMenu = () => {
19+
const api = useApi();
1720
const [faqOpen, setFaqOpen] = useState(false);
1821
const openFaq = useCallback(() => {
1922
setFaqOpen(true);
@@ -34,6 +37,11 @@ export const HelpMenu = () => {
3437
setPublisherFaqOpen(true);
3538
}, [setPublisherFaqOpen]);
3639

40+
const [parametersOpen, setParametersOpen] = useState(false);
41+
const openParameters = useCallback(() => {
42+
setParametersOpen(true);
43+
}, [setParametersOpen]);
44+
3745
return (
3846
<>
3947
<MenuTrigger>
@@ -65,6 +73,17 @@ export const HelpMenu = () => {
6573
Data Publisher Guide
6674
</MenuItem>
6775
</Section>
76+
{(api.type === StateType.Loaded ||
77+
api.type === StateType.LoadedNoStakeAccount) && (
78+
<>
79+
<Separator />
80+
<Section>
81+
<MenuItem onAction={openParameters}>
82+
Current Program Parameters
83+
</MenuItem>
84+
</Section>
85+
</>
86+
)}
6887
</Menu>
6988
</MenuTrigger>
7089
<GeneralFaq isOpen={faqOpen} onOpenChange={setFaqOpen} />
@@ -80,6 +99,14 @@ export const HelpMenu = () => {
8099
isOpen={publisherFaqOpen}
81100
onOpenChange={setPublisherFaqOpen}
82101
/>
102+
{(api.type === StateType.Loaded ||
103+
api.type === StateType.LoadedNoStakeAccount) && (
104+
<ProgramParameters
105+
api={api}
106+
isOpen={parametersOpen}
107+
onOpenChange={setParametersOpen}
108+
/>
109+
)}
83110
</>
84111
);
85112
};
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import type { ComponentProps, ReactNode } from "react";
2+
3+
import type { StateType, States } from "../../hooks/use-api";
4+
import { StateType as DataStateType, useData } from "../../hooks/use-data";
5+
import { tokensToString } from "../../tokens";
6+
import { Link } from "../Link";
7+
import { ModalDialog } from "../ModalDialog";
8+
import { Tokens } from "../Tokens";
9+
10+
const ONE_SECOND_IN_MS = 1000;
11+
const ONE_MINUTE_IN_MS = 60 * ONE_SECOND_IN_MS;
12+
const REFRESH_INTERVAL = 1 * ONE_MINUTE_IN_MS;
13+
14+
type Props = Omit<ComponentProps<typeof ModalDialog>, "title" | "children"> & {
15+
api: States[StateType.Loaded] | States[StateType.LoadedNoStakeAccount];
16+
};
17+
18+
export const ProgramParameters = ({ api, ...props }: Props) => {
19+
const data = useData(api.dashboardDataCacheKey, api.loadData, {
20+
refreshInterval: REFRESH_INTERVAL,
21+
});
22+
23+
return (
24+
<ModalDialog
25+
title="Program Parameters"
26+
description={
27+
<>
28+
See the current program parameters. For more details, see{" "}
29+
<Link
30+
href="https://docs.pyth.network/home/oracle-integrity-staking/mathematical-representation"
31+
className="underline"
32+
target="_blank"
33+
>
34+
the docs
35+
</Link>
36+
</>
37+
}
38+
{...props}
39+
>
40+
<ul className="mb-4 mt-8 flex flex-col gap-4 sm:mb-8 sm:mt-16">
41+
<Parameter
42+
value={
43+
data.type === DataStateType.Loaded ? (
44+
<Tokens>{data.data.m}</Tokens>
45+
) : (
46+
<Loading />
47+
)
48+
}
49+
variable="M"
50+
>
51+
A constant parameter representing the target stake per symbol
52+
</Parameter>
53+
<Parameter
54+
value={
55+
data.type === DataStateType.Loaded ? (
56+
data.data.z.toString()
57+
) : (
58+
<Loading />
59+
)
60+
}
61+
variable="Z"
62+
>
63+
A constant parameter to control cap contribution from symbols with a
64+
low number of publishers
65+
</Parameter>
66+
<Parameter
67+
value={
68+
data.type === DataStateType.Loaded ? (
69+
`${tokensToString(data.data.yieldRate * 100n)}% / epoch`
70+
) : (
71+
<Loading />
72+
)
73+
}
74+
variable="y"
75+
>
76+
The cap to the rate of rewards for any pool
77+
</Parameter>
78+
</ul>
79+
</ModalDialog>
80+
);
81+
};
82+
83+
type ParameterProps = {
84+
value: ReactNode;
85+
variable: ReactNode;
86+
children: ReactNode;
87+
};
88+
89+
const Parameter = ({ variable, value, children }: ParameterProps) => (
90+
<li className="relative rounded-md bg-white/5 p-5">
91+
<div className="absolute right-2 top-2 grid size-6 place-content-center rounded-md bg-pythpurple-100 text-center text-sm font-semibold text-pythpurple-950">
92+
{variable}
93+
</div>
94+
<div className="mb-2 text-2xl font-semibold leading-none">{value}</div>
95+
<p className="max-w-sm text-sm opacity-60">{children}</p>
96+
</li>
97+
);
98+
99+
const Loading = () => (
100+
<div className="h-6 w-10 animate-pulse rounded-md bg-white/30" />
101+
);

apps/staking/src/components/Root/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
WALLETCONNECT_PROJECT_ID,
1414
MAINNET_RPC,
1515
HERMES_URL,
16+
PYTHNET_RPC,
1617
} from "../../config/server";
1718
import { ApiProvider } from "../../hooks/use-api";
1819
import { LoggerProvider } from "../../hooks/use-logger";
@@ -79,7 +80,7 @@ const HtmlWithProviders = ({ lang, ...props }: HTMLProps<HTMLHtmlElement>) => (
7980
walletConnectProjectId={WALLETCONNECT_PROJECT_ID}
8081
mainnetRpc={MAINNET_RPC}
8182
>
82-
<ApiProvider hermesUrl={HERMES_URL}>
83+
<ApiProvider hermesUrl={HERMES_URL} pythnetRpcUrl={PYTHNET_RPC}>
8384
<ToastProvider>
8485
<html lang={lang} {...props} />
8586
</ToastProvider>

apps/staking/src/config/server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export const WALLETCONNECT_PROJECT_ID = demandInProduction(
5252
"WALLETCONNECT_PROJECT_ID",
5353
);
5454
export const MAINNET_RPC = process.env.MAINNET_RPC;
55+
export const PYTHNET_RPC = getOr("PYTHNET_RPC", "https://pythnet.rpcpool.com");
5556
export const HERMES_URL = getOr("HERMES_URL", "https://hermes.pyth.network");
5657
export const BLOCKED_REGIONS = transformOr("BLOCKED_REGIONS", fromCsv, []);
5758
export const IP_ALLOWLIST = transformOr("IP_ALLOWLIST", fromCsv, []);

0 commit comments

Comments
 (0)