import { InformationCircleIcon } from "@heroicons/react/24/outline"; import { epochToDate } from "@pythnetwork/staking-sdk"; import clsx from "clsx"; import Image from "next/image"; import { type ComponentProps, type ReactNode, useCallback, useState, useMemo, } from "react"; import { DialogTrigger, Button as ReactAriaButton, } from "react-aria-components"; import background from "./background.png"; import { type States, StateType as ApiStateType } from "../../hooks/use-api"; import { StateType, useAsync } from "../../hooks/use-async"; import { useToast } from "../../hooks/use-toast"; import { Button } from "../Button"; import { Date } from "../Date"; import { ErrorMessage } from "../ErrorMessage"; import { ModalDialog } from "../ModalDialog"; import { Tokens } from "../Tokens"; import { TransferButton } from "../TransferButton"; type Props = { api: States[ApiStateType.Loaded] | States[ApiStateType.LoadedNoStakeAccount]; total: bigint; locked: bigint; unlockSchedule: { amount: bigint; date: Date; }[]; lastSlash: | { amount: bigint; date: Date; } | undefined; walletAmount: bigint; availableRewards: bigint; expiringRewards: Date | undefined; availableToWithdraw: bigint; enableGovernance: boolean; enableOis: boolean; integrityStakingWarmup: bigint; integrityStakingStaked: bigint; integrityStakingCooldown: bigint; integrityStakingCooldown2: bigint; currentEpoch: bigint; }; export const AccountSummary = ({ api, locked, unlockSchedule, lastSlash, walletAmount, total, availableToWithdraw, availableRewards, expiringRewards, enableGovernance, enableOis, integrityStakingWarmup, integrityStakingStaked, integrityStakingCooldown, integrityStakingCooldown2, currentEpoch, }: Props) => (
Total Balance
{total} {lastSlash && (

{lastSlash.amount} were slashed on{" "} {lastSlash.date}

)}
{locked > 0n && ( <>
{locked}
locked included
Show Unlock Schedule
{unlockSchedule.map((unlock, i) => ( ))}
Date Amount
{unlock.date} {unlock.amount}
)}
{(enableGovernance || enableOis) && ( )} {availableToWithdraw === 0n ? (

You have no tokens available for withdrawal

You can only withdraw tokens that are unlocked and not staked in either OIS or Pyth Governance
) : ( )} {enableOis && ( {availableRewards === 0n || api.type === ApiStateType.LoadedNoStakeAccount ? (

You have no rewards available to be claimed

) : ( )}
)}
{!enableOis && api.type === ApiStateType.Loaded && ( )}
} /> {!enableOis && api.type === ApiStateType.Loaded && ( )} {enableOis && ( ) : ( ) } {...(expiringRewards !== undefined && availableRewards > 0n && { warning: ( <> Rewards expire one year from the epoch in which they were earned. You have rewards expiring on{" "} {expiringRewards}. ), })} /> )}
); type OisUnstakeProps = { api: States[ApiStateType.Loaded]; warmup: bigint; staked: bigint; cooldown: bigint; cooldown2: bigint; currentEpoch: bigint; className?: string | undefined; }; const OisUnstake = ({ api, warmup, staked, cooldown, cooldown2, currentEpoch, className, }: OisUnstakeProps) => { const stakedPlusWarmup = useMemo(() => staked + warmup, [staked, warmup]); const totalCooldown = useMemo( () => cooldown + cooldown2, [cooldown, cooldown2], ); const total = useMemo( () => staked + warmup + cooldown + cooldown2, [staked, warmup, cooldown, cooldown2], ); const toast = useToast(); const { state, execute } = useAsync(api.unstakeAllIntegrityStaking); const doUnstakeAll = useCallback(() => { execute() .then(() => { toast.success( "Your tokens are now cooling down and will be available to withdraw at the end of the next epoch", ); }) .catch((error: unknown) => { toast.error(error); }); }, [execute, toast]); // eslint-disable-next-line unicorn/no-null return total === 0n ? null : (

{stakedPlusWarmup > 0n ? ( <> You have tokens that are staked or in warmup to OIS. You are not eligible to participate in OIS because you are in a restricted region. Please unstake your tokens here and wait for the cooldown. ) : ( <>You have OIS tokens in cooldown. )}

{stakedPlusWarmup > 0n && totalCooldown > 0n && (

Cooldown Summary

)} {cooldown > 0n && (
{cooldown} end{" "} {epochToDate(currentEpoch + 2n)}
)} {cooldown2 > 0n && (
{cooldown2} end{" "} {epochToDate(currentEpoch + 1n)}
)} } action={ <> {stakedPlusWarmup > 0n && ( )} } /> ); }; type WithdrawButtonProps = Omit< ComponentProps, "variant" | "actionDescription" | "actionName" | "transfer" | "successMessage" > & { api: States[ApiStateType.Loaded] | States[ApiStateType.LoadedNoStakeAccount]; }; const WithdrawButton = ({ api, ...props }: WithdrawButtonProps) => (
You can only withdraw tokens that are unlocked and not staked in either OIS or Pyth Governance
); type BalanceCategoryProps = { name: string; amount: bigint; description: ReactNode; action: ReactNode; warning?: ReactNode | undefined; className?: string | undefined; }; const BalanceCategory = ({ className, name, amount, description, action, warning, }: BalanceCategoryProps) => (
{name}
{amount}
{description}
{action} {warning &&
{warning}
}
); type ClaimDialogProps = { availableRewards: bigint; expiringRewards: Date | undefined; api: States[ApiStateType.Loaded]; }; const ClaimDialog = ({ api, expiringRewards, availableRewards, }: ClaimDialogProps) => { const [closeDisabled, setCloseDisabled] = useState(false); return ( {({ close }) => ( )} ); }; type ClaimDialogContentsProps = { availableRewards: bigint; expiringRewards: Date | undefined; api: States[ApiStateType.Loaded]; close: () => void; setCloseDisabled: (value: boolean) => void; }; const ClaimDialogContents = ({ api, expiringRewards, availableRewards, close, setCloseDisabled, }: ClaimDialogContentsProps) => { const { state, execute } = useAsync(api.claim); const toast = useToast(); const doClaim = useCallback(() => { setCloseDisabled(true); execute() .then(() => { close(); toast.success("You have claimed your rewards"); }) .catch(() => { /* no-op since this is already handled in the UI using `state` and is logged in useAsync */ }) .finally(() => { setCloseDisabled(false); }); }, [execute, toast, close, setCloseDisabled]); return ( <>

Claim your {availableRewards} rewards

{expiringRewards && (
Rewards expire one year from the epoch in which they were earned. You have rewards expiring on {expiringRewards}.
)} {state.type === StateType.Error && (
)}
); }; type ClaimButtonProps = Omit< ComponentProps, "onClick" | "disabled" | "loading" > & { api: States[ApiStateType.Loaded]; }; const ClaimButton = ({ api, ...props }: ClaimButtonProps) => { const { state, execute } = useAsync(api.claim); const toast = useToast(); const doClaim = useCallback(() => { execute() .then(() => { toast.success("You have claimed your rewards"); }) .catch((error: unknown) => { toast.error(error); }); }, [execute, toast]); return ( ); };