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

feat(staking): allow governance only for some regions #1998

Merged
merged 1 commit into from
Oct 7, 2024
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
1 change: 1 addition & 0 deletions apps/staking/src/app/geo-blocked/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { GeoBlockedHome as default } from "../../components/Home";
1 change: 1 addition & 0 deletions apps/staking/src/app/governance-only/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { GovernanceOnlyHome as default } from "../../components/Home";
1 change: 0 additions & 1 deletion apps/staking/src/app/restricted-mode/page.tsx

This file was deleted.

16 changes: 9 additions & 7 deletions apps/staking/src/components/AccountSummary/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ type Props = {
availableRewards: bigint;
expiringRewards: Date | undefined;
availableToWithdraw: bigint;
restrictedMode?: boolean | undefined;
enableGovernance: boolean;
enableOis: boolean;
integrityStakingWarmup: bigint;
integrityStakingStaked: bigint;
integrityStakingCooldown: bigint;
Expand All @@ -58,7 +59,8 @@ export const AccountSummary = ({
availableToWithdraw,
availableRewards,
expiringRewards,
restrictedMode,
enableGovernance,
enableOis,
integrityStakingWarmup,
integrityStakingStaked,
integrityStakingCooldown,
Expand Down Expand Up @@ -128,7 +130,7 @@ export const AccountSummary = ({
</>
)}
<div className="mt-3 flex flex-row items-center gap-4 sm:mt-8">
{!restrictedMode && (
{(enableGovernance || enableOis) && (
<TransferButton
actionName="Add tokens"
actionDescription="Add funds to your balance"
Expand Down Expand Up @@ -163,7 +165,7 @@ export const AccountSummary = ({
className="xl:hidden"
/>
)}
{!restrictedMode && (
{enableOis && (
<DialogTrigger>
<Button variant="secondary" className="xl:hidden">
Claim
Expand All @@ -184,7 +186,7 @@ export const AccountSummary = ({
)}
</div>
</div>
{restrictedMode && api.type === ApiStateType.Loaded && (
{!enableOis && api.type === ApiStateType.Loaded && (
<OisUnstake
api={api}
className="max-w-sm xl:hidden"
Expand All @@ -204,7 +206,7 @@ export const AccountSummary = ({
<WithdrawButton api={api} max={availableToWithdraw} size="small" />
}
/>
{restrictedMode && api.type === ApiStateType.Loaded && (
{!enableOis && api.type === ApiStateType.Loaded && (
<OisUnstake
api={api}
warmup={integrityStakingWarmup}
Expand All @@ -214,7 +216,7 @@ export const AccountSummary = ({
currentEpoch={currentEpoch}
/>
)}
{!restrictedMode && (
{enableOis && (
<BalanceCategory
name="Available Rewards"
amount={availableRewards}
Expand Down
35 changes: 19 additions & 16 deletions apps/staking/src/components/Dashboard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ type Props = {
integrityStakingPublishers: ComponentProps<
typeof OracleIntegrityStaking
>["publishers"];
restrictedMode?: boolean | undefined;
enableGovernance: boolean;
enableOis: boolean;
};

export const Dashboard = ({
Expand All @@ -65,7 +66,8 @@ export const Dashboard = ({
integrityStakingPublishers,
unlockSchedule,
yieldRate,
restrictedMode,
enableGovernance,
enableOis,
}: Props) => {
const [tab, setTab] = useState<TabId>(TabIds.Empty);

Expand Down Expand Up @@ -138,7 +140,7 @@ export const Dashboard = ({
<>
<main
className={clsx("flex w-full flex-col gap-8 xl:px-4 xl:py-6", {
"sm:gap-0": restrictedMode,
"sm:gap-0": !enableOis,
})}
>
<AccountSummary
Expand All @@ -151,25 +153,15 @@ export const Dashboard = ({
availableToWithdraw={availableToWithdraw}
availableRewards={availableRewards}
expiringRewards={expiringRewards}
restrictedMode={restrictedMode}
enableGovernance={enableGovernance}
enableOis={enableOis}
integrityStakingWarmup={integrityStakingWarmup}
integrityStakingStaked={integrityStakingStaked}
integrityStakingCooldown={integrityStakingCooldown}
integrityStakingCooldown2={integrityStakingCooldown2}
currentEpoch={currentEpoch}
/>
{restrictedMode ? (
<Governance
api={api}
currentEpoch={currentEpoch}
availableToStake={availableToStakeGovernance}
warmup={governance.warmup}
staked={governance.staked}
cooldown={governance.cooldown}
cooldown2={governance.cooldown2}
restrictedMode
/>
) : (
{enableOis ? (
<Tabs
selectedKey={tab}
onSelectionChange={setTab}
Expand Down Expand Up @@ -229,6 +221,17 @@ export const Dashboard = ({
/>
</TabPanel>
</Tabs>
) : (
<Governance
api={api}
currentEpoch={currentEpoch}
availableToStake={availableToStakeGovernance}
warmup={governance.warmup}
staked={governance.staked}
cooldown={governance.cooldown}
cooldown2={governance.cooldown2}
allowStaking={enableGovernance}
/>
)}
</main>
<Disclosure />
Expand Down
8 changes: 4 additions & 4 deletions apps/staking/src/components/Governance/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type Props = {
staked: bigint;
cooldown: bigint;
cooldown2: bigint;
restrictedMode?: boolean | undefined;
allowStaking?: boolean | undefined;
};

export const Governance = ({
Expand All @@ -23,10 +23,10 @@ export const Governance = ({
staked,
cooldown,
cooldown2,
restrictedMode,
allowStaking,
}: Props) => (
<ProgramSection
className={clsx({ "border-t sm:border-t-0": restrictedMode })}
className={clsx({ "border-t sm:border-t-0": !allowStaking })}
name="Pyth Governance"
helpDialog={<GovernanceGuide />}
tagline="Vote and Influence the Network"
Expand All @@ -47,7 +47,7 @@ export const Governance = ({
unstake:
api.type === ApiStateType.Loaded ? api.unstakeGovernance : undefined,
unstakeDescription: "Unstake tokens from the Governance program",
...(!restrictedMode && {
...(allowStaking && {
stake:
api.type === ApiStateType.Loaded ? api.stakeGovernance : undefined,
stakeDescription: "Stake funds to participate in governance votes",
Expand Down
43 changes: 32 additions & 11 deletions apps/staking/src/components/Home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,34 @@ const ONE_SECOND_IN_MS = 1000;
const ONE_MINUTE_IN_MS = 60 * ONE_SECOND_IN_MS;
const REFRESH_INTERVAL = 1 * ONE_MINUTE_IN_MS;

export const Home = () => <HomeImpl />;
export const RestrictedMode = () => <HomeImpl restrictedMode />;
export const Home = () => <HomeImpl enableGovernance enableOis />;
export const GeoBlockedHome = () => <HomeImpl />;
export const GovernanceOnlyHome = () => <HomeImpl enableGovernance />;

type HomeImplProps = {
restrictedMode?: boolean | undefined;
enableGovernance?: boolean | undefined;
enableOis?: boolean | undefined;
};

export const HomeImpl = ({ restrictedMode }: HomeImplProps) => {
export const HomeImpl = ({ enableGovernance, enableOis }: HomeImplProps) => {
const isSSR = useIsSSR();

return isSSR ? <Loading /> : <MountedHome restrictedMode={restrictedMode} />;
return isSSR ? (
<Loading />
) : (
<MountedHome
enableGovernance={enableGovernance ?? false}
enableOis={enableOis ?? false}
/>
);
};

type MountedHomeProps = {
restrictedMode?: boolean | undefined;
enableGovernance: boolean;
enableOis: boolean;
};

const MountedHome = ({ restrictedMode }: MountedHomeProps) => {
const MountedHome = ({ enableGovernance, enableOis }: MountedHomeProps) => {
const api = useApi();

switch (api.type) {
Expand All @@ -56,20 +66,26 @@ const MountedHome = ({ restrictedMode }: MountedHomeProps) => {
case ApiStateType.LoadedNoStakeAccount:
case ApiStateType.Loaded: {
return (
<StakeAccountLoadedHome restrictedMode={restrictedMode} api={api} />
<StakeAccountLoadedHome
enableGovernance={enableGovernance}
enableOis={enableOis}
api={api}
/>
);
}
}
};

type StakeAccountLoadedHomeProps = {
api: States[ApiStateType.Loaded] | States[ApiStateType.LoadedNoStakeAccount];
restrictedMode?: boolean | undefined;
enableGovernance: boolean;
enableOis: boolean;
};

const StakeAccountLoadedHome = ({
api,
restrictedMode,
enableGovernance,
enableOis,
}: StakeAccountLoadedHomeProps) => {
const data = useData(api.dashboardDataCacheKey, api.loadData, {
refreshInterval: REFRESH_INTERVAL,
Expand All @@ -87,7 +103,12 @@ const StakeAccountLoadedHome = ({

case DashboardDataStateType.Loaded: {
return (
<Dashboard {...data.data} api={api} restrictedMode={restrictedMode} />
<Dashboard
{...data.data}
api={api}
enableGovernance={enableGovernance}
enableOis={enableOis}
/>
);
}
}
Expand Down
8 changes: 6 additions & 2 deletions apps/staking/src/components/Root/restricted-region-banner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@

import { useSelectedLayoutSegment } from "next/navigation";

import { RESTRICTED_MODE_SEGMENT } from "../../config/isomorphic";
import {
GEO_BLOCKED_SEGMENT,
GOVERNANCE_ONLY_SEGMENT,
} from "../../config/isomorphic";
import { Link } from "../Link";

export const RestrictedRegionBanner = () => {
const segment = useSelectedLayoutSegment();
const isRestrictedMode = segment === RESTRICTED_MODE_SEGMENT;
const isRestrictedMode =
segment === GEO_BLOCKED_SEGMENT || segment === GOVERNANCE_ONLY_SEGMENT;

return isRestrictedMode ? (
<div className="mx-auto mt-8 flex max-w-3xl flex-col gap-2 bg-red-900 px-8 py-6">
Expand Down
17 changes: 12 additions & 5 deletions apps/staking/src/config/isomorphic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,26 @@
export const IS_PRODUCTION_BUILD = process.env.NODE_ENV === "production";

/**
* Region or VPN-blocked requests will be redirected here if they are eligible
* for "restricted mode" (aka only allowing withdrawals). This is used in the
* Region blocked requests will be rewritten here. This is used in the
* middleware to implement the block, and also consumed in any components that
* are part of the page layout but need to know if the request is blocked from
* accessing the app, such as the WalletButton in the app header.
*
* Don't change unless you also change the relevant app route path to match.
*/
export const RESTRICTED_MODE_SEGMENT = "restricted-mode";
export const GEO_BLOCKED_SEGMENT = "geo-blocked";

/**
* Similar to `RESTRICTED_MODE_SEGMENT`; this is where vpn-blocked traffic will
* be rewritten to if it isn't eligible for restricted mode.
* Similar to `GEO_BLOCKED_SEGMENT`; this is where governance-only region
* requests are rewritten to.
*
* Don't change unless you also change the relevant app route path to match.
*/
export const GOVERNANCE_ONLY_SEGMENT = "governance-only";

/**
* Similar to `GEO_BLOCKED_SEGMENT`; this is where vpn-blocked traffic will be
* rewritten to.
*
* Don't change unless you also change the relevant app route path to match.
*/
Expand Down
5 changes: 5 additions & 0 deletions apps/staking/src/config/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ export const WALLETCONNECT_PROJECT_ID = demandInProduction(
export const MAINNET_RPC = process.env.MAINNET_RPC;
export const HERMES_URL = getOr("HERMES_URL", "https://hermes.pyth.network");
export const BLOCKED_REGIONS = transformOr("BLOCKED_REGIONS", fromCsv, []);
export const GOVERNANCE_ONLY_REGIONS = transformOr(
"GOVERNANCE_ONLY_REGIONS",
fromCsv,
[],
);
export const PROXYCHECK_API_KEY = demandInProduction("PROXYCHECK_API_KEY");

class MissingEnvironmentError extends Error {
Expand Down
29 changes: 21 additions & 8 deletions apps/staking/src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,31 @@ import { type NextRequest, NextResponse } from "next/server";
import ProxyCheck from "proxycheck-ts";

import {
RESTRICTED_MODE_SEGMENT,
GEO_BLOCKED_SEGMENT,
GOVERNANCE_ONLY_SEGMENT,
VPN_BLOCKED_SEGMENT,
} from "./config/isomorphic";
import { BLOCKED_REGIONS, PROXYCHECK_API_KEY } from "./config/server";
import {
BLOCKED_REGIONS,
GOVERNANCE_ONLY_REGIONS,
PROXYCHECK_API_KEY,
} from "./config/server";

const RESTRICTED_MODE_PATH = `/${RESTRICTED_MODE_SEGMENT}`;
const VPN_BLOCK_PATH = `/${VPN_BLOCKED_SEGMENT}`;
const GEO_BLOCKED_PATH = `/${GEO_BLOCKED_SEGMENT}`;
const GOVERNANCE_ONLY_PATH = `/${GOVERNANCE_ONLY_SEGMENT}`;
const VPN_BLOCKED_PATH = `/${VPN_BLOCKED_SEGMENT}`;

const proxyCheckClient = PROXYCHECK_API_KEY
? new ProxyCheck({ api_key: PROXYCHECK_API_KEY })
: undefined;

export const middleware = async (request: NextRequest) => {
if (await isProxyBlocked(request)) {
return rewrite(request, VPN_BLOCK_PATH);
return rewrite(request, VPN_BLOCKED_PATH);
} else if (isGovernanceOnlyRegion(request)) {
return rewrite(request, GOVERNANCE_ONLY_PATH);
} else if (isRegionBlocked(request)) {
return rewrite(request, RESTRICTED_MODE_PATH);
return rewrite(request, GEO_BLOCKED_PATH);
} else if (isBlockedSegment(request)) {
return rewrite(request, "/not-found");
} else {
Expand All @@ -29,6 +37,10 @@ export const middleware = async (request: NextRequest) => {
const rewrite = (request: NextRequest, path: string) =>
NextResponse.rewrite(new URL(path, request.url));

const isGovernanceOnlyRegion = ({ geo }: NextRequest) =>
geo?.country !== undefined &&
GOVERNANCE_ONLY_REGIONS.includes(geo.country.toLowerCase());

const isRegionBlocked = ({ geo }: NextRequest) =>
geo?.country !== undefined &&
BLOCKED_REGIONS.includes(geo.country.toLowerCase());
Expand All @@ -43,8 +55,9 @@ const isProxyBlocked = async ({ ip }: NextRequest) => {
};

const isBlockedSegment = ({ nextUrl: { pathname } }: NextRequest) =>
pathname.startsWith(`/${VPN_BLOCKED_SEGMENT}`) ||
pathname.startsWith(`/${RESTRICTED_MODE_SEGMENT}`);
pathname.startsWith(VPN_BLOCKED_PATH) ||
pathname.startsWith(GEO_BLOCKED_PATH) ||
pathname.startsWith(GOVERNANCE_ONLY_PATH);

export const config = {
// Next.js requires that this is a static string and fails to read it if it's
Expand Down
Loading