Skip to content
This repository was archived by the owner on May 11, 2026. It is now read-only.

Commit 6300de5

Browse files
committed
adds geoblock
1 parent 96f1889 commit 6300de5

6 files changed

Lines changed: 176 additions & 15 deletions

File tree

app/page.tsx

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Header from "@/components/Header";
55
import PolygonAssets from "@/components/PolygonAssets";
66
import TradingSession from "@/components/TradingSession";
77
import MarketTabs from "@/components/Trading/MarketTabs";
8+
import GeoBlockedBanner from "@/components/GeoBlockedBanner";
89

910
export default function Home() {
1011
const {
@@ -15,21 +16,38 @@ export default function Home() {
1516
initializeTradingSession,
1617
endTradingSession,
1718
eoaAddress,
19+
isGeoblocked,
20+
isGeoblockLoading,
21+
geoblockStatus,
1822
} = useTrading();
1923

2024
return (
2125
<div className="p-6 min-h-screen flex flex-col gap-6 max-w-7xl mx-auto">
2226
<Header onEndSession={endTradingSession} />
27+
28+
{/* Show geoblock banner if user is in blocked region */}
29+
{isGeoblocked && !isGeoblockLoading && (
30+
<GeoBlockedBanner geoblockStatus={geoblockStatus} />
31+
)}
32+
2333
<PolygonAssets />
24-
<TradingSession
25-
session={tradingSession}
26-
currentStep={currentStep}
27-
error={sessionError}
28-
isComplete={isTradingSessionComplete}
29-
initialize={initializeTradingSession}
30-
endSession={endTradingSession}
31-
/>
32-
{isTradingSessionComplete && eoaAddress && <MarketTabs />}
34+
35+
{/* Hide trading session initialization when geoblocked */}
36+
{!isGeoblocked && (
37+
<TradingSession
38+
session={tradingSession}
39+
currentStep={currentStep}
40+
error={sessionError}
41+
isComplete={isTradingSessionComplete}
42+
initialize={initializeTradingSession}
43+
endSession={endTradingSession}
44+
/>
45+
)}
46+
47+
{/* Markets are viewable even when geoblocked, but trading buttons should be disabled */}
48+
{(isTradingSessionComplete || isGeoblocked) && eoaAddress && (
49+
<MarketTabs />
50+
)}
3351
</div>
3452
);
3553
}

components/GeoBlockedBanner.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"use client";
2+
3+
import { GeoblockStatus } from "@/hooks/useGeoblock";
4+
5+
interface GeoBlockedBannerProps {
6+
geoblockStatus: GeoblockStatus | null;
7+
}
8+
9+
// This banner is displayed when the user is geoblocked from trading
10+
// It prevents trading initialization while still allowing users to view markets
11+
12+
export default function GeoBlockedBanner({
13+
geoblockStatus,
14+
}: GeoBlockedBannerProps) {
15+
return (
16+
<div className="bg-red-900/30 border border-red-500/50 rounded-lg p-4 mb-6">
17+
<div className="flex items-start gap-3">
18+
<div className="flex-1">
19+
<h3 className="text-red-300 font-semibold text-lg mb-1">
20+
Trading Unavailable in Your Region
21+
</h3>
22+
<p className="text-red-200/80 text-sm mb-2">
23+
Polymarket trading is not available in your location. You can view
24+
markets but cannot place trades or initialize a trading session.
25+
</p>
26+
{geoblockStatus && (
27+
<p className="text-red-200/60 text-xs">
28+
Detected region: {geoblockStatus.country}
29+
{geoblockStatus.region ? `, ${geoblockStatus.region}` : ""}
30+
</p>
31+
)}
32+
</div>
33+
</div>
34+
</div>
35+
);
36+
}
37+

components/Trading/Markets/MarketCard.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { PolymarketMarket } from "@/hooks/useHighVolumeMarkets";
1+
import type { PolymarketMarket } from "@/hooks/useMarkets";
22

33
import Card from "@/components/shared/Card";
44
import Badge from "@/components/shared/Badge";
@@ -9,6 +9,7 @@ import { formatVolume, formatLiquidity } from "@/utils/formatting";
99

1010
interface MarketCardProps {
1111
market: PolymarketMarket;
12+
disabled?: boolean;
1213
onOutcomeClick: (
1314
marketTitle: string,
1415
outcome: string,
@@ -20,6 +21,7 @@ interface MarketCardProps {
2021

2122
export default function MarketCard({
2223
market,
24+
disabled = false,
2325
onOutcomeClick,
2426
}: MarketCardProps) {
2527
const volumeUSD = parseFloat(
@@ -79,6 +81,7 @@ export default function MarketCard({
7981
isClosed={isClosed}
8082
negRisk={negRisk}
8183
marketQuestion={market.question}
84+
disabled={disabled}
8285
onOutcomeClick={onOutcomeClick}
8386
/>
8487
</div>

components/Trading/Markets/OutcomeButtons.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ interface OutcomeButtonsProps {
88
isClosed: boolean;
99
negRisk: boolean;
1010
marketQuestion: string;
11+
disabled?: boolean;
1112
onOutcomeClick: (
1213
marketTitle: string,
1314
outcome: string,
@@ -24,10 +25,13 @@ export default function OutcomeButtons({
2425
isClosed,
2526
negRisk,
2627
marketQuestion,
28+
disabled = false,
2729
onOutcomeClick,
2830
}: OutcomeButtonsProps) {
2931
if (outcomes.length === 0) return null;
3032

33+
const isDisabled = isClosed || disabled;
34+
3135
return (
3236
<div className="flex gap-2 flex-wrap">
3337
{outcomes.map((outcome: string, idx: number) => {
@@ -39,7 +43,7 @@ export default function OutcomeButtons({
3943
<button
4044
key={`outcome-${idx}`}
4145
onClick={() => {
42-
if (!isClosed && tokenId) {
46+
if (!isDisabled && tokenId) {
4347
onOutcomeClick(
4448
marketQuestion,
4549
outcome,
@@ -49,10 +53,10 @@ export default function OutcomeButtons({
4953
);
5054
}
5155
}}
52-
disabled={isClosed || !tokenId}
56+
disabled={isDisabled || !tokenId}
5357
className={cn(
5458
"flex-1 min-w-[120px] px-3 py-2 rounded border transition-all duration-200",
55-
isClosed || !tokenId
59+
isDisabled || !tokenId
5660
? "bg-white/5 border-white/10 cursor-not-allowed opacity-50"
5761
: "bg-white/5 border-white/10 hover:bg-blue-500/20 hover:border-blue-500/40 cursor-pointer"
5862
)}

hooks/useGeoblock.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { useState, useEffect, useCallback } from "react";
2+
import { GEOBLOCK_API_URL } from "@/constants/api";
3+
4+
export type GeoblockStatus = {
5+
blocked: boolean;
6+
ip: string;
7+
country: string;
8+
region: string;
9+
};
10+
11+
type UseGeoblockReturn = {
12+
isBlocked: boolean;
13+
isLoading: boolean;
14+
error: Error | null;
15+
geoblockStatus: GeoblockStatus | null;
16+
recheckGeoblock: () => Promise<void>;
17+
};
18+
19+
// This hook checks if the user is geoblocked from using Polymarket
20+
// Integrators should use this to enforce the same geoblocking rules as Polymarket.com
21+
22+
export default function useGeoblock(): UseGeoblockReturn {
23+
const [isBlocked, setIsBlocked] = useState(false);
24+
const [isLoading, setIsLoading] = useState(true);
25+
const [error, setError] = useState<Error | null>(null);
26+
const [geoblockStatus, setGeoblockStatus] = useState<GeoblockStatus | null>(
27+
null
28+
);
29+
30+
const checkGeoblock = useCallback(async () => {
31+
setIsLoading(true);
32+
setError(null);
33+
34+
try {
35+
const response = await fetch(GEOBLOCK_API_URL, {
36+
method: "GET",
37+
headers: {
38+
Accept: "application/json",
39+
},
40+
});
41+
42+
if (!response.ok) {
43+
throw new Error(`Geoblock API error: ${response.status}`);
44+
}
45+
46+
const data: GeoblockStatus = await response.json();
47+
48+
setGeoblockStatus(data);
49+
setIsBlocked(data.blocked);
50+
} catch (err) {
51+
const error =
52+
err instanceof Error ? err : new Error("Failed to check geoblock");
53+
setError(error);
54+
console.error("Geoblock check failed:", error);
55+
56+
// On error, default to not blocked to avoid false positives
57+
// In production, you may want to block by default for safety
58+
setIsBlocked(false);
59+
} finally {
60+
setIsLoading(false);
61+
}
62+
}, []);
63+
64+
// Check geoblock on mount
65+
useEffect(() => {
66+
checkGeoblock();
67+
}, [checkGeoblock]);
68+
69+
return {
70+
isBlocked,
71+
isLoading,
72+
error,
73+
geoblockStatus,
74+
recheckGeoblock: checkGeoblock,
75+
};
76+
}
77+

providers/TradingProvider.tsx

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
"use client";
22

3-
import { createContext, useContext, ReactNode } from "react";
3+
import { createContext, useContext, ReactNode, useCallback } from "react";
44
import type { ClobClient } from "@polymarket/clob-client";
55
import type { RelayClient } from "@polymarket/builder-relayer-client";
66
import { useWallet } from "./WalletContext";
77
import useClobClient from "@/hooks/useClobClient";
88
import useTradingSession from "@/hooks/useTradingSession";
99
import useSafeDeployment from "@/hooks/useSafeDeployment";
10+
import useGeoblock, { GeoblockStatus } from "@/hooks/useGeoblock";
1011
import { TradingSession, SessionStep } from "@/utils/session";
1112

1213
interface TradingContextType {
@@ -20,6 +21,9 @@ interface TradingContextType {
2021
relayClient: RelayClient | null;
2122
eoaAddress: string | undefined;
2223
safeAddress: string | undefined;
24+
isGeoblocked: boolean;
25+
isGeoblockLoading: boolean;
26+
geoblockStatus: GeoblockStatus | null;
2327
}
2428

2529
const TradingContext = createContext<TradingContextType | null>(null);
@@ -34,12 +38,18 @@ export default function TradingProvider({ children }: { children: ReactNode }) {
3438
const { eoaAddress } = useWallet();
3539
const { derivedSafeAddressFromEoa } = useSafeDeployment(eoaAddress);
3640

41+
const {
42+
isBlocked: isGeoblocked,
43+
isLoading: isGeoblockLoading,
44+
geoblockStatus,
45+
} = useGeoblock();
46+
3747
const {
3848
tradingSession,
3949
currentStep,
4050
sessionError,
4151
isTradingSessionComplete,
42-
initializeTradingSession,
52+
initializeTradingSession: initSession,
4353
endTradingSession,
4454
relayClient,
4555
} = useTradingSession();
@@ -49,6 +59,15 @@ export default function TradingProvider({ children }: { children: ReactNode }) {
4959
isTradingSessionComplete
5060
);
5161

62+
const initializeTradingSession = useCallback(async () => {
63+
if (isGeoblocked) {
64+
throw new Error(
65+
"Trading is not available in your region. Polymarket is geoblocked in your location."
66+
);
67+
}
68+
return initSession();
69+
}, [isGeoblocked, initSession]);
70+
5271
return (
5372
<TradingContext.Provider
5473
value={{
@@ -62,6 +81,9 @@ export default function TradingProvider({ children }: { children: ReactNode }) {
6281
relayClient,
6382
eoaAddress,
6483
safeAddress: derivedSafeAddressFromEoa,
84+
isGeoblocked,
85+
isGeoblockLoading,
86+
geoblockStatus,
6587
}}
6688
>
6789
{children}

0 commit comments

Comments
 (0)