diff --git a/next.config.mjs b/next.config.mjs
index 8aa55005a7..e4c9d08baa 100644
--- a/next.config.mjs
+++ b/next.config.mjs
@@ -13,8 +13,6 @@ const withPWA = NextPwa({
/** @type {import('next').NextConfig} */
const nextConfig = {
- output: 'export', // static site export
-
images: {
unoptimized: true,
},
diff --git a/package.json b/package.json
index eea885d6c4..19f40f3446 100644
--- a/package.json
+++ b/package.json
@@ -17,7 +17,7 @@
"test:coverage": "yarn test --coverage --watchAll=false",
"cmp": "./scripts/cmp.sh",
"routes": "node scripts/generate-routes.js > src/config/routes.ts && prettier -w src/config/routes.ts && cat src/config/routes.ts",
- "css-vars": "ts-node-esm ./scripts/css-vars.ts > ./src/styles/vars.css && prettier -w src/styles/vars.css",
+ "css-vars": "npx -y tsx ./scripts/css-vars.ts > ./src/styles/vars.css && prettier -w src/styles/vars.css",
"generate-types": "typechain --target ethers-v5 --out-dir src/types/contracts ./node_modules/@safe-global/safe-deployments/dist/assets/**/*.json ./node_modules/@safe-global/safe-modules-deployments/dist/assets/**/*.json ./node_modules/@openzeppelin/contracts/build/contracts/ERC20.json ./node_modules/@openzeppelin/contracts/build/contracts/ERC721.json",
"postinstall": "yarn generate-types && yarn css-vars",
"analyze": "cross-env ANALYZE=true yarn build",
@@ -29,7 +29,7 @@
"static-serve": "yarn build && yarn serve"
},
"engines": {
- "node": ">=16"
+ "node": ">=18"
},
"pre-commit": [
"lint"
diff --git a/src/components/terms/clabs.tsx b/src/components/terms/clabs.tsx
new file mode 100644
index 0000000000..12ddf1c6fd
--- /dev/null
+++ b/src/components/terms/clabs.tsx
@@ -0,0 +1,14 @@
+import { Typography } from '@mui/material'
+
+const Terms = () => {
+ return (
+
+
+ Terms and Conditions
+
+
This app shall not be accessed in any country or region sanctioned by US Law.
+
+ )
+}
+
+export default Terms
diff --git a/src/pages/apps/custom.tsx b/src/pages/apps/custom.tsx
index 56932a0511..e839e64c03 100644
--- a/src/pages/apps/custom.tsx
+++ b/src/pages/apps/custom.tsx
@@ -1,5 +1,5 @@
import { useState } from 'react'
-import type { NextPage } from 'next'
+import type { NextPage, GetServerSideProps } from 'next'
import Head from 'next/head'
import { useSafeApps } from '@/hooks/safe-apps/useSafeApps'
@@ -8,6 +8,11 @@ import SafeAppList from '@/components/safe-apps/SafeAppList'
import SafeAppsSDKLink from '@/components/safe-apps/SafeAppsSDKLink'
import { RemoveCustomAppModal } from '@/components/safe-apps/RemoveCustomAppModal'
import type { SafeAppData } from '@safe-global/safe-gateway-typescript-sdk'
+import { checkForForbiddenRegions } from '@/utils/geo'
+
+export const getServerSideProps: GetServerSideProps<{}> = async ({ req, res }) => {
+ return checkForForbiddenRegions(req, res) ?? { props: {} }
+}
const CustomSafeApps: NextPage = () => {
// TODO: create a custom hook instead of use useSafeApps
diff --git a/src/pages/apps/index.tsx b/src/pages/apps/index.tsx
index 347ec4b99e..25938eda1f 100644
--- a/src/pages/apps/index.tsx
+++ b/src/pages/apps/index.tsx
@@ -8,6 +8,12 @@ import SafeAppsSDKLink from '@/components/safe-apps/SafeAppsSDKLink'
import SafeAppsHeader from '@/components/safe-apps/SafeAppsHeader'
import SafeAppList from '@/components/safe-apps/SafeAppList'
import { AppRoutes } from '@/config/routes'
+import type { GetServerSideProps } from 'next'
+import { checkForForbiddenRegions } from '@/utils/geo'
+
+export const getServerSideProps: GetServerSideProps<{}> = async ({ req, res }) => {
+ return checkForForbiddenRegions(req, res) ?? { props: {} }
+}
const SafeApps: NextPage = () => {
const router = useRouter()
diff --git a/src/pages/apps/open.tsx b/src/pages/apps/open.tsx
index 1c83cdd96b..98514286be 100644
--- a/src/pages/apps/open.tsx
+++ b/src/pages/apps/open.tsx
@@ -1,8 +1,8 @@
-import type { NextPage } from 'next'
+import type { NextPage, GetServerSideProps } from 'next'
import { useRouter } from 'next/router'
import { useCallback } from 'react'
import { Box, CircularProgress } from '@mui/material'
-
+import { checkForForbiddenRegions } from '@/utils/geo'
import { useSafeAppUrl } from '@/hooks/safe-apps/useSafeAppUrl'
import { useSafeApps } from '@/hooks/safe-apps/useSafeApps'
import SafeAppsInfoModal from '@/components/safe-apps/SafeAppsInfoModal'
@@ -84,3 +84,7 @@ const SafeApps: NextPage = () => {
}
export default SafeApps
+
+export const getServerSideProps: GetServerSideProps<{}> = async ({ req, res }) => {
+ return checkForForbiddenRegions(req, res) ?? { props: {} }
+}
diff --git a/src/pages/balances/index.tsx b/src/pages/balances/index.tsx
index e42868d988..43e1d95bf7 100644
--- a/src/pages/balances/index.tsx
+++ b/src/pages/balances/index.tsx
@@ -1,10 +1,11 @@
-import type { NextPage } from 'next'
+import type { NextPage, GetServerSideProps } from 'next'
import Head from 'next/head'
import AssetsTable from '@/components/balances/AssetsTable'
import AssetsHeader from '@/components/balances/AssetsHeader'
import useBalances from '@/hooks/useBalances'
import { useState } from 'react'
+import { checkForForbiddenRegions } from '@/utils/geo'
import PagePlaceholder from '@/components/common/PagePlaceholder'
import NoAssetsIcon from '@/public/images/balances/no-assets.svg'
@@ -41,3 +42,7 @@ const Balances: NextPage = () => {
}
export default Balances
+
+export const getServerSideProps: GetServerSideProps<{}> = async ({ req, res }) => {
+ return checkForForbiddenRegions(req, res) ?? { props: {} }
+}
diff --git a/src/pages/balances/nfts.tsx b/src/pages/balances/nfts.tsx
index 35a4d27fa7..929d298f01 100644
--- a/src/pages/balances/nfts.tsx
+++ b/src/pages/balances/nfts.tsx
@@ -1,5 +1,5 @@
import { type ReactElement, memo } from 'react'
-import type { NextPage } from 'next'
+import type { NextPage, GetServerSideProps } from 'next'
import Head from 'next/head'
import { Grid, Skeleton, Typography } from '@mui/material'
import AssetsHeader from '@/components/balances/AssetsHeader'
@@ -7,6 +7,7 @@ import NftCollections from '@/components/nfts/NftCollections'
import SafeAppCard from '@/components/safe-apps/SafeAppCard'
import { SafeAppsTag } from '@/config/constants'
import { useRemoteSafeApps } from '@/hooks/safe-apps/useRemoteSafeApps'
+import { checkForForbiddenRegions } from '@/utils/geo'
// `React.memo` requires a `displayName`
const NftApps = memo(function NftApps(): ReactElement | null {
@@ -62,3 +63,7 @@ const NFTs: NextPage = () => {
}
export default NFTs
+
+export const getServerSideProps: GetServerSideProps<{}> = async ({ req, res }) => {
+ return checkForForbiddenRegions(req, res) ?? { props: {} }
+}
diff --git a/src/pages/home.tsx b/src/pages/home.tsx
index 62590cca4c..f5c524bfc4 100644
--- a/src/pages/home.tsx
+++ b/src/pages/home.tsx
@@ -1,7 +1,8 @@
-import type { NextPage } from 'next'
+import type { NextPage, GetServerSideProps } from 'next'
import Head from 'next/head'
import Dashboard from '@/components/dashboard'
+import { checkForForbiddenRegions } from '@/utils/geo'
const Home: NextPage = () => {
return (
@@ -18,3 +19,7 @@ const Home: NextPage = () => {
}
export default Home
+
+export const getServerSideProps: GetServerSideProps<{}> = async ({ req, res }) => {
+ return checkForForbiddenRegions(req, res) ?? { props: {} }
+}
diff --git a/src/pages/new-safe/create.tsx b/src/pages/new-safe/create.tsx
index d8f53e2c55..082ee6d6a3 100644
--- a/src/pages/new-safe/create.tsx
+++ b/src/pages/new-safe/create.tsx
@@ -1,5 +1,7 @@
import Head from 'next/head'
-import type { NextPage } from 'next'
+import type { NextPage, GetServerSideProps } from 'next'
+
+import { checkForForbiddenRegions } from '@/utils/geo'
import CreateSafe from '@/components/new-safe/create'
@@ -16,3 +18,7 @@ const Open: NextPage = () => {
}
export default Open
+
+export const getServerSideProps: GetServerSideProps<{}> = async ({ req, res }) => {
+ return checkForForbiddenRegions(req, res) ?? { props: {} }
+}
diff --git a/src/pages/new-safe/load.tsx b/src/pages/new-safe/load.tsx
index 427436f417..be0636a5d8 100644
--- a/src/pages/new-safe/load.tsx
+++ b/src/pages/new-safe/load.tsx
@@ -1,7 +1,8 @@
-import type { NextPage } from 'next'
+import type { NextPage, GetServerSideProps } from 'next'
import Head from 'next/head'
import { useRouter } from 'next/router'
import LoadSafe, { loadSafeDefaultData } from '@/components/new-safe/load'
+import { checkForForbiddenRegions } from '@/utils/geo'
const Load: NextPage = () => {
const router = useRouter()
@@ -24,3 +25,7 @@ const Load: NextPage = () => {
}
export default Load
+
+export const getServerSideProps: GetServerSideProps<{}> = async ({ req, res }) => {
+ return checkForForbiddenRegions(req, res) ?? { props: {} }
+}
diff --git a/src/pages/share/safe-app.tsx b/src/pages/share/safe-app.tsx
index fcf32e74f1..9acba17ee3 100644
--- a/src/pages/share/safe-app.tsx
+++ b/src/pages/share/safe-app.tsx
@@ -1,11 +1,13 @@
import { useEffect } from 'react'
import Head from 'next/head'
+import type { GetServerSideProps } from 'next'
import { useRouter } from 'next/router'
import { Box, CircularProgress } from '@mui/material'
import { useSafeAppUrl } from '@/hooks/safe-apps/useSafeAppUrl'
import { useChainFromQueryParams } from '@/hooks/safe-apps/useChainFromQueryParams'
import { SafeAppLanding } from '@/components/safe-apps/SafeAppLandingPage'
import { AppRoutes } from '@/config/routes'
+import { checkForForbiddenRegions } from '@/utils/geo'
const ShareSafeApp = () => {
const router = useRouter()
@@ -50,3 +52,7 @@ const ShareSafeApp = () => {
}
export default ShareSafeApp
+
+export const getServerSideProps: GetServerSideProps<{}> = async ({ req, res }) => {
+ return checkForForbiddenRegions(req, res) ?? { props: {} }
+}
diff --git a/src/pages/terms.tsx b/src/pages/terms.tsx
index 369d05aad3..275a5ec738 100644
--- a/src/pages/terms.tsx
+++ b/src/pages/terms.tsx
@@ -1,6 +1,7 @@
import type { NextPage } from 'next'
import Head from 'next/head'
import SafeTerms from '@/components/terms'
+import ClabsTerms from '@/components/terms/clabs'
import { IS_OFFICIAL_HOST } from '@/config/constants'
const Imprint: NextPage = () => {
@@ -10,7 +11,7 @@ const Imprint: NextPage = () => {
{'Safe{Wallet} – Terms'}
- {IS_OFFICIAL_HOST && }
+ {IS_OFFICIAL_HOST ? : }
>
)
}
diff --git a/src/pages/transactions/history.tsx b/src/pages/transactions/history.tsx
index bfa605e209..c6cccccd3b 100644
--- a/src/pages/transactions/history.tsx
+++ b/src/pages/transactions/history.tsx
@@ -1,4 +1,4 @@
-import type { NextPage } from 'next'
+import type { NextPage, GetServerSideProps } from 'next'
import Head from 'next/head'
import useTxHistory from '@/hooks/useTxHistory'
import PaginatedTxns from '@/components/common/PaginatedTxns'
@@ -10,6 +10,7 @@ import ExpandLessIcon from '@mui/icons-material/ExpandLess'
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
import TxFilterForm from '@/components/transactions/TxFilterForm'
import { useTxFilter } from '@/utils/tx-history-filter'
+import { checkForForbiddenRegions } from '@/utils/geo'
const History: NextPage = () => {
const [filter] = useTxFilter()
@@ -45,3 +46,7 @@ const History: NextPage = () => {
}
export default History
+
+export const getServerSideProps: GetServerSideProps<{}> = async ({ req, res }) => {
+ return checkForForbiddenRegions(req, res) ?? { props: {} }
+}
diff --git a/src/pages/transactions/index.tsx b/src/pages/transactions/index.tsx
index 177c1d0e43..4454862118 100644
--- a/src/pages/transactions/index.tsx
+++ b/src/pages/transactions/index.tsx
@@ -1,3 +1,9 @@
import HistoryPage from './history'
+import type { GetServerSideProps } from 'next'
+import { checkForForbiddenRegions } from '@/utils/geo'
export default HistoryPage
+
+export const getServerSideProps: GetServerSideProps<{}> = async ({ req, res }) => {
+ return checkForForbiddenRegions(req, res) ?? { props: {} }
+}
diff --git a/src/pages/transactions/queue.tsx b/src/pages/transactions/queue.tsx
index 8c7c8d0b1f..7870c186a6 100644
--- a/src/pages/transactions/queue.tsx
+++ b/src/pages/transactions/queue.tsx
@@ -1,4 +1,4 @@
-import type { NextPage } from 'next'
+import type { NextPage, GetServerSideProps } from 'next'
import Head from 'next/head'
import useTxQueue from '@/hooks/useTxQueue'
import PaginatedTxns from '@/components/common/PaginatedTxns'
@@ -7,6 +7,7 @@ import BatchExecuteButton from '@/components/transactions/BatchExecuteButton'
import { Box } from '@mui/material'
import { BatchExecuteHoverProvider } from '@/components/transactions/BatchExecuteButton/BatchExecuteHoverProvider'
import { usePendingTxsQueue, useShowUnsignedQueue } from '@/hooks/usePendingTxs'
+import { checkForForbiddenRegions } from '@/utils/geo'
const Queue: NextPage = () => {
const showPending = useShowUnsignedQueue()
@@ -37,3 +38,7 @@ const Queue: NextPage = () => {
}
export default Queue
+
+export const getServerSideProps: GetServerSideProps<{}> = async ({ req, res }) => {
+ return checkForForbiddenRegions(req, res) ?? { props: {} }
+}
diff --git a/src/pages/transactions/tx.tsx b/src/pages/transactions/tx.tsx
index cc89616eea..3aae3b13a6 100644
--- a/src/pages/transactions/tx.tsx
+++ b/src/pages/transactions/tx.tsx
@@ -1,8 +1,10 @@
-import type { NextPage } from 'next'
+import type { NextPage, GetServerSideProps } from 'next'
+
import Head from 'next/head'
import SingleTx from '@/components/transactions/SingleTx'
import Typography from '@mui/material/Typography'
+import { checkForForbiddenRegions } from '@/utils/geo'
const SingleTransaction: NextPage = () => {
return (
@@ -23,3 +25,7 @@ const SingleTransaction: NextPage = () => {
}
export default SingleTransaction
+
+export const getServerSideProps: GetServerSideProps<{}> = async ({ req, res }) => {
+ return checkForForbiddenRegions(req, res) ?? { props: {} }
+}
diff --git a/src/pages/welcome.tsx b/src/pages/welcome.tsx
index 6a6506b4d6..81d589db2e 100644
--- a/src/pages/welcome.tsx
+++ b/src/pages/welcome.tsx
@@ -1,7 +1,10 @@
-import type { NextPage } from 'next'
+import type { NextPage, GetServerSideProps } from 'next'
+
import Head from 'next/head'
import NewSafe from '@/components/welcome/NewSafe'
+import { checkForForbiddenRegions } from '@/utils/geo'
+
const Welcome: NextPage = () => {
return (
<>
@@ -15,3 +18,7 @@ const Welcome: NextPage = () => {
}
export default Welcome
+
+export const getServerSideProps: GetServerSideProps<{}> = async ({ req, res }) => {
+ return checkForForbiddenRegions(req, res) ?? { props: {} }
+}
diff --git a/src/utils/geo.ts b/src/utils/geo.ts
new file mode 100644
index 0000000000..00cce6a9cd
--- /dev/null
+++ b/src/utils/geo.ts
@@ -0,0 +1,35 @@
+import type { IncomingMessage, OutgoingMessage } from 'http'
+
+export const HTTP_HEADER_COUNTRY = 'x-vercel-ip-country'
+export const HTTP_HEADER_REGION = 'x-vercel-ip-country-region'
+
+// korea, iran, cuba, syria
+const RESTRICTED_COUNTRIES = new Set(['KP', 'IR', 'CU', 'SY'])
+
+// https://www.iso.org/obp/ui/#iso:code:3166:UA although listed with UA prefix. the header/api recieved that and just used the number
+const crimea = '43'
+const luhansk = '09'
+const donetska = '14'
+//https://en.wikipedia.org/wiki/Russian-occupied_territories_of_Ukraine
+const RESTRICED_SUBREGION: Record> = {
+ UA: new Set([crimea, luhansk, donetska]),
+}
+
+export function isForbiddenLand(iso3166Country: string, iso3166Region: string) {
+ const iso3166CountryUppercase = iso3166Country?.toUpperCase()
+ return (
+ RESTRICTED_COUNTRIES.has(iso3166CountryUppercase) ||
+ RESTRICED_SUBREGION[iso3166CountryUppercase]?.has(iso3166Region)
+ )
+}
+
+export function checkForForbiddenRegions(req: IncomingMessage, res: OutgoingMessage) {
+ const coutry = req.headers[HTTP_HEADER_COUNTRY] as string
+ const region = req.headers[HTTP_HEADER_REGION] as string
+
+ console.info('country', coutry, 'region', region)
+
+ if (isForbiddenLand(coutry, region)) {
+ return { redirect: { destination: '/terms', permanent: false } }
+ }
+}