This commit is contained in:
Adrian Brzeziński 2024-02-24 01:46:15 +01:00
parent cfd9120169
commit f24189185f
7 changed files with 226 additions and 8 deletions

View File

@ -28,6 +28,7 @@ import SheenLoader from './shared/SheenLoader'
import { sleep } from 'utils'
import ButtonGroup from './forms/ButtonGroup'
import Decimal from 'decimal.js'
import useIpAddress from 'hooks/useIpAddress'
const set = mangoStore.getState().set
@ -84,6 +85,7 @@ function DespositForm({ token: selectedToken }: StakeFormProps) {
const { connected, publicKey } = useWallet()
const walletTokens = mangoStore((s) => s.wallet.tokens)
const [sizePercentage, setSizePercentage] = useState('')
const { ipAllowed } = useIpAddress()
const depositBank = useMemo(() => {
return group?.banksMapByName.get(selectedToken)?.[0]
@ -115,6 +117,9 @@ function DespositForm({ token: selectedToken }: StakeFormProps) {
}, [depositBank, usedTokens, totalTokens])
const handleDeposit = useCallback(async () => {
if (!ipAllowed) {
return
}
const client = mangoStore.getState().client
const group = mangoStore.getState().group
const actions = mangoStore.getState().actions
@ -272,7 +277,10 @@ function DespositForm({ token: selectedToken }: StakeFormProps) {
<Button
onClick={handleDeposit}
className="w-full"
disabled={connected && (!inputAmount || showInsufficientBalance)}
disabled={
connected &&
(!inputAmount || showInsufficientBalance || !ipAllowed)
}
size="large"
>
{submitting ? (
@ -284,8 +292,10 @@ function DespositForm({ token: selectedToken }: StakeFormProps) {
symbol: selectedToken,
})}
</div>
) : (
) : ipAllowed ? (
`Boost! ${inputAmount} ${formatTokenSymbol(selectedToken)}`
) : (
'Country not allowed'
)}
</Button>
) : (

View File

@ -11,6 +11,10 @@ import { useTranslation } from 'next-i18next'
import TermsOfUseModal from './modals/TermsOfUseModal'
import SunburstBackground from './SunburstBackground'
import Footer from './Footer'
import useIpAddress from 'hooks/useIpAddress'
import RestrictedCountryModal from './shared/RestrictedCountryModal'
export const NON_RESTRICTED_JURISDICTION_KEY = 'non-restricted-jurisdiction-0.1'
export const sideBarAnimationDuration = 300
const termsLastUpdated = 1679441610978
@ -22,6 +26,25 @@ const Layout = ({ children }: { children: ReactNode }) => {
useEffect(() => setMounted(true), [])
if (!mounted) return null
// this will only show if the ip api doesn't return the country
const RestrictedCountryCheck = () => {
const { ipCountry, loadingIpCountry } = useIpAddress()
const [confirmedCountry, setConfirmedCountry] = useLocalStorageState(
NON_RESTRICTED_JURISDICTION_KEY,
false,
)
const showModal = useMemo(() => {
return !confirmedCountry && !ipCountry && !loadingIpCountry
}, [confirmedCountry, ipCountry, loadingIpCountry])
return showModal ? (
<RestrictedCountryModal
isOpen={showModal}
onClose={() => setConfirmedCountry(true)}
/>
) : null
}
return (
<main
className={`${themeData.fonts.body.variable} ${themeData.fonts.display.variable} font-sans bg-th-primary-2`}
@ -37,6 +60,7 @@ const Layout = ({ children }: { children: ReactNode }) => {
</div>
<DeployRefreshManager />
<TermsOfUse />
<RestrictedCountryCheck />
</div>
</div>
</main>

View File

@ -38,6 +38,7 @@ import { sleep } from 'utils'
import ButtonGroup from './forms/ButtonGroup'
import Decimal from 'decimal.js'
import { toUiDecimals } from '@blockworks-foundation/mango-v4'
import useIpAddress from 'hooks/useIpAddress'
const set = mangoStore.getState().set
@ -90,6 +91,7 @@ function StakeForm({ token: selectedToken }: StakeFormProps) {
const [leverage, setLeverage] = useState(1)
const [refreshingWalletTokens, setRefreshingWalletTokens] = useState(false)
const { maxSolDeposit } = useSolBalance()
const { ipAllowed } = useIpAddress()
const { usedTokens, totalTokens } = useMangoAccountAccounts()
const { group } = useMangoGroup()
@ -181,6 +183,9 @@ function StakeForm({ token: selectedToken }: StakeFormProps) {
}, [publicKey])
const handleDeposit = useCallback(async () => {
if (!ipAllowed) {
return
}
const client = mangoStore.getState().client
const group = mangoStore.getState().group
const actions = mangoStore.getState().actions
@ -241,7 +246,7 @@ function StakeForm({ token: selectedToken }: StakeFormProps) {
type: 'error',
})
}
}, [stakeBank, publicKey, inputAmount, amountToBorrow])
}, [ipAllowed, stakeBank, publicKey, amountToBorrow, inputAmount])
const showInsufficientBalance =
tokenMax.maxAmount < Number(inputAmount) ||
@ -556,7 +561,8 @@ function StakeForm({ token: selectedToken }: StakeFormProps) {
Number(inputAmount) == 0 ||
showInsufficientBalance ||
(borrowBank && availableVaultBalance < amountToBorrow) ||
depositLimitExceeded)
depositLimitExceeded ||
!ipAllowed)
}
size="large"
>
@ -569,8 +575,10 @@ function StakeForm({ token: selectedToken }: StakeFormProps) {
symbol: selectedToken,
})}
</div>
) : (
) : ipAllowed ? (
`Boost! ${inputAmount} ${formatTokenSymbol(selectedToken)}`
) : (
'Country not allowed'
)}
</Button>
) : (

View File

@ -37,6 +37,7 @@ import ButtonGroup from './forms/ButtonGroup'
import Decimal from 'decimal.js'
import { Disclosure } from '@headlessui/react'
import { sleep } from 'utils'
import useIpAddress from 'hooks/useIpAddress'
const set = mangoStore.getState().set
@ -79,6 +80,7 @@ function UnstakeForm({ token: selectedToken }: UnstakeFormProps) {
const { usedTokens, totalTokens } = useMangoAccountAccounts()
const { group } = useMangoGroup()
const { mangoAccount } = useMangoAccount()
const { ipAllowed } = useIpAddress()
const stakeBank = useMemo(() => {
return group?.banksMapByName.get(selectedToken)?.[0]
@ -143,6 +145,9 @@ function UnstakeForm({ token: selectedToken }: UnstakeFormProps) {
}, [borrowBank, mangoAccount])
const handleWithdraw = useCallback(async () => {
if (!ipAllowed) {
return
}
const client = mangoStore.getState().client
const group = mangoStore.getState().group
const actions = mangoStore.getState().actions
@ -394,8 +399,10 @@ function UnstakeForm({ token: selectedToken }: UnstakeFormProps) {
symbol: formatTokenSymbol(selectedToken),
})}
</div>
) : (
) : ipAllowed ? (
`Unboost ${inputAmount} ${formatTokenSymbol(selectedToken)}`
) : (
'Country not allowed'
)}
</Button>
) : (

View File

@ -37,6 +37,7 @@ import ButtonGroup from './forms/ButtonGroup'
import Decimal from 'decimal.js'
import { Disclosure } from '@headlessui/react'
import { sleep } from 'utils'
import useIpAddress from 'hooks/useIpAddress'
const set = mangoStore.getState().set
@ -69,6 +70,7 @@ function WithdrawForm({ token: selectedToken }: UnstakeFormProps) {
const { t } = useTranslation(['common', 'account'])
const [inputAmount, setInputAmount] = useState('')
const [submitting, setSubmitting] = useState(false)
const { ipAllowed } = useIpAddress()
// const [selectedToken, setSelectedToken] = useState(
// token || INPUT_TOKEN_DEFAULT,
// )
@ -143,6 +145,9 @@ function WithdrawForm({ token: selectedToken }: UnstakeFormProps) {
}, [borrowBank, mangoAccount])
const handleWithdraw = useCallback(async () => {
if (!ipAllowed) {
return
}
const client = mangoStore.getState().client
const group = mangoStore.getState().group
const actions = mangoStore.getState().actions
@ -357,7 +362,10 @@ function WithdrawForm({ token: selectedToken }: UnstakeFormProps) {
<Button
onClick={handleWithdraw}
className="w-full"
disabled={connected && (!inputAmount || showInsufficientBalance)}
disabled={
connected &&
(!inputAmount || showInsufficientBalance || !ipAllowed)
}
size="large"
>
{submitting ? (
@ -369,8 +377,10 @@ function WithdrawForm({ token: selectedToken }: UnstakeFormProps) {
symbol: formatTokenSymbol(selectedToken),
})}
</div>
) : ipAllowed ? (
`Boost! ${inputAmount} ${formatTokenSymbol(selectedToken)}`
) : (
`Unboost ${inputAmount} ${formatTokenSymbol(selectedToken)}`
'Country not allowed'
)}
</Button>
) : (

View File

@ -0,0 +1,44 @@
import { ModalProps } from '../../types/modal'
import Modal from '../shared/Modal'
import { useTranslation } from 'next-i18next'
import Checkbox from '@components/forms/Checkbox'
import { useState } from 'react'
import Button from '@components/shared/Button'
import { SANCTIONED_COUNTRIES } from 'hooks/useIpAddress'
const RestrictedCountryModal = ({ isOpen, onClose }: ModalProps) => {
const { t } = useTranslation('common')
const [confirm, setConfirm] = useState(false)
return (
<Modal isOpen={isOpen} onClose={onClose} hideClose disableOutsideClose>
<h2 className="mb-2 text-center">{t('confirm-jurisdiction')}</h2>
<p className="text-center">{t('confirm-jurisdiction-desc')}</p>
<div className="thin-scroll my-4 max-h-[300px] overflow-auto rounded-lg bg-th-bkg-2 p-4">
<ul>
{SANCTIONED_COUNTRIES.map((country) => (
<li key={country[0]}>{country[1]}</li>
))}
</ul>
</div>
<Checkbox
checked={confirm}
onChange={(e) => setConfirm(e.target.checked)}
>
<p className="whitespace-normal">
{t('confirm-non-restricted-jurisdiction')}
</p>
</Checkbox>
<Button
className="mt-6 w-full"
disabled={!confirm}
onClick={onClose}
size="large"
>
{t('confirm')}
</Button>
</Modal>
)
}
export default RestrictedCountryModal

115
hooks/useIpAddress.ts Normal file
View File

@ -0,0 +1,115 @@
import { CLUSTER } from '@store/mangoStore'
import { useQuery } from '@tanstack/react-query'
import { useEffect, useState } from 'react'
export const SANCTIONED_COUNTRIES = [
['AG', 'Antigua and Barbuda'],
['DZ', 'Algeria'],
['BD', 'Bangladesh'],
['BO', 'Bolivia'],
['BY', 'Belarus'],
['BI', 'Burundi'],
['MM', 'Burma (Myanmar)'],
['CI', "Cote D'Ivoire (Ivory Coast)"],
['CU', 'Cuba'],
['CD', 'Democratic Republic of Congo'],
['EC', 'Ecuador'],
['GB', 'United Kingdom'],
['IR', 'Iran'],
['IQ', 'Iraq'],
['LR', 'Liberia'],
['LY', 'Libya'],
['ML', 'Mali'],
['MA', 'Morocco'],
['NP', 'Nepal'],
['KP', 'North Korea'],
['SO', 'Somalia'],
['SD', 'Sudan'],
['SY', 'Syria'],
['VE', 'Venezuela'],
['YE', 'Yemen'],
['ZW', 'Zimbabwe'],
['US', 'United States'],
]
const SANCTIONED_COUNTRY_CODES = SANCTIONED_COUNTRIES.map(
(country) => country[0],
)
const PERP_ALLOWED: string[] = []
const SPOT_ALLOWED: string[] = []
const SWAP_ALLOWED: string[] = []
const BORROW_LEND_ALLOWED: string[] = []
const SHOW_WARNING: string[] = ['GB']
const fetchIpGeolocation = async () => {
try {
const response = await fetch(
`https://country-code.mangomarkets.workers.dev`,
)
const parsedResponse = await response.json()
const ipCountryCode = parsedResponse ? parsedResponse?.country : ''
return ipCountryCode
} catch (e) {
console.log('failed to fetch ip country', e)
return ''
}
}
export default function useIpAddress() {
const [ipAllowed, setIpAllowed] = useState(true)
const [spotAllowed, setSpotAllowed] = useState(true)
const [perpAllowed, setPerpAllowed] = useState(true)
const [swapAllowed, setSwapAllowed] = useState(true)
const [borrowLendAllowed, setBorrowLendAllowed] = useState(true)
const [showWarning, setShowWarning] = useState(false)
const [ipCountry, setIpCountry] = useState('')
const { data: ipCountryCode, isInitialLoading } = useQuery<string, Error>(
['ip-address'],
() => fetchIpGeolocation(),
{
cacheTime: 1000 * 60 * 2,
staleTime: 1000 * 60 * 2,
retry: 3,
refetchOnWindowFocus: true,
},
)
useEffect(() => {
if (ipCountryCode) {
setIpCountry(ipCountryCode)
setIpAllowed(!SANCTIONED_COUNTRY_CODES.includes(ipCountryCode))
setSpotAllowed(SPOT_ALLOWED.includes(ipCountryCode))
setPerpAllowed(PERP_ALLOWED.includes(ipCountryCode))
setSwapAllowed(SWAP_ALLOWED.includes(ipCountryCode))
setBorrowLendAllowed(BORROW_LEND_ALLOWED.includes(ipCountryCode))
setShowWarning(SHOW_WARNING.includes(ipCountryCode))
}
}, [ipCountryCode])
if (CLUSTER === 'mainnet-beta' && !process.env.NEXT_PUBLIC_DISABLE_GEOBLOCK) {
return {
ipAllowed,
spotAllowed,
perpAllowed,
swapAllowed,
borrowLendAllowed,
showWarning,
ipCountry,
loadingIpCountry: isInitialLoading,
}
} else {
return {
ipAllowed: true,
spotAllowed: true,
perpAllowed: true,
swapAllowed: true,
borrowLendAllowed: true,
showWarning: true,
ipCountry,
loadingIpCountry: isInitialLoading,
}
}
}