diff --git a/next-env.d.ts b/next-env.d.ts index 4f11a03..40c3d68 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -2,4 +2,4 @@ /// // NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. +// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. diff --git a/package.json b/package.json index abcb350..84b1129 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "homepage": "https://github.com/celo-org/celo-mondo", "dependencies": { "@celo/abis": "^11.0.0", + "@celo/compliance": "^1.0.23", "@headlessui/react": "^1.7.18", "@metamask/jazzicon": "https://github.com/jmrossy/jazzicon#7a8df28974b4e81129bfbe3cab76308b889032a6", "@metamask/post-message-stream": "6.1.2", diff --git a/src/app/app.tsx b/src/app/app.tsx index 6d9d603..2d9c323 100644 --- a/src/app/app.tsx +++ b/src/app/app.tsx @@ -6,6 +6,7 @@ import 'react-toastify/dist/ReactToastify.css'; import { ErrorBoundary } from 'src/components/errors/ErrorBoundary'; import { Footer } from 'src/components/nav/Footer'; import { Header } from 'src/components/nav/Header'; +import { LegalRestrict } from 'src/components/police'; import { WagmiContext } from 'src/config/wagmi'; import { TransactionModal } from 'src/features/transactions/TransactionModal'; import { useIsSsr } from 'src/utils/ssr'; @@ -17,7 +18,9 @@ export function App({ children }: PropsWithChildren) { - {children} + + {children} + diff --git a/src/app/police/route.ts b/src/app/police/route.ts new file mode 100644 index 0000000..982ec08 --- /dev/null +++ b/src/app/police/route.ts @@ -0,0 +1,35 @@ +import { headers } from 'next/headers'; +import { NextResponse, type NextRequest } from 'next/server'; + +export function GET(request: NextRequest) { + const headerList = headers(); + + const country = request.geo?.country || (headerList.get('x-vercel-ip-country') as string); + const region = request.geo?.region || (headerList.get('x-vercel-ip-country-region') as string); + + console.info('country', country, region); + + if (isForbiddenLand(country, region)) { + return new NextResponse(null, { status: 451 }); + } + + return new NextResponse(null, { status: 202 }); +} +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]), +}; + +function isForbiddenLand(iso3166Country: string, iso3166Region: string) { + const iso3166CountryUppercase = iso3166Country?.toUpperCase(); + return ( + RESTRICTED_COUNTRIES.has(iso3166CountryUppercase) || + RESTRICED_SUBREGION[iso3166CountryUppercase]?.has(iso3166Region) + ); +} diff --git a/src/components/police.tsx b/src/components/police.tsx new file mode 100644 index 0000000..db76a54 --- /dev/null +++ b/src/components/police.tsx @@ -0,0 +1,55 @@ +import { OFAC_SANCTIONS_LIST_URL, SANCTIONED_ADDRESSES } from '@celo/compliance'; +import { PropsWithChildren, useEffect } from 'react'; +import { readFromCache, writeToCache } from 'src/utils/localSave'; +import { useAccount, useDisconnect } from 'wagmi'; + +export function LegalRestrict(props: PropsWithChildren) { + usePolice(); + return props.children; +} + +function usePolice() { + const { address, isConnected } = useAccount(); + const { disconnect } = useDisconnect(); + + useEffect(() => { + if (isConnected && address) { + isSanctionedAddress(address).then((isSanctioned) => { + if (isSanctioned) { + disconnect(); + alert('The Address is under OFAC Sanctions'); + } + }); + fetch('/police').then((response) => { + if (response.status === 451) { + disconnect(); + alert('The Region is under Sanction'); + } + }); + } + }, [isConnected, address, disconnect]); +} + +const DAY = 24 * 60 * 60 * 1000; + +export async function isSanctionedAddress(address: string): Promise { + const cache = readFromCache(OFAC_SANCTIONS_LIST_URL); + if (cache && cache.ts + DAY > Date.now()) { + return cache.data.includes(address.toLowerCase()); + } + + const sanctionedAddresses: string[] = await fetch(OFAC_SANCTIONS_LIST_URL) + .then((x) => x.json()) + .catch(() => SANCTIONED_ADDRESSES); // fallback if github is down or something. + + if (process.env.NODE_ENV !== 'production' && process.env.TEST_SANCTIONED_ADDRESS) { + sanctionedAddresses.push(process.env.TEST_SANCTIONED_ADDRESS); + } + + writeToCache( + OFAC_SANCTIONS_LIST_URL, + sanctionedAddresses.map((x) => x.toLowerCase()), + ); + + return isSanctionedAddress(address); +} diff --git a/src/config/proposals.json b/src/config/proposals.json index fadd718..092e46c 100644 --- a/src/config/proposals.json +++ b/src/config/proposals.json @@ -1988,14 +1988,15 @@ "cgpUrlRaw": "https://raw.githubusercontent.com/celo-org/governance/main/CGPs/cgp-0132.md", "title": "Proposal for Funding Stabila - Driving Stablecoin Adoption on Celo", "author": "Kevin Tharayil (@KevinTharayil)", - "stage": 5, - "id": 173, + "stage": 6, + "id": 176, "url": "https://forum.celo.org/t/final-proposal-for-funding-stabila-driving-stablecoin-adoption-on-celo/7810", "timestamp": 1714089600000, + "timestampExecuted": 1715904000000, "votes": { - "yes": "13352572718004564022826739", - "no": "199993080818103341960279", - "abstain": "119152064836338536867928" + "yes": "13900081646743131204940936", + "no": "177123734397638981301695", + "abstain": "115470039848008913828587" } }, { @@ -2219,5 +2220,15 @@ "id": 188, "url": "https://forum.celo.org/t/launch-of-puso-the-philippines-first-community-led-stablecoin/8786/10?u=philbow61", "timestamp": 1724889600000 + }, + { + "cgp": 149, + "cgpUrl": "https://github.com/celo-org/governance/blob/main/CGPs/cgp-0149.md", + "cgpUrlRaw": "https://raw.githubusercontent.com/celo-org/governance/main/CGPs/cgp-0149.md", + "title": "Decrease `constitution` parameter for `GoldToken.increaseAllowance`", + "author": "Martin Chrzanowski (@m-chrzan)", + "stage": 0, + "url": "https://forum.celo.org/t/decrease-constitution-parameter-for-goldtoken-increaseallowance/9002", + "timestamp": 1725926400000 } ] \ No newline at end of file diff --git a/src/utils/localSave.ts b/src/utils/localSave.ts new file mode 100644 index 0000000..2ffbdca --- /dev/null +++ b/src/utils/localSave.ts @@ -0,0 +1,12 @@ +export const writeToCache = (url: string, data: string[]) => + localStorage.setItem(url, JSON.stringify({ ts: Date.now(), data })); + +export const deleteFromCache = (url: string) => localStorage.removeItem(url); + +export const readFromCache = (url: string) => { + const cached = localStorage.getItem(url); + if (cached) { + return JSON.parse(cached) as { ts: number; data: string[] }; + } + return null; +}; diff --git a/yarn.lock b/yarn.lock index a37b782..5f0cb8d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -461,6 +461,7 @@ __metadata: resolution: "@celo-mondo/celo-mondo-web@workspace:." dependencies: "@celo/abis": "npm:^11.0.0" + "@celo/compliance": "npm:^1.0.23" "@headlessui/react": "npm:^1.7.18" "@metamask/jazzicon": "https://github.com/jmrossy/jazzicon#7a8df28974b4e81129bfbe3cab76308b889032a6" "@metamask/post-message-stream": "npm:6.1.2" @@ -521,6 +522,13 @@ __metadata: languageName: node linkType: hard +"@celo/compliance@npm:^1.0.23": + version: 1.0.23 + resolution: "@celo/compliance@npm:1.0.23" + checksum: 10/3b67363e4fd266d03fdecfd3dd6ad17b0328c235d79522eea6c815864d7be5fd0aba6accb2b63dd78cc2ec2ec07ed8c85de8a282b8de60c9bd1cd72ff09c01bd + languageName: node + linkType: hard + "@coinbase/wallet-sdk@npm:4.0.4": version: 4.0.4 resolution: "@coinbase/wallet-sdk@npm:4.0.4"