From 0c65ca283c814907fb84db09e0022ec5ba22281c Mon Sep 17 00:00:00 2001 From: Connor Prussin Date: Sat, 5 Oct 2024 09:13:48 -0700 Subject: [PATCH] feat(staking): allow governance only for some regions --- apps/staking/src/app/geo-blocked/page.tsx | 1 + apps/staking/src/app/governance-only/page.tsx | 1 + apps/staking/src/app/restricted-mode/page.tsx | 1 - .../src/components/AccountSummary/index.tsx | 16 ++++--- .../src/components/Dashboard/index.tsx | 35 ++++++++------- .../src/components/Governance/index.tsx | 8 ++-- apps/staking/src/components/Home/index.tsx | 43 ++++++++++++++----- .../Root/restricted-region-banner.tsx | 8 +++- apps/staking/src/config/isomorphic.ts | 17 +++++--- apps/staking/src/config/server.ts | 5 +++ apps/staking/src/middleware.ts | 29 +++++++++---- 11 files changed, 110 insertions(+), 54 deletions(-) create mode 100644 apps/staking/src/app/geo-blocked/page.tsx create mode 100644 apps/staking/src/app/governance-only/page.tsx delete mode 100644 apps/staking/src/app/restricted-mode/page.tsx diff --git a/apps/staking/src/app/geo-blocked/page.tsx b/apps/staking/src/app/geo-blocked/page.tsx new file mode 100644 index 000000000..92a708b83 --- /dev/null +++ b/apps/staking/src/app/geo-blocked/page.tsx @@ -0,0 +1 @@ +export { GeoBlockedHome as default } from "../../components/Home"; diff --git a/apps/staking/src/app/governance-only/page.tsx b/apps/staking/src/app/governance-only/page.tsx new file mode 100644 index 000000000..768275f15 --- /dev/null +++ b/apps/staking/src/app/governance-only/page.tsx @@ -0,0 +1 @@ +export { GovernanceOnlyHome as default } from "../../components/Home"; diff --git a/apps/staking/src/app/restricted-mode/page.tsx b/apps/staking/src/app/restricted-mode/page.tsx deleted file mode 100644 index fb09aec3b..000000000 --- a/apps/staking/src/app/restricted-mode/page.tsx +++ /dev/null @@ -1 +0,0 @@ -export { RestrictedMode as default } from "../../components/Home"; diff --git a/apps/staking/src/components/AccountSummary/index.tsx b/apps/staking/src/components/AccountSummary/index.tsx index 6f4605265..f147c4f32 100644 --- a/apps/staking/src/components/AccountSummary/index.tsx +++ b/apps/staking/src/components/AccountSummary/index.tsx @@ -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; @@ -58,7 +59,8 @@ export const AccountSummary = ({ availableToWithdraw, availableRewards, expiringRewards, - restrictedMode, + enableGovernance, + enableOis, integrityStakingWarmup, integrityStakingStaked, integrityStakingCooldown, @@ -128,7 +130,7 @@ export const AccountSummary = ({ )}
- {!restrictedMode && ( + {(enableGovernance || enableOis) && ( )} - {!restrictedMode && ( + {enableOis && (
- {restrictedMode && api.type === ApiStateType.Loaded && ( + {!enableOis && api.type === ApiStateType.Loaded && ( } /> - {restrictedMode && api.type === ApiStateType.Loaded && ( + {!enableOis && api.type === ApiStateType.Loaded && ( )} - {!restrictedMode && ( + {enableOis && ( ["publishers"]; - restrictedMode?: boolean | undefined; + enableGovernance: boolean; + enableOis: boolean; }; export const Dashboard = ({ @@ -65,7 +66,8 @@ export const Dashboard = ({ integrityStakingPublishers, unlockSchedule, yieldRate, - restrictedMode, + enableGovernance, + enableOis, }: Props) => { const [tab, setTab] = useState(TabIds.Empty); @@ -138,7 +140,7 @@ export const Dashboard = ({ <>
- {restrictedMode ? ( - - ) : ( + {enableOis ? ( + ) : ( + )}
diff --git a/apps/staking/src/components/Governance/index.tsx b/apps/staking/src/components/Governance/index.tsx index d5fc12fa6..7af33719f 100644 --- a/apps/staking/src/components/Governance/index.tsx +++ b/apps/staking/src/components/Governance/index.tsx @@ -12,7 +12,7 @@ type Props = { staked: bigint; cooldown: bigint; cooldown2: bigint; - restrictedMode?: boolean | undefined; + allowStaking?: boolean | undefined; }; export const Governance = ({ @@ -23,10 +23,10 @@ export const Governance = ({ staked, cooldown, cooldown2, - restrictedMode, + allowStaking, }: Props) => ( } tagline="Vote and Influence the Network" @@ -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", diff --git a/apps/staking/src/components/Home/index.tsx b/apps/staking/src/components/Home/index.tsx index 591dd139d..21b9cd032 100644 --- a/apps/staking/src/components/Home/index.tsx +++ b/apps/staking/src/components/Home/index.tsx @@ -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 = () => ; -export const RestrictedMode = () => ; +export const Home = () => ; +export const GeoBlockedHome = () => ; +export const GovernanceOnlyHome = () => ; 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 ? : ; + return isSSR ? ( + + ) : ( + + ); }; type MountedHomeProps = { - restrictedMode?: boolean | undefined; + enableGovernance: boolean; + enableOis: boolean; }; -const MountedHome = ({ restrictedMode }: MountedHomeProps) => { +const MountedHome = ({ enableGovernance, enableOis }: MountedHomeProps) => { const api = useApi(); switch (api.type) { @@ -56,7 +66,11 @@ const MountedHome = ({ restrictedMode }: MountedHomeProps) => { case ApiStateType.LoadedNoStakeAccount: case ApiStateType.Loaded: { return ( - + ); } } @@ -64,12 +78,14 @@ const MountedHome = ({ restrictedMode }: MountedHomeProps) => { 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, @@ -87,7 +103,12 @@ const StakeAccountLoadedHome = ({ case DashboardDataStateType.Loaded: { return ( - + ); } } diff --git a/apps/staking/src/components/Root/restricted-region-banner.tsx b/apps/staking/src/components/Root/restricted-region-banner.tsx index 66dde5364..2b8bf5681 100644 --- a/apps/staking/src/components/Root/restricted-region-banner.tsx +++ b/apps/staking/src/components/Root/restricted-region-banner.tsx @@ -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 ? (
diff --git a/apps/staking/src/config/isomorphic.ts b/apps/staking/src/config/isomorphic.ts index eb77bfdd8..666864997 100644 --- a/apps/staking/src/config/isomorphic.ts +++ b/apps/staking/src/config/isomorphic.ts @@ -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. */ diff --git a/apps/staking/src/config/server.ts b/apps/staking/src/config/server.ts index c1bb63443..9c83e767b 100644 --- a/apps/staking/src/config/server.ts +++ b/apps/staking/src/config/server.ts @@ -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 { diff --git a/apps/staking/src/middleware.ts b/apps/staking/src/middleware.ts index 032296fa4..bd90b2f06 100644 --- a/apps/staking/src/middleware.ts +++ b/apps/staking/src/middleware.ts @@ -2,13 +2,19 @@ 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 }) @@ -16,9 +22,11 @@ const proxyCheckClient = PROXYCHECK_API_KEY 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 { @@ -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()); @@ -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