Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions apps/staking/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,14 @@ export default {
],
},
],

async rewrites() {
return [
{
source: "/api/publishers-ranking",
destination:
"https://web-api.pyth.network/publishers_ranking?cluster=pythnet",
},
];
},
};
4 changes: 3 additions & 1 deletion apps/staking/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@bonfida/spl-name-service": "^3.0.0",
"@heroicons/react": "^2.1.4",
"@next/third-parties": "^14.2.5",
"@pythnetwork/hermes-client": "workspace:*",
"@pythnetwork/staking-sdk": "workspace:*",
"@solana/wallet-adapter-base": "^0.9.20",
"@solana/wallet-adapter-react": "^0.15.28",
Expand All @@ -41,7 +42,8 @@
"react-aria-components": "^1.3.3",
"react-dom": "^18.3.1",
"recharts": "^2.12.7",
"swr": "^2.2.5"
"swr": "^2.2.5",
"zod": "^3.23.8"
},
"devDependencies": {
"@axe-core/react": "^4.9.1",
Expand Down
189 changes: 123 additions & 66 deletions apps/staking/src/api.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
// TODO remove these disables when moving off the mock APIs
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/require-await */
/* eslint-disable @typescript-eslint/no-unused-vars */

import type { HermesClient } from "@pythnetwork/hermes-client";
import {
epochToDate,
extractPublisherData,
getAmountByTargetAndState,
getCurrentEpoch,
PositionState,
PythStakingClient,
type StakeAccountPositions,
} from "@pythnetwork/staking-sdk";
import { PublicKey } from "@solana/web3.js";
import { z } from "zod";

const publishersRankingSchema = z
.object({
publisher: z.string(),
rank: z.number(),
numSymbols: z.number(),
timestamp: z.string(),
})
.array();

type Data = {
total: bigint;
Expand Down Expand Up @@ -37,6 +50,7 @@ type Data = {
cooldown: bigint;
cooldown2: bigint;
};
yieldRate: bigint;
integrityStakingPublishers: {
name: string | undefined;
publicKey: PublicKey;
Expand Down Expand Up @@ -144,22 +158,39 @@ export const getStakeAccounts = async (

export const loadData = async (
client: PythStakingClient,
hermesClient: HermesClient,
stakeAccount: StakeAccountPositions,
): Promise<Data> => {
const [
stakeAccountCustody,
publishers,
ownerAtaAccount,
currentEpoch,
poolData,
ownerPythBalance,
unlockSchedule,
poolConfig,
claimableRewards,
currentEpoch,
publisherRankingsResponse,
publisherCaps,
] = await Promise.all([
client.getStakeAccountCustody(stakeAccount.address),
client.getPublishers(),
client.getOwnerPythAtaAccount(),
getCurrentEpoch(client.connection),
client.getPoolDataAccount(),
client.getOwnerPythBalance(),
client.getUnlockSchedule(stakeAccount.address),
client.getPoolConfigAccount(),
client.getClaimableRewards(stakeAccount.address),
getCurrentEpoch(client.connection),
fetch("/api/publishers-ranking"),
hermesClient.getLatestPublisherCaps({
parsed: true,
}),
]);

const publishers = extractPublisherData(poolData);

const publisherRankings = publishersRankingSchema.parse(
await publisherRankingsResponse.json(),
);

const filterGovernancePositions = (positionState: PositionState) =>
getAmountByTargetAndState({
stakeAccountPositions: stakeAccount,
Expand All @@ -179,11 +210,19 @@ export const loadData = async (
epoch: currentEpoch,
});

const getPublisherCap = (publisher: PublicKey) =>
BigInt(
publisherCaps.parsed?.[0]?.publisher_stake_caps.find(
({ publisher: p }) => p === publisher.toBase58(),
)?.cap ?? 0,
);

return {
lastSlash: undefined, // TODO
availableRewards: 0n, // TODO
availableRewards: claimableRewards,
expiringRewards: undefined, // TODO
total: stakeAccountCustody.amount,
yieldRate: poolConfig.y,
governance: {
warmup: filterGovernancePositions(PositionState.LOCKING),
staked: filterGovernancePositions(PositionState.LOCKED),
Expand All @@ -192,24 +231,47 @@ export const loadData = async (
},
unlockSchedule,
locked: unlockSchedule.reduce((sum, { amount }) => sum + amount, 0n),
walletAmount: ownerAtaAccount.amount,
integrityStakingPublishers: publishers.map(({ pubkey: publisher }) => ({
apyHistory: [], // TODO
isSelf: false, // TODO
name: undefined, // TODO
numFeeds: 0, // TODO
poolCapacity: 100n, // TODO
poolUtilization: 0n, // TODO
publicKey: publisher,
qualityRanking: 0, // TODO
selfStake: 0n, // TODO
positions: {
warmup: filterOISPositions(publisher, PositionState.LOCKING),
staked: filterOISPositions(publisher, PositionState.LOCKED),
cooldown: filterOISPositions(publisher, PositionState.PREUNLOCKING),
cooldown2: filterOISPositions(publisher, PositionState.UNLOCKED),
},
})),
walletAmount: ownerPythBalance,
integrityStakingPublishers: publishers.map((publisherData) => {
const publisherPubkeyString = publisherData.pubkey.toBase58();
const publisherRanking = publisherRankings.find(
(ranking) => ranking.publisher === publisherPubkeyString,
);
const apyHistory = publisherData.apyHistory.map(({ epoch, apy }) => ({
date: epochToDate(epoch + 1n),
apy: Number(apy),
}));
return {
apyHistory,
isSelf:
publisherData.stakeAccount?.equals(stakeAccount.address) ?? false,
name: undefined, // TODO
numFeeds: publisherRanking?.numSymbols ?? 0,
poolCapacity: getPublisherCap(publisherData.pubkey),
poolUtilization: publisherData.totalDelegation,
publicKey: publisherData.pubkey,
qualityRanking: publisherRanking?.rank ?? 0,
selfStake: publisherData.selfDelegation,
positions: {
warmup: filterOISPositions(
publisherData.pubkey,
PositionState.LOCKING,
),
staked: filterOISPositions(
publisherData.pubkey,
PositionState.LOCKED,
),
cooldown: filterOISPositions(
publisherData.pubkey,
PositionState.PREUNLOCKING,
),
cooldown2: filterOISPositions(
publisherData.pubkey,
PositionState.UNLOCKED,
),
},
};
}),
};
};

Expand Down Expand Up @@ -253,19 +315,27 @@ export const stakeGovernance = async (
};

export const cancelWarmupGovernance = async (
_client: PythStakingClient,
_stakeAccount: PublicKey,
_amount: bigint,
client: PythStakingClient,
stakeAccount: PublicKey,
amount: bigint,
): Promise<void> => {
throw new NotImplementedError();
await client.unstakeFromGovernance(
stakeAccount,
PositionState.LOCKING,
amount,
);
};

export const unstakeGovernance = async (
_client: PythStakingClient,
_stakeAccount: PublicKey,
_amount: bigint,
client: PythStakingClient,
stakeAccount: PublicKey,
amount: bigint,
): Promise<void> => {
throw new NotImplementedError();
await client.unstakeFromGovernance(
stakeAccount,
PositionState.LOCKED,
amount,
);
};

export const delegateIntegrityStaking = async (
Expand All @@ -278,36 +348,30 @@ export const delegateIntegrityStaking = async (
};

export const cancelWarmupIntegrityStaking = async (
_client: PythStakingClient,
_stakeAccount: PublicKey,
_publisherKey: PublicKey,
_amount: bigint,
client: PythStakingClient,
stakeAccount: PublicKey,
publisherKey: PublicKey,
amount: bigint,
): Promise<void> => {
throw new NotImplementedError();
await client.unstakeFromPublisher(
stakeAccount,
publisherKey,
PositionState.LOCKING,
amount,
);
};

export const unstakeIntegrityStaking = async (
_client: PythStakingClient,
_stakeAccount: PublicKey,
_publisherKey: PublicKey,
_amount: bigint,
client: PythStakingClient,
stakeAccount: PublicKey,
publisherKey: PublicKey,
amount: bigint,
): Promise<void> => {
throw new NotImplementedError();
};

export const calculateApy = (
poolCapacity: bigint,
poolUtilization: bigint,
isSelf: boolean,
) => {
const maxApy = isSelf ? 25 : 20;
const minApy = isSelf ? 10 : 5;
return Math.min(
Math.max(
maxApy - Number((poolUtilization - poolCapacity) / 100_000_000n),
minApy,
),
maxApy,
await client.unstakeFromPublisher(
stakeAccount,
publisherKey,
PositionState.LOCKED,
amount,
);
};

Expand Down Expand Up @@ -367,10 +431,3 @@ const mkMockHistory = (): AccountHistory => [
locked: 0n,
},
];

class NotImplementedError extends Error {
constructor() {
super("Not yet implemented!");
this.name = "NotImplementedError";
}
}
3 changes: 3 additions & 0 deletions apps/staking/src/components/Dashboard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type Props = {
cooldown: bigint;
cooldown2: bigint;
};
yieldRate: bigint;
integrityStakingPublishers: ComponentProps<
typeof OracleIntegrityStaking
>["publishers"];
Expand All @@ -48,6 +49,7 @@ export const Dashboard = ({
integrityStakingPublishers,
locked,
unlockSchedule,
yieldRate,
}: Props) => {
const availableToStakeGovernance = useMemo(
() =>
Expand Down Expand Up @@ -156,6 +158,7 @@ export const Dashboard = ({
cooldown={integrityStakingCooldown}
cooldown2={integrityStakingCooldown2}
publishers={integrityStakingPublishers}
yieldRate={yieldRate}
/>
</DashboardTabPanel>
</Tabs>
Expand Down
Loading
Loading