Merge pull request #300 from blockworks-foundation/pan/granular-geoblock

Granular Geoblocking
This commit is contained in:
saml33 2024-01-05 09:51:07 +11:00 committed by GitHub
commit 1b69697deb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 299 additions and 45 deletions

View File

@ -15,6 +15,7 @@ import TopBar from './TopBar'
import useLocalStorageState from '../hooks/useLocalStorageState'
import {
ACCEPT_TERMS_KEY,
NON_RESTRICTED_JURISDICTION_KEY,
SECONDS,
SIDEBAR_COLLAPSE_KEY,
SLOTS_WARNING_KEY,
@ -31,6 +32,7 @@ import { useTheme } from 'next-themes'
import PromoBanner from './rewards/PromoBanner'
import { useRouter } from 'next/router'
import StatusBar from './StatusBar'
import WarningBanner from './shared/WarningBanner'
import useMangoAccountAccounts from 'hooks/useMangoAccountAccounts'
import TokenSlotsWarningModal, {
WARNING_LEVEL,
@ -38,6 +40,8 @@ import TokenSlotsWarningModal, {
import useMangoAccount from 'hooks/useMangoAccount'
import useUnownedAccount from 'hooks/useUnownedAccount'
import NewListingBanner from './NewListingBanner'
import useIpAddress from 'hooks/useIpAddress'
import RestrictedCountryModal from './modals/RestrictedCountryModal'
export const sideBarAnimationDuration = 300
const termsLastUpdated = 1679441610978
@ -167,11 +171,15 @@ const Layout = ({ children }: { children: ReactNode }) => {
<TopBar />
<NewListingBanner />
{asPath !== '/rewards' ? <PromoBanner /> : null}
{children}
<div className="pb-12 md:pb-[27px]">
{children}
<WarningBanner />
</div>
<StatusBar collapsed={isCollapsed} />
</div>
<DeployRefreshManager />
<TermsOfUse />
<RestrictedCountryCheck />
{showSlotsNearlyFullWarning ? (
<TokenSlotsWarningModal
isOpen={showSlotsNearlyFullWarning}
@ -216,6 +224,26 @@ const TermsOfUse = () => {
)
}
// 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
}
function DeployRefreshManager(): JSX.Element | null {
const { t } = useTranslation('common')
const [newBuildAvailable, setNewBuildAvailable] = useState(false)

View File

@ -89,7 +89,7 @@ const BorrowPage = () => {
}, [borrowValue, mangoAccount, group])
return (
<div className="md:pb-[27px]">
<div className="min-h-[calc(100vh-146px)]">
<div className="flex flex-col border-b border-th-bkg-3 px-4 py-4 md:px-6 lg:flex-row lg:items-center lg:justify-between">
<div className="flex flex-col md:flex-row">
<div className="pb-4 md:pb-0 md:pr-6">

View File

@ -0,0 +1,44 @@
import { SANCTIONED_COUNTRIES } from 'hooks/useIpAddress'
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'
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

View File

@ -0,0 +1,63 @@
import { IconButton } from '@components/shared/Button'
import { XMarkIcon } from '@heroicons/react/20/solid'
import Link from 'next/link'
import useIpAddress from 'hooks/useIpAddress'
const BANNER_WRAPPER_CLASSES =
'flex flex-wrap items-center justify-center bg-th-bkg-1 px-10 py-3 text-xs'
const LINK_TEXT_CLASSES =
'bg-gradient-to-b from-mango-classic-theme-active to-mango-classic-theme-down bg-clip-text font-bold text-transparent'
const TEXT_CLASSES = 'mx-1 text-center text-th-fgd-1 text-xs'
const WarningBanner = () => {
const { showWarning } = useIpAddress()
return showWarning ? (
<BannerContent
text={`Don't invest unless you're prepared to lose all the money you invest. This is a high-risk investment and you should not expect to be protected if something goes wrong.`}
linkText="Learn More"
/>
) : null
}
export default WarningBanner
const BannerContent = ({
text,
linkText,
onClickLink,
onClose,
}: {
text: string
linkText: string
onClickLink?: () => void
onClose?: () => void
}) => {
return (
<div className="relative">
<div className={BANNER_WRAPPER_CLASSES}>
<p className={TEXT_CLASSES}>{text}</p>
<Link
className={LINK_TEXT_CLASSES}
href="https://docs.mango.markets/mango-markets/risks"
onClick={onClickLink}
target="blank"
>
{linkText}
</Link>
</div>
{onClose ? (
<IconButton
className="absolute right-0 top-1/2 -translate-y-1/2 sm:right-2"
hideBg
onClick={onClose}
size="medium"
>
<XMarkIcon className="h-5 w-5 text-th-fgd-3" />
</IconButton>
) : null}
</div>
)
}

View File

@ -34,7 +34,7 @@ const StatsPage = () => {
return TABS.map((t) => [t, 0])
}, [])
return (
<div className="pb-16 md:pb-[27px]">
<>
{market ? (
<PerpStatsPage />
) : token ? (
@ -53,7 +53,7 @@ const StatsPage = () => {
<TabContent activeTab={activeTab} />
</>
)}
</div>
</>
)
}

View File

@ -36,7 +36,7 @@ const SwapFormSubmitButton = ({
const { t } = useTranslation('common')
const { mangoAccountAddress } = useMangoAccount()
const { connected } = useWallet()
const { ipAllowed, ipCountry } = useIpAddress()
const { ipAllowed, ipCountry, swapAllowed } = useIpAddress()
const { inputBank, outputBank } = mangoStore((s) => s.swap)
const { remainingBorrowsInPeriod, timeToNextPeriod } =
useRemainingBorrowsInPeriod(true)
@ -61,7 +61,7 @@ const SwapFormSubmitButton = ({
!selectedRoute ||
!!selectedRoute.error
return ipAllowed ? (
return ipAllowed || swapAllowed ? (
<>
{connected ? (
<Button

View File

@ -105,7 +105,7 @@ const TriggerSwapForm = ({
}: TriggerSwapFormProps) => {
const { t } = useTranslation(['common', 'swap', 'trade'])
const { mangoAccountAddress } = useMangoAccount()
const { ipAllowed, ipCountry } = useIpAddress()
const { ipAllowed, ipCountry, swapAllowed } = useIpAddress()
const [orderType, setOrderType] = useState(ORDER_TYPES[0])
const [submitting, setSubmitting] = useState(false)
const [swapFormSizeUi] = useLocalStorageState(SIZE_INPUT_UI_KEY, 'slider')
@ -729,7 +729,7 @@ const TriggerSwapForm = ({
<InlineNotification desc={orderDescription} type="info" />
</div>
) : null}
{ipAllowed ? (
{ipAllowed || swapAllowed ? (
<Button
onClick={onClick}
className="mb-4 mt-6 flex w-full items-center justify-center text-base"

View File

@ -78,6 +78,7 @@ import AccountSlotsFullNotification from '@components/shared/AccountSlotsFullNot
import DepositWithdrawModal from '@components/modals/DepositWithdrawModal'
import CreateAccountModal from '@components/modals/CreateAccountModal'
import TradeformSubmitButton from './TradeformSubmitButton'
import useIpAddress from 'hooks/useIpAddress'
dayjs.extend(relativeTime)
@ -122,6 +123,7 @@ const AdvancedTradeForm = () => {
const [tradeFormSizeUi] = useLocalStorageState(SIZE_INPUT_UI_KEY, 'slider')
const [savedCheckboxSettings, setSavedCheckboxSettings] =
useLocalStorageState(TRADE_CHECKBOXES_KEY, DEFAULT_CHECKBOX_SETTINGS)
const { ipAllowed, perpAllowed, spotAllowed, ipCountry } = useIpAddress()
const [soundSettings] = useLocalStorageState(
SOUND_SETTINGS_KEY,
INITIAL_SOUND_SETTINGS,
@ -405,6 +407,48 @@ const AdvancedTradeForm = () => {
}
}, [tradeForm.baseSize, marketAddress])
const isSanctioned = useMemo(() => {
return (
!ipAllowed ||
(selectedMarket instanceof PerpMarket && !perpAllowed) ||
(selectedMarket instanceof Serum3Market && !spotAllowed)
)
}, [selectedMarket, ipAllowed, perpAllowed, spotAllowed])
const hasPosition = useMemo(() => {
const group = mangoStore.getState().group
if (!mangoAccount || !selectedMarket || !group) return false
if (selectedMarket instanceof PerpMarket) {
const basePosition = mangoAccount
.getPerpPosition(selectedMarket.perpMarketIndex)
?.getBasePositionUi(selectedMarket)
return basePosition !== undefined && basePosition !== 0
} else if (selectedMarket instanceof Serum3Market) {
const baseBank = group.getFirstBankByTokenIndex(
selectedMarket.baseTokenIndex,
)
const tokenPosition = mangoAccount.getTokenBalanceUi(baseBank)
return tradeForm.side === 'sell' && tokenPosition !== 0
}
}, [selectedMarket, ipCountry, mangoAccount, tradeForm])
const isForceReduceOnly = useMemo(() => {
if (!selectedMarket) return false
return selectedMarket.reduceOnly || !!(isSanctioned && hasPosition)
}, [selectedMarket, isSanctioned, hasPosition])
useEffect(() => {
if (isSanctioned) {
set((state) => {
state.tradeForm.reduceOnly = true
})
setSavedCheckboxSettings({
...savedCheckboxSettings,
margin: false,
})
}
}, [isSanctioned])
/*
* Updates the limit price on page load
*/
@ -1083,6 +1127,7 @@ const AdvancedTradeForm = () => {
>
<Checkbox
checked={savedCheckboxSettings.margin}
disabled={isSanctioned}
onChange={handleSetMargin}
>
{t('trade:margin')}
@ -1101,10 +1146,13 @@ const AdvancedTradeForm = () => {
>
<div className="flex items-center text-xs text-th-fgd-3">
<Checkbox
checked={tradeForm.reduceOnly}
checked={
tradeForm.reduceOnly || isForceReduceOnly === true
}
onChange={(e) =>
handleReduceOnlyChange(e.target.checked)
}
disabled={isForceReduceOnly}
>
{t('trade:reduce-only')}
</Checkbox>
@ -1116,6 +1164,8 @@ const AdvancedTradeForm = () => {
<div className="mb-4 mt-6 flex px-3 md:px-4">
<TradeformSubmitButton
disabled={disabled}
isForceReduceOnly={isForceReduceOnly}
isSanctioned={isSanctioned}
placingOrder={placingOrder}
setShowCreateAccountModal={setShowCreateAccountModal}
setShowDepositModal={setShowDepositModal}
@ -1172,6 +1222,14 @@ const AdvancedTradeForm = () => {
/>
</div>
) : null}
{isSanctioned && hasPosition ? (
<div className="mb-4 px-4">
<InlineNotification
type="error"
desc={t('trade:error-sanctioned-reduce-only')}
/>
</div>
) : null}
{isTriggerOrder ? (
<div className="mb-4 px-4">
<InlineNotification

View File

@ -288,7 +288,7 @@ const TradeAdvancedPage = () => {
<MobileTradeAdvancedPage />
) : (
<TradeHotKeys>
<div className="pb-[27px]">
<div className="border-b border-th-bkg-3">
<FavoriteMarketsBar />
<ResponsiveGridLayout
layouts={layouts}

View File

@ -13,6 +13,8 @@ const TradeformSubmitButton = ({
setShowCreateAccountModal,
sideNames,
tooMuchSize,
isForceReduceOnly,
isSanctioned,
}: {
disabled: boolean
placingOrder: boolean
@ -21,6 +23,8 @@ const TradeformSubmitButton = ({
sideNames: string[]
tooMuchSize: boolean
useMargin: boolean
isForceReduceOnly: boolean
isSanctioned: boolean
}) => {
const { t } = useTranslation(['common', 'swap', 'trade'])
const side = mangoStore((s) => s.tradeForm.side)
@ -28,9 +32,9 @@ const TradeformSubmitButton = ({
const { connected } = useWallet()
const { initialLoad: mangoAccountLoading, mangoAccountAddress } =
useMangoAccount()
const { ipAllowed, ipCountry } = useIpAddress()
const { ipCountry } = useIpAddress()
return ipAllowed ? (
return !isSanctioned || isForceReduceOnly ? (
(connected && mangoAccountLoading) || mangoAccountAddress ? (
<Button
className={`flex w-full items-center justify-center ${

View File

@ -2,7 +2,7 @@ import { CLUSTER } from '@store/mangoStore'
import { useQuery } from '@tanstack/react-query'
import { useEffect, useState } from 'react'
const SANCTIONED_COUNTRIES = [
export const SANCTIONED_COUNTRIES = [
['AG', 'Antigua and Barbuda'],
['DZ', 'Algeria'],
['BD', 'Bangladesh'],
@ -14,6 +14,7 @@ const SANCTIONED_COUNTRIES = [
['CU', 'Cuba'],
['CD', 'Democratic Republic of Congo'],
['EC', 'Ecuador'],
['GB', 'United Kingdom'],
['IR', 'Iran'],
['IQ', 'Iraq'],
['LR', 'Liberia'],
@ -35,43 +36,83 @@ const SANCTIONED_COUNTRY_CODES = SANCTIONED_COUNTRIES.map(
(country) => country[0],
)
const SPOT_ALLOWED = ['GB']
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 () => {
const response = await fetch(`https://country-code.mangomarkets.workers.dev`)
const parsedResponse = await response.json()
const ipCountryCode = parsedResponse ? parsedResponse?.country : ''
try {
const response = await fetch(
`https://country-code.mangomarkets.workers.dev`,
)
const parsedResponse = await response.json()
const ipCountryCode = parsedResponse ? parsedResponse?.country : ''
return ipCountryCode
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 ipCountryCode = useQuery<string, Error>(
['ip-address'],
() => fetchIpGeolocation(),
{
cacheTime: 1000 * 60 * 2,
staleTime: 1000 * 60 * 2,
retry: 3,
refetchOnWindowFocus: true,
},
)
const {
data: ipCountryCode,
isInitialLoading,
isFetching,
isLoading,
} = useQuery<string, Error>(['ip-address'], () => fetchIpGeolocation(), {
cacheTime: 1000 * 60 * 2,
staleTime: 1000 * 60 * 2,
retry: 3,
refetchOnWindowFocus: true,
})
useEffect(() => {
if (ipCountryCode.data) {
setIpCountry(ipCountryCode.data)
setIpAllowed(!SANCTIONED_COUNTRY_CODES.includes(ipCountryCode.data))
setSpotAllowed(SPOT_ALLOWED.includes(ipCountryCode.data))
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') {
return { ipAllowed, spotAllowed, ipCountry }
const loadingIpCountry = isInitialLoading || isFetching || isLoading
if (CLUSTER === 'mainnet-beta' && !process.env.NEXT_PUBLIC_DISABLE_GEOBLOCK) {
return {
ipAllowed,
spotAllowed,
perpAllowed,
swapAllowed,
borrowLendAllowed,
showWarning,
ipCountry,
loadingIpCountry,
}
} else {
return { ipAllowed: true, spotAllowed: true, ipCountry }
return {
ipAllowed: true,
spotAllowed: true,
perpAllowed: true,
swapAllowed: true,
borrowLendAllowed: true,
showWarning: true,
ipCountry,
loadingIpCountry,
}
}
}

View File

@ -43,7 +43,7 @@ const Index: NextPage = () => {
<meta name="twitter:title" content={metaTitle} />
<meta name="twitter:description" content={metaDescription} />
</Head>
<div className="min-h-[calc(100vh-64px)] pb-32 md:pb-20 lg:pb-[27px]">
<div className="min-h-[calc(100vh-64px)]">
<AccountPage />
</div>
</>

View File

@ -36,9 +36,7 @@ const Leaderboard: NextPage = () => {
<meta name="twitter:title" content={metaTitle} />
<meta name="twitter:description" content={metaDescription} />
</Head>
<div className="pb-16 md:pb-[27px]">
<LeaderboardPage />
</div>
<LeaderboardPage />
</>
)
}

View File

@ -38,9 +38,7 @@ const Swap: NextPage = () => {
<meta name="twitter:title" content={metaTitle} />
<meta name="twitter:description" content={metaDescription} />
</Head>
<div className="pb-32 md:pb-20 lg:pb-[27px]">
<SwapPage />
</div>
<SwapPage />
</>
)
}

View File

@ -105,9 +105,7 @@ const Trade: NextPage = () => {
<meta name="twitter:title" content={metaTitle} />
<meta name="twitter:description" content={metaDescription} />
</Head>
<div className="pb-32 md:pb-0">
<TradeAdvancedPage />
</div>
<TradeAdvancedPage />
</>
)
}

View File

@ -48,6 +48,9 @@
"close-borrow": "Close {{token}} Borrow",
"collateral-value": "Collateral Value",
"confirm": "Confirm",
"confirm-jurisdiction": "Confirm Jurisdiction",
"confirm-jurisdiction-desc": "It's against our Terms of Service to use Mango if you're a resident, citizen or are located in any of the following jurisdictions.",
"confirm-non-restricted-jurisdiction": "I confirm I am not a resident, citizen or are located in a prohibited jurisdiction",
"connect": "Connect",
"connect-balances": "Connect to view your balances",
"connect-helper": "Connect to get started",

View File

@ -30,6 +30,7 @@
"error-no-route": "No route found. Try adjusting your size",
"error-no-short": "No borrow position to reduce",
"error-perp-positions-full": "You've used all of your available perp market account slots. Close unused slots to trade this market or open a new account.",
"error-sanctioned-reduce-only": "Country not allowed. You can reduce your position only.",
"error-serum-positions-full": "You've used all of your available spot market account slots. Close unused slots to trade this market or open a new account.",
"error-trigger-above": "Trigger price must be above oracle price",
"error-trigger-below": "Trigger price must be below oracle price",

View File

@ -48,6 +48,9 @@
"close-borrow": "Close {{token}} Borrow",
"collateral-value": "Collateral Value",
"confirm": "Confirm",
"confirm-jurisdiction": "Confirm Jurisdiction",
"confirm-jurisdiction-desc": "It's against our Terms of Service to use Mango if you're a resident, citizen or are located in any of the following jurisdictions.",
"confirm-non-restricted-jurisdiction": "I confirm I am not a resident, citizen or are located in a prohibited jurisdiction",
"connect": "Connect",
"connect-balances": "Connect to view your balances",
"connect-helper": "Connect to get started",

View File

@ -30,6 +30,7 @@
"error-no-route": "No route found. Try adjusting your size",
"error-no-short": "No borrow position to reduce",
"error-perp-positions-full": "You've used all of your available perp market account slots. Close unused slots to trade this market or open a new account.",
"error-sanctioned-reduce-only": "Country not allowed. You can reduce your position only.",
"error-serum-positions-full": "You've used all of your available spot market account slots. Close unused slots to trade this market or open a new account.",
"error-trigger-above": "Trigger price must be above oracle price",
"error-trigger-below": "Trigger price must be below oracle price",

View File

@ -48,6 +48,9 @@
"close-borrow": "Close {{token}} Borrow",
"collateral-value": "Collateral Value",
"confirm": "Confirm",
"confirm-jurisdiction": "Confirm Jurisdiction",
"confirm-jurisdiction-desc": "It's against our Terms of Service to use Mango if you're a resident, citizen or are located in any of the following jurisdictions.",
"confirm-non-restricted-jurisdiction": "I confirm I am not a resident, citizen or are located in a prohibited jurisdiction",
"connect": "Connect",
"connect-balances": "Connect to view your balances",
"connect-helper": "Connect to get started",

View File

@ -31,6 +31,7 @@
"error-no-short": "No borrow position to reduce",
"error-perp-positions-full": "You've used all of your available perp market account slots. Close unused slots to trade this market or open a new account.",
"error-serum-positions-full": "You've used all of your available spot market account slots. Close unused slots to trade this market or open a new account.",
"error-sanctioned-reduce-only": "Country not allowed. You can reduce your position only.",
"error-trigger-above": "Trigger price must be above oracle price",
"error-trigger-below": "Trigger price must be below oracle price",
"est-liq-price": "Est. Liq. Price",

View File

@ -48,6 +48,9 @@
"close-borrow": "结清{{token}}借贷",
"collateral-value": "质押品价值",
"confirm": "确认",
"confirm-jurisdiction": "Confirm Jurisdiction",
"confirm-jurisdiction-desc": "It's against our Terms of Service to use Mango if you're a resident, citizen or are located in any of the following jurisdictions.",
"confirm-non-restricted-jurisdiction": "I confirm I am not a resident, citizen or are located in a prohibited jurisdiction",
"connect": "连接",
"connect-balances": "连接而查看资产余额",
"connect-helper": "连接来开始",

View File

@ -32,6 +32,7 @@
"error-trigger-above": "触发价格必须高于预言机价格",
"error-trigger-below": "触发价格必须低于预言机价格",
"error-perp-positions-full": "You've used all of your available perp market account slots. Close unused slots to trade this market or open a new account.",
"error-sanctioned-reduce-only": "Country not allowed. You can reduce your position only.",
"error-serum-positions-full": "You've used all of your available spot market account slots. Close unused slots to trade this market or open a new account.",
"est-liq-price": "预计清算价格",
"est-slippage": "预计下滑",

View File

@ -48,6 +48,9 @@
"close-borrow": "結清{{token}}借貸",
"collateral-value": "質押品價值",
"confirm": "確認",
"confirm-jurisdiction": "Confirm Jurisdiction",
"confirm-jurisdiction-desc": "It's against our Terms of Service to use Mango if you're a resident, citizen or are located in any of the following jurisdictions.",
"confirm-non-restricted-jurisdiction": "I confirm I am not a resident, citizen or are located in a prohibited jurisdiction",
"connect": "連接",
"connect-balances": "連接而查看資產餘額",
"connect-helper": "連接來開始",

View File

@ -32,6 +32,7 @@
"error-trigger-above": "觸發價格必須高於預言機價格",
"error-trigger-below": "觸發價格必須低於預言機價格",
"error-perp-positions-full": "You've used all of your available perp market account slots. Close unused slots to trade this market or open a new account.",
"error-sanctioned-reduce-only": "Country not allowed. You can reduce your position only.",
"error-serum-positions-full": "You've used all of your available spot market account slots. Close unused slots to trade this market or open a new account.",
"est-liq-price": "預計清算價格",
"est-slippage": "預計下滑",

View File

@ -83,6 +83,8 @@ export const SLOTS_WARNING_KEY = 'tokenSlotsWarning-0.1'
export const NEW_LISTING_BANNER_KEY = 'new-listing-banner-0.2'
export const NON_RESTRICTED_JURISDICTION_KEY = 'non-restricted-jurisdiction-0.1'
// Unused
export const PROFILE_CATEGORIES = [
'borrower',