merge main

This commit is contained in:
saml33 2023-05-24 21:11:58 +10:00
commit 0cf5200b2b
122 changed files with 2388 additions and 1478 deletions

View File

@ -23,6 +23,8 @@ import {
ResolutionString,
SearchSymbolResultItem,
} from '@public/charting_library'
import { PublicKey } from '@solana/web3.js'
import { Group } from '@blockworks-foundation/mango-v4'
export const SUPPORTED_RESOLUTIONS = [
'1',
@ -68,13 +70,33 @@ const configurationData = {
exchanges: [],
}
// async function getAllSymbols() {
// const data = await makeApiRequest(
// 'public/tokenlist?sort_by=v24hUSD&sort_type=desc&offset=0&limit=-1'
// )
const getTickerFromMktAddress = (
group: Group,
symbolAddress: string
): string | null => {
try {
const serumMkt = group.getSerum3MarketByExternalMarket(
new PublicKey(symbolAddress)
)
// return data.data.tokens
// }
if (serumMkt) {
return serumMkt.name
}
} catch {
console.log('Address is not a serum market')
}
const perpMarkets = Array.from(group.perpMarketsMapByMarketIndex.values())
const perpMkt = perpMarkets.find(
(perpMarket) => perpMarket.publicKey.toString() === symbolAddress
)
if (perpMkt) {
return perpMkt.name
}
return null
}
let marketType: 'spot' | 'perp'
@ -211,13 +233,22 @@ export default {
symbol: '',
}
}
const ticker = mangoStore.getState().selectedMarket.name
console.log('ticker', ticker, mangoStore.getState().group)
const mangoStoreState = mangoStore.getState()
const group = mangoStoreState.group
let ticker = mangoStoreState.selectedMarket.name
if (group && symbolAddress) {
const newTicker = getTickerFromMktAddress(group, symbolAddress)
if (newTicker) {
ticker = newTicker
}
}
const symbolInfo: SymbolInfo = {
address: symbolItem.address,
ticker: symbolItem.address,
name: symbolItem.symbol || symbolItem.address,
name: ticker || symbolItem.address,
description: ticker || symbolItem.address,
type: symbolItem.type,
session: '24x7',
@ -277,7 +308,6 @@ export default {
periodParams
)
}
if (!bars || bars.length === 0) {
// "noData" should be set if there is no data in the requested period.
onHistoryCallback([], {

View File

@ -9,7 +9,7 @@ const ColorBlur = ({
}) => {
return (
<div
className={`absolute rounded-full bg-th-button opacity-10 mix-blend-screen blur-3xl filter ${className}`}
className={`absolute rounded-full blur-3xl filter ${className}`}
style={{ height: height, width: width }}
/>
)

View File

@ -113,9 +113,7 @@ const Layout = ({ children }: { children: ReactNode }) => {
isCollapsed ? 'md:pl-[64px]' : 'md:pl-44 lg:pl-48 xl:pl-52'
}`}
>
<div className="flex h-16 items-center justify-between border-b border-th-bkg-3 bg-th-bkg-1 pl-4 md:pl-6">
<TopBar />
</div>
<TopBar />
{children}
</div>
<DeployRefreshManager />

View File

@ -1,14 +1,16 @@
import { useCallback, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import {
ArrowLeftIcon,
ArrowRightIcon,
CheckCircleIcon,
DocumentDuplicateIcon,
ExclamationTriangleIcon,
EyeIcon,
} from '@heroicons/react/20/solid'
import { useWallet } from '@solana/wallet-adapter-react'
import { useTranslation } from 'next-i18next'
import WalletIcon from './icons/WalletIcon'
import Button, { IconButton } from './shared/Button'
import Button from './shared/Button'
import ConnectedMenu from './wallet/ConnectedMenu'
import { ConnectWalletButton } from './wallet/ConnectWalletButton'
import { IS_ONBOARDED_KEY } from '../utils/constants'
@ -26,6 +28,8 @@ import { breakpoints } from 'utils/theme'
import AccountsButton from './AccountsButton'
import useUnownedAccount from 'hooks/useUnownedAccount'
import NotificationsButton from './notifications/NotificationsButton'
import Tooltip from './shared/Tooltip'
import { copyToClipboard } from 'utils'
const TopBar = () => {
const { t } = useTranslation('common')
@ -34,6 +38,7 @@ const TopBar = () => {
const [isOnboarded, setIsOnboarded] = useLocalStorageState(IS_ONBOARDED_KEY)
const [action, setAction] = useState<'deposit' | 'withdraw'>('deposit')
const [showUserSetup, setShowUserSetup] = useState(false)
const [copied, setCopied] = useState('')
const [showDepositWithdrawModal, setShowDepositWithdrawModal] =
useState(false)
const [showCreateAccountModal, setShowCreateAccountModal] = useState(false)
@ -62,16 +67,30 @@ const TopBar = () => {
}
}
const handleCopy = (text: string) => {
copyToClipboard(text)
setCopied(text)
}
useEffect(() => {
setTimeout(() => setCopied(''), 2000)
}, [copied])
return (
<>
<div
className={`flex h-16 items-center justify-between border-b border-th-bkg-3 bg-th-bkg-1 ${
query.token || query.market ? '' : 'pl-4 md:pl-6'
}`}
>
<div className="flex w-full items-center justify-between space-x-4">
<span className="mb-0 flex items-center">
{query.token || query.market ? (
<div className="mr-2 flex h-16 items-center border-r border-th-bkg-3 pr-4 md:mr-4 md:pr-6">
<IconButton onClick={() => router.back()} hideBg size="small">
<ArrowLeftIcon className="h-6 w-6" />
</IconButton>
</div>
<button
className="mr-4 flex h-16 w-16 items-center justify-center border-r border-th-bkg-3 focus-visible:bg-th-bkg-3 md:hover:bg-th-bkg-2"
onClick={() => router.back()}
>
<ArrowLeftIcon className="h-5 w-5" />
</button>
) : null}
{connected ? (
<div className="hidden md:block">
@ -89,9 +108,54 @@ const TopBar = () => {
<EyeIcon className="h-5 w-5 text-th-fgd-3" />
<span className="ml-2">
{t('unowned-helper', {
accountPk: abbreviateAddress(mangoAccount.publicKey),
accountPk: '',
})}
</span>
:
<Tooltip
content={
<>
<p>{t('account')}</p>
<button
className="mb-2 flex items-center"
onClick={() =>
handleCopy(mangoAccount.publicKey.toString())
}
>
<p className="mr-1.5 font-mono text-th-fgd-1">
{abbreviateAddress(mangoAccount.publicKey)}
</p>
{copied === mangoAccount.publicKey.toString() ? (
<CheckCircleIcon className="h-4 w-4 flex-shrink-0 text-th-success" />
) : (
<DocumentDuplicateIcon className="h-4 w-4 flex-shrink-0" />
)}
</button>
<p>{t('wallet')}</p>
<button
className="flex items-center"
onClick={() =>
handleCopy(mangoAccount.owner.toString())
}
>
<p className="mr-1.5 font-mono text-th-fgd-1">
{abbreviateAddress(mangoAccount.owner)}
</p>
{copied === mangoAccount.owner.toString() ? (
<CheckCircleIcon className="h-4 w-4 flex-shrink-0 text-th-success" />
) : (
<DocumentDuplicateIcon className="h-4 w-4 flex-shrink-0" />
)}
</button>
</>
}
>
<span className="tooltip-underline ml-1 font-bold text-th-fgd-1">
{mangoAccount.name
? mangoAccount.name
: abbreviateAddress(mangoAccount.publicKey)}
</span>
</Tooltip>
</span>
) : (
<span className="hidden items-center md:flex">
@ -155,7 +219,7 @@ const TopBar = () => {
onClose={() => setShowCreateAccountModal(false)}
/>
) : null}
</>
</div>
)
}

View File

@ -4,9 +4,19 @@ import { formatYAxis } from 'utils/formatting'
import { HourlyFundingChartData, PerformanceDataItem } from 'types'
import { ContentType } from 'recharts/types/component/Tooltip'
import DetailedAreaChart from '@components/shared/DetailedAreaChart'
import { ChartToShow } from './AccountPage'
import { IconButton } from '@components/shared/Button'
import { ArrowLeftIcon } from '@heroicons/react/20/solid'
const CHART_TABS: ChartToShow[] = [
'account-value',
'pnl',
'cumulative-interest-value',
]
const AccountChart = ({
chartToShow,
setChartToShow,
customTooltip,
data,
hideChart,
@ -14,7 +24,8 @@ const AccountChart = ({
yDecimals,
yKey,
}: {
chartToShow: string
chartToShow: ChartToShow
setChartToShow: (chart: ChartToShow) => void
customTooltip?: ContentType<number, string>
data: PerformanceDataItem[] | HourlyFundingChartData[]
hideChart: () => void
@ -28,7 +39,7 @@ const AccountChart = ({
const chartData = useMemo(() => {
if (!data.length) return []
if (chartToShow === 'cumulative-interest-value') {
data.map((d) => ({
return data.map((d) => ({
interest_value:
d.borrow_interest_cumulative_usd + d.deposit_interest_cumulative_usd,
time: d.time,
@ -38,22 +49,46 @@ const AccountChart = ({
}, [data, chartToShow])
return (
<DetailedAreaChart
customTooltip={customTooltip}
data={chartData}
daysToShow={daysToShow}
heightClass="h-[calc(100vh-200px)]"
loaderHeightClass="h-[calc(100vh-116px)]"
hideChart={hideChart}
loading={loading}
prefix="$"
setDaysToShow={setDaysToShow}
tickFormat={(x) => `$${formatYAxis(x)}`}
title={t(chartToShow)}
xKey="time"
yDecimals={yDecimals}
yKey={yKey}
/>
<>
<div className="hide-scroll mb-3 flex h-14 items-center space-x-4 overflow-x-auto border-b border-th-bkg-3">
<div className="flex h-14 w-14 flex-shrink-0 items-center justify-center border-r border-th-bkg-3">
<IconButton hideBg onClick={hideChart} size="medium">
<ArrowLeftIcon className="h-5 w-5" />
</IconButton>
</div>
<div className="flex space-x-2">
{CHART_TABS.map((tab) => (
<button
className={`whitespace-nowrap rounded-md py-1.5 px-2.5 text-sm font-medium focus-visible:bg-th-bkg-3 focus-visible:text-th-fgd-1 ${
chartToShow === tab
? 'bg-th-bkg-3 text-th-active md:hover:text-th-active'
: 'text-th-fgd-3 md:hover:text-th-fgd-2'
}`}
onClick={() => setChartToShow(tab)}
key={tab}
>
{t(tab)}
</button>
))}
</div>
</div>
<div className="px-6 pt-4">
<DetailedAreaChart
customTooltip={customTooltip}
data={chartData}
daysToShow={daysToShow}
heightClass="h-[calc(100vh-240px)]"
loaderHeightClass="h-[calc(100vh-116px)]"
loading={loading}
prefix="$"
setDaysToShow={setDaysToShow}
tickFormat={(x) => `$${formatYAxis(x)}`}
xKey="time"
yDecimals={yDecimals}
yKey={yKey}
/>
</div>
</>
)
}

View File

@ -70,6 +70,13 @@ const fetchFundingTotals = async (mangoAccountPk: string) => {
}
}
export type ChartToShow =
| ''
| 'account-value'
| 'cumulative-interest-value'
| 'pnl'
| 'hourly-funding'
const AccountPage = () => {
const { t } = useTranslation(['common', 'account'])
const { connected } = useWallet()
@ -84,13 +91,7 @@ const AccountPage = () => {
const totalInterestData = mangoStore(
(s) => s.mangoAccount.interestTotals.data
)
const [chartToShow, setChartToShow] = useState<
| 'account-value'
| 'cumulative-interest-value'
| 'pnl'
| 'hourly-funding'
| ''
>('')
const [chartToShow, setChartToShow] = useState<ChartToShow>('')
const [showExpandChart, setShowExpandChart] = useState<boolean>(false)
const [showPnlHistory, setShowPnlHistory] = useState<boolean>(false)
const { theme } = useTheme()
@ -205,7 +206,7 @@ const AccountPage = () => {
const interestTotalValue = useMemo(() => {
if (totalInterestData.length) {
return totalInterestData.reduce(
(a, c) => a + c.borrow_interest_usd * -1 + c.deposit_interest_usd,
(a, c) => a + c.borrow_interest_usd + c.deposit_interest_usd,
0
)
}
@ -413,27 +414,36 @@ const AccountPage = () => {
The lower your account health is the more likely you are to
get liquidated when prices fluctuate.
</p>
<p className="text-xs font-bold text-th-fgd-1">
Your account health is {maintHealth}%
</p>
<p className="text-xs">
<span className="font-bold text-th-fgd-1">Scenario:</span>{' '}
If the prices of all your liabilities increase by{' '}
{maintHealth}%, even for just a moment, some of your
liabilities will be liquidated.
</p>
<p className="text-xs">
<span className="font-bold text-th-fgd-1">Scenario:</span>{' '}
If the value of your total collateral decreases by{' '}
{((1 - 1 / ((maintHealth || 0) / 100 + 1)) * 100).toFixed(
2
)}
% , some of your liabilities will be liquidated.
</p>
<p className="text-xs">
These are examples. A combination of events can also lead to
liquidation.
</p>
{maintHealth < 100 ? (
<>
<p className="text-xs font-bold text-th-fgd-1">
Your account health is {maintHealth}%
</p>
<p className="text-xs">
<span className="font-bold text-th-fgd-1">
Scenario:
</span>{' '}
If the prices of all your liabilities increase by{' '}
{maintHealth}%, even for just a moment, some of your
liabilities will be liquidated.
</p>
<p className="text-xs">
<span className="font-bold text-th-fgd-1">
Scenario:
</span>{' '}
If the value of your total collateral decreases by{' '}
{(
(1 - 1 / ((maintHealth || 0) / 100 + 1)) *
100
).toFixed(2)}
% , some of your liabilities will be liquidated.
</p>
<p className="text-xs">
These are examples. A combination of events can also
lead to liquidation.
</p>
</>
) : null}
</div>
}
>
@ -672,10 +682,11 @@ const AccountPage = () => {
) : null}
</>
) : (
<div className="p-6 pb-0">
<>
{chartToShow === 'account-value' ? (
<AccountChart
chartToShow="account-value"
setChartToShow={setChartToShow}
data={performanceData.concat(latestAccountData)}
hideChart={handleHideChart}
yKey="account_equity"
@ -683,6 +694,7 @@ const AccountPage = () => {
) : chartToShow === 'pnl' ? (
<AccountChart
chartToShow="pnl"
setChartToShow={setChartToShow}
data={performanceData}
hideChart={handleHideChart}
yKey="pnl"
@ -692,12 +704,13 @@ const AccountPage = () => {
) : (
<AccountChart
chartToShow="cumulative-interest-value"
setChartToShow={setChartToShow}
data={performanceData}
hideChart={handleHideChart}
yKey="interest_value"
/>
)}
</div>
</>
)
}

View File

@ -49,7 +49,7 @@ const ActionTokenItem = ({
src={logoUri || `/icons/${name.toLowerCase()}.svg`}
/>
</div>
<p className="text-th-fgd-1">{name}</p>
<p className="text-left text-th-fgd-1">{name}</p>
</div>
{showDepositRates ? (
<div className="w-1/4 text-right font-mono">

View File

@ -362,7 +362,7 @@ const ActivityFeedTable = () => {
)}
</Disclosure>
) : (
<TrBody>
<TrBody key={`${signature}${index}`}>
<SharedTableBody
activity_type={activity_type}
amounts={amounts}

View File

@ -52,7 +52,7 @@ const ActivityFilters = () => {
const [advancedFilters, setAdvancedFilters] = useState<AdvancedFilters>(
DEFAULT_ADVANCED_FILTERS
)
const [params, setParams] = useState<string[]>(DEFAULT_PARAMS)
const [params, setParams] = useState<string[]>([])
const { t } = useTranslation(['common', 'activity'])
const loadActivityFeed = mangoStore((s) => s.activityFeed.loading)
const [showFilters, setShowFilters] = useState(false)
@ -114,7 +114,7 @@ const ActivityFilters = () => {
if (mangoAccountAddress) {
await actions.fetchActivityFeed(mangoAccountAddress)
setAdvancedFilters(DEFAULT_ADVANCED_FILTERS)
setParams(DEFAULT_PARAMS)
setParams([])
}
}, [actions, mangoAccountAddress])
@ -125,14 +125,14 @@ const ActivityFilters = () => {
return mangoAccountAddress ? (
<Disclosure>
<div className="flex items-center">
<div className="flex items-center pl-2">
{hasFilters ? (
<Tooltip
className="hidden md:block"
content={t('activity:reset-filters')}
>
<IconButton
className={`${loadActivityFeed ? 'animate-spin' : ''}`}
className={`ml-2 ${loadActivityFeed ? 'animate-spin' : ''}`}
onClick={() => handleResetFilters()}
size="small"
>
@ -164,7 +164,7 @@ const ActivityFilters = () => {
</div>
{showFilters ? (
<Disclosure.Panel
className="absolute top-[98px] left-0 z-10 w-full border-t border-th-bkg-3 bg-th-bkg-2 px-6 pb-6 shadow-md sm:top-14"
className="absolute top-[114px] left-0 z-10 w-full border-t border-th-bkg-3 bg-th-bkg-2 px-6 pb-6 shadow-md sm:top-14"
static
>
<FiltersForm

View File

@ -114,7 +114,9 @@ const CreateAccountForm = ({
{handleBack ? <div className="h-5 w-5" /> : null}
</div>
{isFirstAccount ? (
<p className="mt-1">You need a Mango Account to get started.</p>
<p className="mt-1 text-center">
You need a Mango Account to get started.
</p>
) : null}
<div className="pt-4">
<Label optional text={t('account-name')} />

View File

@ -205,7 +205,7 @@ const FundingDetails = ({ hideChart }: { hideChart: () => void }) => {
return (
<FadeInFadeOut show={true}>
<ContentBox hideBorder hidePadding>
<ContentBox className="px-6 pt-4" hideBorder hidePadding>
{loadingFunding || fetchingFunding ? (
<SheenLoader className="flex flex-1">
<div
@ -214,12 +214,12 @@ const FundingDetails = ({ hideChart }: { hideChart: () => void }) => {
</SheenLoader>
) : filteredData.length ? (
<div>
<div className="flex items-end justify-between sm:items-center">
<div className="flex flex-col sm:flex-row sm:items-center sm:space-x-4 md:space-x-6">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4 md:space-x-6">
<IconButton onClick={hideChart} size="medium">
<ArrowLeftIcon className="h-5 w-5" />
</IconButton>
<h2 className="mt-3 text-lg sm:mt-0">{t('funding')}</h2>
<h2 className="text-lg">{t('funding')}</h2>
</div>
<ChartRangeButtons
activeValue={daysToShow}

View File

@ -183,7 +183,7 @@ const HistoryTabs = () => {
setActiveTab={setActiveTab}
tabs={TABS}
/>
<div className="flex items-center justify-end space-x-2 border-b border-th-bkg-3 py-3 pr-4 sm:absolute sm:bottom-0 sm:top-0 sm:right-0 sm:border-b-0 sm:py-0 md:pr-6">
<div className="flex w-full items-center justify-end border-b border-th-bkg-3 py-3 pr-4 sm:-mt-14 sm:h-14 sm:border-b-0 sm:py-0 md:pr-6">
{!isUnownedAccount ? (
<LinkButton
className="flex items-center font-normal no-underline"

View File

@ -5,7 +5,7 @@ import {
} from '@heroicons/react/20/solid'
import { useTranslation } from 'next-i18next'
import Image from 'next/legacy/image'
import { useCallback, useEffect, useState } from 'react'
import { useCallback, useState } from 'react'
import { useViewport } from '../../hooks/useViewport'
import { formatNumericValue } from '../../utils/numbers'
import { breakpoints } from '../../utils/theme'
@ -14,7 +14,6 @@ import Tooltip from '@components/shared/Tooltip'
import useJupiterMints from 'hooks/useJupiterMints'
import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements'
import useMangoGroup from 'hooks/useMangoGroup'
import mangoStore from '@store/mangoStore'
import BorrowRepayModal from '@components/modals/BorrowRepayModal'
import BankAmountWithValue from '@components/shared/BankAmountWithValue'
import useBanksWithBalances from 'hooks/useBanksWithBalances'
@ -25,8 +24,6 @@ const AssetsBorrowsTable = () => {
const { t } = useTranslation(['common', 'token'])
const [showBorrowModal, setShowBorrowModal] = useState(false)
const [selectedToken, setSelectedToken] = useState('')
const actions = mangoStore.getState().actions
const initialStatsLoad = mangoStore((s) => s.tokenStats.initialLoad)
const { group } = useMangoGroup()
const { mangoTokens } = useJupiterMints()
const { width } = useViewport()
@ -38,12 +35,6 @@ const AssetsBorrowsTable = () => {
setShowBorrowModal(true)
}, [])
useEffect(() => {
if (group && !initialStatsLoad) {
actions.fetchTokenStats()
}
}, [group])
return (
<>
{showTableView ? (

View File

@ -0,0 +1,69 @@
import Select from '@components/forms/Select'
import { useWallet } from '@solana/wallet-adapter-react'
import { PublicKey } from '@solana/web3.js'
import GovernanceStore from '@store/governanceStore'
import useLocalStorageState from 'hooks/useLocalStorageState'
import { useEffect } from 'react'
import { abbreviateAddress } from 'utils/formatting'
import { GOVERNANCE_DELEGATE_KEY } from 'utils/governance/constants'
import { useTranslation } from 'next-i18next'
const GovernanceDelegate = () => {
const { t } = useTranslation('governance')
const { publicKey } = useWallet()
const delegates = GovernanceStore((s) => s.delegates)
const connectionContext = GovernanceStore((s) => s.connectionContext)
const vsrClient = GovernanceStore((s) => s.vsrClient)
const getCurrentVotingPower = GovernanceStore((s) => s.getCurrentVotingPower)
const voter = GovernanceStore((s) => s.voter.tokenOwnerRecord)
const [selectedDelegatePk, setSelectedDelegatePk] = useLocalStorageState(
`${publicKey?.toBase58()}${GOVERNANCE_DELEGATE_KEY}`
)
const currentDelegate = delegates
.find((x) => x.pubkey.toBase58() === selectedDelegatePk)
?.account.governingTokenOwner.toBase58()
useEffect(() => {
if (
publicKey?.toBase58() &&
connectionContext?.endpoint &&
vsrClient?.program.programId.toBase58() &&
voter
) {
getCurrentVotingPower(publicKey, vsrClient, connectionContext)
}
}, [selectedDelegatePk])
return delegates.length ? (
<div className="flex items-center">
<p className="mr-2">{t('delegate')}</p>
<Select
value={
currentDelegate
? abbreviateAddress(new PublicKey(currentDelegate))
: t('none')
}
onChange={(selected) => {
setSelectedDelegatePk(selected)
}}
className="w-full"
>
<Select.Option value={''}>
<div className="flex w-full items-center justify-between">
{t('none')}
</div>
</Select.Option>
{delegates.map((x) => (
<Select.Option key={x.pubkey.toBase58()} value={x.pubkey.toBase58()}>
<div className="flex w-full items-center justify-between">
{abbreviateAddress(x.account.governingTokenOwner)}
</div>
</Select.Option>
))}
</Select>
</div>
) : null
}
export default GovernanceDelegate

View File

@ -10,7 +10,7 @@ const GovernancePageWrapper = ({ children }: { children: ReactNode }) => {
const connectionContext = GovernanceStore((s) => s.connectionContext)
const initRealm = GovernanceStore((s) => s.initRealm)
const vsrClient = GovernanceStore((s) => s.vsrClient)
const fetchVoter = GovernanceStore((s) => s.fetchVoter)
const getCurrentVotingPower = GovernanceStore((s) => s.getCurrentVotingPower)
const resetVoter = GovernanceStore((s) => s.resetVoter)
const realm = GovernanceStore((s) => s.realm)
const connection = mangoStore((s) => s.connection)
@ -33,7 +33,7 @@ const GovernancePageWrapper = ({ children }: { children: ReactNode }) => {
connectionContext?.endpoint &&
vsrClient?.program.programId.toBase58()
) {
fetchVoter(publicKey, vsrClient, connectionContext)
getCurrentVotingPower(publicKey, vsrClient, connectionContext)
} else {
resetVoter()
}

View File

@ -89,7 +89,7 @@ const ListToken = () => {
const loadingRealm = GovernanceStore((s) => s.loadingRealm)
const loadingVoter = GovernanceStore((s) => s.loadingVoter)
const proposals = GovernanceStore((s) => s.proposals)
const fetchVoter = GovernanceStore((s) => s.fetchVoter)
const getCurrentVotingPower = GovernanceStore((s) => s.getCurrentVotingPower)
const connectionContext = GovernanceStore((s) => s.connectionContext)
const { t } = useTranslation(['governance'])
@ -364,7 +364,7 @@ const ListToken = () => {
return
}
if (!wallet?.publicKey || !vsrClient || !connectionContext) return
await fetchVoter(wallet.publicKey, vsrClient, connectionContext)
await getCurrentVotingPower(wallet.publicKey, vsrClient, connectionContext)
if (voter.voteWeight.cmp(minVoterWeight) === -1) {
notify({

View File

@ -189,7 +189,7 @@ const ProposalCard = ({
<ArrowTopRightOnSquareIcon className="mb-1 inline-block h-4 w-4 flex-shrink-0" />
</a>
</h2>
<p className="mb-2 md:mb-0">{description}</p>
<p className="mb-2 break-all md:mb-0">{description}</p>
</div>
<VoteCountdown
proposal={proposal.account}

View File

@ -7,12 +7,12 @@ import { RawMint } from '@solana/spl-token'
import { MANGO_MINT } from 'utils/constants'
import { PublicKey } from '@solana/web3.js'
import dynamic from 'next/dynamic'
import { fmtTokenAmount, tryGetMint } from 'utils/governance/tools'
import { tryGetMint } from 'utils/governance/tools'
import { BN } from '@project-serum/anchor'
import { MANGO_MINT_DECIMALS } from 'utils/governance/constants'
import { useTranslation } from 'next-i18next'
import SheenLoader from '@components/shared/SheenLoader'
import { NoSymbolIcon } from '@heroicons/react/20/solid'
import VotingPower from './VotingPower'
const ProposalCard = dynamic(() => import('./ProposalCard'))
const OnBoarding = dynamic(() => import('../OnBoarding'))
@ -23,7 +23,6 @@ const Vote = () => {
const governances = GovernanceStore((s) => s.governances)
const proposals = GovernanceStore((s) => s.proposals)
const loadingProposals = GovernanceStore((s) => s.loadingProposals)
const voter = GovernanceStore((s) => s.voter)
const loadingVoter = GovernanceStore((s) => s.loadingVoter)
const loadingRealm = GovernanceStore((s) => s.loadingRealm)
@ -62,16 +61,9 @@ const Vote = () => {
return (
<div>
<div className="mb-4 flex flex-wrap items-end justify-between">
<h1 className="mr-4">{t('active-proposals')}</h1>
<p className="whitespace-no-wrap mb-0.5 mt-2">
{t('your-votes')}{' '}
<span className="font-mono text-th-fgd-2">
{!loadingVoter
? fmtTokenAmount(voter.voteWeight, MANGO_MINT_DECIMALS)
: 0}
</span>
</p>
<div className="mb-4 flex flex-col items-center justify-between sm:flex-row">
<h1 className="mb-3 sm:mr-4 sm:mb-0">{t('active-proposals')}</h1>
<VotingPower />
</div>
{loadingProposals || loadingRealm ? (
<div className="space-y-3">

View File

@ -1,8 +1,5 @@
import Tooltip from '@components/shared/Tooltip'
import {
CheckCircleIcon,
InformationCircleIcon,
} from '@heroicons/react/20/solid'
import { CheckCircleIcon } from '@heroicons/react/20/solid'
import { Governance, ProgramAccount, Proposal } from '@solana/spl-governance'
import { RawMint } from '@solana/spl-token'
import GovernanceStore from '@store/governanceStore'
@ -55,9 +52,10 @@ const QuorumProgress = ({ governance, proposal, communityMint }: Props) => {
<div className="flex items-center">
<div className="w-full">
<div className="flex items-center">
<p className="text-fgd-2 mb-0 mr-1.5">{t('approval-q')}</p>
<Tooltip content={t('quorum-description')}>
<InformationCircleIcon className="text-fgd-2 h-5 w-5 cursor-help" />
<p className="tooltip-underline text-fgd-2 mb-0 mr-1.5">
{t('approval-q')}
</p>
</Tooltip>
</div>
{typeof yesVoteProgress !== 'undefined' && yesVoteProgress < 100 ? (
@ -70,7 +68,7 @@ const QuorumProgress = ({ governance, proposal, communityMint }: Props) => {
} required`}</p>
) : (
<div className="flex items-center">
<CheckCircleIcon className="text-green mr-1.5 h-5 w-5 flex-shrink-0" />
<CheckCircleIcon className="mr-1.5 h-4 w-4 flex-shrink-0 text-th-success" />
<p className="text-fgd-1 mb-0 font-bold">
{t('required-approval-achieved')}
</p>

View File

@ -0,0 +1,27 @@
import GovernanceDelegate from '@components/governance/GovernanceDelegate'
import GovernanceStore from '@store/governanceStore'
import { useTranslation } from 'next-i18next'
import { MANGO_MINT_DECIMALS } from 'utils/governance/constants'
import { fmtTokenAmount } from 'utils/governance/tools'
const VotingPower = () => {
const { t } = useTranslation('governance')
const voter = GovernanceStore((s) => s.voter)
const loadingVoter = GovernanceStore((s) => s.loadingVoter)
return (
<p className="whitespace-no-wrap mb-0.5 mt-2 flex items-center">
<GovernanceDelegate />
<div className="ml-4 flex h-10 items-center rounded-full bg-th-bkg-2 px-4">
<span className="mr-1">{t('your-votes')}</span>
<span className="font-mono text-th-fgd-2">
{!loadingVoter
? fmtTokenAmount(voter.voteWeight, MANGO_MINT_DECIMALS)
: 0}
</span>
</div>
</p>
)
}
export default VotingPower

View File

@ -0,0 +1,258 @@
import { useCallback, useMemo, useState } from 'react'
import { ModalProps } from '../../types/modal'
import Modal from '../shared/Modal'
import { useTranslation } from 'next-i18next'
import Label from '@components/forms/Label'
import NumberFormat, { NumberFormatValues } from 'react-number-format'
import { INPUT_CLASSES } from './TradeVolumeAlertModal'
import { Order } from '@project-serum/serum/lib/market'
import {
PerpOrder,
PerpOrderType,
Serum3OrderType,
Serum3SelfTradeBehavior,
Serum3Side,
} from '@blockworks-foundation/mango-v4'
import mangoStore from '@store/mangoStore'
import { PublicKey } from '@solana/web3.js'
import { notify } from 'utils/notifications'
import { isMangoError } from 'types'
import Button, { LinkButton } from '@components/shared/Button'
import { findSerum3MarketPkInOpenOrders } from '@components/trade/OpenOrders'
import useSelectedMarket from 'hooks/useSelectedMarket'
import {
floorToDecimal,
formatNumericValue,
getDecimalCount,
} from 'utils/numbers'
import useLocalStorageState from 'hooks/useLocalStorageState'
import { TRADE_CHECKBOXES_KEY } from 'utils/constants'
import { DEFAULT_CHECKBOX_SETTINGS } from '@components/trade/AdvancedTradeForm'
import MaxSizeButton from '@components/trade/MaxSizeButton'
import InlineNotification from '@components/shared/InlineNotification'
import MarketLogos from '@components/trade/MarketLogos'
import PerpSideBadge from '@components/trade/PerpSideBadge'
import SideBadge from '@components/shared/SideBadge'
import { ArrowRightIcon } from '@heroicons/react/20/solid'
interface ModifyModalProps {
order: Order | PerpOrder
price: string
}
type ModalCombinedProps = ModifyModalProps & ModalProps
const ModifyTvOrderModal = ({
isOpen,
onClose,
order,
price,
}: ModalCombinedProps) => {
const { t } = useTranslation(['common', 'trade'])
const [modifiedOrderPrice, setModifiedOrderPrice] = useState(price)
const [modifiedOrderSize, setModifiedOrderSize] = useState(
order.size.toString()
)
const { baseSymbol, selectedMarket, serumOrPerpMarket } = useSelectedMarket()
const [savedCheckboxSettings] = useLocalStorageState(
TRADE_CHECKBOXES_KEY,
DEFAULT_CHECKBOX_SETTINGS
)
const tickDecimals = useMemo(() => {
if (!serumOrPerpMarket) return 1
const tickSize = serumOrPerpMarket.tickSize
const tickDecimals = getDecimalCount(tickSize)
return tickDecimals
}, [serumOrPerpMarket])
const [minOrderDecimals, minOrderSize] = useMemo(() => {
if (!serumOrPerpMarket) return [1, 0.1]
const minOrderSize = serumOrPerpMarket.minOrderSize
const minOrderDecimals = getDecimalCount(minOrderSize)
return [minOrderDecimals, minOrderSize]
}, [serumOrPerpMarket])
const modifyOrder = useCallback(
async (o: PerpOrder | Order) => {
const client = mangoStore.getState().client
const group = mangoStore.getState().group
const mangoAccount = mangoStore.getState().mangoAccount.current
const actions = mangoStore.getState().actions
const baseSize = modifiedOrderSize ? Number(modifiedOrderSize) : o.size
const price = modifiedOrderPrice
? floorToDecimal(modifiedOrderPrice, tickDecimals).toNumber()
: o.price
if (!group || !mangoAccount) return
try {
let tx = ''
if (o instanceof PerpOrder) {
tx = await client.modifyPerpOrder(
group,
mangoAccount,
o.perpMarketIndex,
o.orderId,
o.side,
price,
Math.abs(baseSize),
undefined, // maxQuoteQuantity
Date.now(),
PerpOrderType.limit,
undefined,
undefined
)
} else {
const marketPk = findSerum3MarketPkInOpenOrders(o)
if (!marketPk) return
const market = group.getSerum3MarketByExternalMarket(
new PublicKey(marketPk)
)
tx = await client.modifySerum3Order(
group,
o.orderId,
mangoAccount,
market.serumMarketExternal,
o.side === 'buy' ? Serum3Side.bid : Serum3Side.ask,
price,
baseSize,
Serum3SelfTradeBehavior.decrementTake,
Serum3OrderType.limit,
Date.now(),
10
)
}
actions.fetchOpenOrders()
notify({
type: 'success',
title: 'Transaction successful',
txid: tx,
})
onClose()
} catch (e) {
console.error('Error canceling', e)
if (!isMangoError(e)) return
notify({
title: 'Unable to modify order',
description: e.message,
txid: e.txid,
type: 'error',
})
}
},
[findSerum3MarketPkInOpenOrders, modifiedOrderPrice, modifiedOrderSize]
)
return selectedMarket ? (
<Modal isOpen={isOpen} onClose={onClose}>
<h2 className="mb-2 text-center">{t('trade:edit-order')}</h2>
<div className="mb-4 flex items-center justify-center">
<MarketLogos market={selectedMarket} />
<p className="mr-2 font-bold text-th-fgd-1">{selectedMarket.name}</p>
</div>
<div className="mb-4">
<Label text={t('trade:limit-price')} />
<NumberFormat
name="price"
id="price"
inputMode="numeric"
thousandSeparator=","
allowNegative={false}
isNumericString={true}
className={INPUT_CLASSES}
value={modifiedOrderPrice}
onValueChange={(e: NumberFormatValues) =>
setModifiedOrderPrice(e.value)
}
/>
</div>
<div className="mb-6">
<MaxSizeButton
minOrderDecimals={minOrderDecimals}
tickDecimals={tickDecimals}
useMargin={savedCheckboxSettings.margin}
large
/>
<NumberFormat
name="size"
id="size"
inputMode="numeric"
thousandSeparator=","
allowNegative={false}
isNumericString={true}
className={INPUT_CLASSES}
value={modifiedOrderSize}
onValueChange={(e: NumberFormatValues) =>
setModifiedOrderSize(e.value)
}
/>
{minOrderSize &&
modifiedOrderSize &&
parseFloat(modifiedOrderSize) < minOrderSize ? (
<div className="mt-1">
<InlineNotification
type="error"
desc={t('trade:min-order-size-error', {
minSize: minOrderSize,
symbol: baseSymbol,
})}
hideBorder
hidePadding
/>
</div>
) : null}
</div>
<div className="my-6 space-y-1.5 border-y border-th-bkg-3 px-2 py-4">
<div className="flex justify-between">
<p>{t('trade:side')}</p>
{order instanceof PerpOrder ? (
<PerpSideBadge basePosition={'bid' in order.side ? 1 : -1} />
) : (
<SideBadge side={order.side} />
)}
</div>
<div className="flex justify-between">
<p>{t('trade:order-type')}</p>
<p className="text-th-fgd-2">{t('trade:limit')}</p>
</div>
<div className="flex justify-between">
<p>{t('trade:limit-price')}</p>
<div className="flex items-center space-x-2">
<p className="font-mono text-th-fgd-2">
{formatNumericValue(order.price, tickDecimals)}
</p>
<ArrowRightIcon className="h-4 w-4 text-th-fgd-3" />
<p className="font-mono text-th-fgd-2">
{formatNumericValue(modifiedOrderPrice, tickDecimals)}
</p>
</div>
</div>
<div className="flex justify-between">
<p>{t('trade:size')}</p>
<div className="flex items-center space-x-2">
{order.size !== parseFloat(modifiedOrderSize) ? (
<>
<p className="font-mono text-th-fgd-2">{order.size}</p>
<ArrowRightIcon className="h-4 w-4 text-th-fgd-3" />
</>
) : null}
<p className="font-mono text-th-fgd-2">
{formatNumericValue(modifiedOrderSize, minOrderDecimals)}
</p>
</div>
</div>
</div>
<Button
className="mb-4 w-full"
size="large"
onClick={() => modifyOrder(order)}
>
{t('confirm')}
</Button>
<LinkButton className="mx-auto" onClick={onClose}>
{t('cancel')}
</LinkButton>
</Modal>
) : null
}
export default ModifyTvOrderModal

View File

@ -19,7 +19,7 @@ const volumeAlertSound = new Howl({
export const DEFAULT_VOLUME_ALERT_SETTINGS = { seconds: 30, value: 10000 }
const INPUT_CLASSES =
export const INPUT_CLASSES =
'h-12 w-full rounded-md border border-th-input-border bg-th-input-bkg px-3 font-mono text-base text-th-fgd-1 focus:border-th-fgd-4 focus:outline-none md:hover:border-th-input-border-hover'
const TradeVolumeAlertModal = ({ isOpen, onClose }: ModalProps) => {

View File

@ -27,7 +27,7 @@ import ButtonGroup from '../forms/ButtonGroup'
import Input from '../forms/Input'
import Label from '../forms/Label'
import WalletIcon from '../icons/WalletIcon'
import ParticlesBackground from '../ParticlesBackground'
// import ParticlesBackground from '../ParticlesBackground'
// import EditNftProfilePic from '../profile/EditNftProfilePic'
// import EditProfileForm from '../profile/EditProfileForm'
import Button, { LinkButton } from '../shared/Button'
@ -209,26 +209,21 @@ const UserSetupModal = ({
return (
<Modal isOpen={isOpen} onClose={onClose} fullScreen disableOutsideClose>
<div className="grid h-screen overflow-auto bg-th-bkg-1 text-left lg:grid-cols-2">
<img
className={`absolute -bottom-20 left-1/2 mt-8 h-auto -translate-x-1/2 sm:w-[60%] md:w-[410px] lg:left-auto lg:-right-10 lg:w-[55%] lg:-translate-x-0 xl:-bottom-40 ${
showSetupStep !== 0 ? 'hidden lg:block' : 'hidden sm:block'
}`}
src="/images/swap-trade@0.75x.png"
srcSet="/images/swap-trade@0.75x.png 1098x, /images/swap-trade@1x.png 1463w,
/images/swap-trade@2x.png 2926w"
sizes="(max-width: 1600px) 1098px, (max-width: 2500px) 1463px, 2926px"
alt="next"
<ColorBlur
width="66%"
height="300px"
className="-top-20 -left-20 bg-th-button opacity-10 brightness-125"
/>
<ColorBlur
width="50%"
height="100%"
className="-bottom-20 -right-20 bg-th-bkg-1 opacity-30 mix-blend-multiply"
/>
<img
className={`absolute top-6 left-6 h-10 w-10 flex-shrink-0`}
src="/logos/logo-mark.svg"
alt="next"
/>
<ColorBlur
width="66%"
height="300px"
className="-top-20 left-0 opacity-20 brightness-125"
/>
<div className="absolute top-0 left-0 z-10 flex h-1.5 w-full flex-grow bg-th-bkg-3">
<div
style={{
@ -544,8 +539,15 @@ const UserSetupModal = ({
) : null}
</UserSetupTransition>
</div>
<div className="col-span-1 hidden h-screen lg:block">
<ParticlesBackground />
<div className="relative col-span-1 hidden h-screen lg:block">
{/* <ParticlesBackground /> */}
<img
className={`absolute left-1/2 top-1/2 h-auto w-[95%] max-w-[700px] -translate-x-1/2 -translate-y-1/2 ${
showSetupStep !== 0 ? 'hidden lg:block' : 'hidden sm:block'
}`}
src="/images/onboarding-image@1x.png"
alt="next"
/>
</div>
</div>
</Modal>

View File

@ -5,6 +5,7 @@ import { BellIcon } from '@heroicons/react/20/solid'
import { useIsAuthorized } from 'hooks/notifications/useIsAuthorized'
import { useCookies } from 'hooks/notifications/useCookies'
import { useNotificationSocket } from 'hooks/notifications/useNotificationSocket'
import { formatNumericValue } from 'utils/numbers'
const NotificationsButton = () => {
useCookies()
@ -35,12 +36,9 @@ const NotificationsButton = () => {
<BellIcon className="h-5 w-5" />
<span className="sr-only">Notifications</span>
{notificationCount !== 0 ? (
<div className="absolute top-4 right-4">
<span className="relative flex h-3.5 w-3.5 items-center justify-center">
<span className="absolute inline-flex h-3.5 w-3.5 animate-ping rounded-full bg-th-down opacity-75"></span>
<span className="relative flex h-3.5 w-3.5 items-center justify-center rounded-full bg-th-down text-xxs font-bold text-th-fgd-1">
{notificationCount}
</span>
<div className="absolute top-4 left-8">
<span className="relative flex h-3.5 w-max items-center justify-center rounded-full bg-th-down px-1 text-xxs font-bold text-white">
{formatNumericValue(notificationCount)}
</span>
</div>
) : null}

View File

@ -8,7 +8,7 @@ import {
XMarkIcon,
} from '@heroicons/react/20/solid'
import { bs58 } from '@project-serum/anchor/dist/cjs/utils/bytes'
import { useWallet } from '@solana/wallet-adapter-react'
import { WalletContextState, useWallet } from '@solana/wallet-adapter-react'
import { Payload, SIWS } from '@web3auth/sign-in-with-solana'
import { useHeaders } from 'hooks/notifications/useHeaders'
import { useIsAuthorized } from 'hooks/notifications/useIsAuthorized'
@ -20,6 +20,57 @@ import dayjs from 'dayjs'
import { useTranslation } from 'next-i18next'
import { notify } from 'utils/notifications'
export const createSolanaMessage = (
wallet: WalletContextState,
setCookie: (wallet: string, token: string) => void
) => {
const payload = new Payload()
payload.domain = window.location.host
payload.address = wallet.publicKey!.toString()
payload.uri = window.location.origin
payload.statement = 'Login to Mango Notifications Admin App'
payload.version = '1'
payload.chainId = 1
const message = new SIWS({ payload })
const messageText = message.prepareMessage()
const messageEncoded = new TextEncoder().encode(messageText)
wallet.signMessage!(messageEncoded)
.then(async (resp) => {
const tokenResp = await fetch(`${NOTIFICATION_API}auth`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
...payload,
signatureString: bs58.encode(resp),
}),
})
const body = await tokenResp.json()
const token = body.token
const error = body.error
if (error) {
notify({
type: 'error',
title: 'Error',
description: error,
})
return
}
setCookie(payload.address, token)
})
.catch((e) => {
notify({
type: 'error',
title: 'Error',
description: e.message ? e.message : `${e}`,
})
})
}
const NotificationsDrawer = ({
isOpen,
onClose,
@ -110,54 +161,6 @@ const NotificationsDrawer = ({
[NOTIFICATION_API, headers]
)
const createSolanaMessage = useCallback(() => {
const payload = new Payload()
payload.domain = window.location.host
payload.address = wallet.publicKey!.toString()
payload.uri = window.location.origin
payload.statement = 'Login to Mango Notifications Admin App'
payload.version = '1'
payload.chainId = 1
const message = new SIWS({ payload })
const messageText = message.prepareMessage()
const messageEncoded = new TextEncoder().encode(messageText)
wallet.signMessage!(messageEncoded)
.then(async (resp) => {
const tokenResp = await fetch(`${NOTIFICATION_API}auth`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
...payload,
signatureString: bs58.encode(resp),
}),
})
const body = await tokenResp.json()
const token = body.token
const error = body.error
if (error) {
notify({
type: 'error',
title: 'Error',
description: error,
})
return
}
setCookie(payload.address, token)
})
.catch((e) => {
notify({
type: 'error',
title: 'Error',
description: e.message ? e.message : `${e}`,
})
})
}, [window.location, wallet, NOTIFICATION_API])
// Mark all notifications as seen when the inbox is opened
useEffect(() => {
if (isOpen && unseenNotifications?.length) {
@ -266,7 +269,10 @@ const NotificationsDrawer = ({
<InboxIcon className="mb-2 h-7 w-7 text-th-fgd-2" />
<h3 className="mb-1 text-base">{t('unauth-title')}</h3>
<p>{t('unauth-desc')}</p>
<Button className="mt-6" onClick={createSolanaMessage}>
<Button
className="mt-6"
onClick={() => createSolanaMessage(wallet, setCookie)}
>
{t('sign-message')}
</Button>
</div>

View File

@ -1,20 +1,22 @@
import Switch from '@components/forms/Switch'
import { createSolanaMessage } from '@components/notifications/NotificationsDrawer'
import Button from '@components/shared/Button'
import ConnectEmptyState from '@components/shared/ConnectEmptyState'
import { BellIcon } from '@heroicons/react/20/solid'
import { useWallet } from '@solana/wallet-adapter-react'
import { useHeaders } from 'hooks/notifications/useHeaders'
import { useIsAuthorized } from 'hooks/notifications/useIsAuthorized'
import { useNotificationSettings } from 'hooks/notifications/useNotificationSettings'
import { useTranslation } from 'next-i18next'
import { NOTIFICATION_API } from 'utils/constants'
export const INITIAL_SOUND_SETTINGS = {
'recent-trades': false,
'swap-success': false,
'transaction-success': false,
'transaction-fail': false,
}
import NotificationCookieStore from '@store/notificationCookieStore'
const NotificationSettings = () => {
const { t } = useTranslation(['common', 'settings'])
const { t } = useTranslation(['common', 'notifications', 'settings'])
const { data, refetch } = useNotificationSettings()
const { connected } = useWallet()
const wallet = useWallet()
const setCookie = NotificationCookieStore((s) => s.setCookie)
const headers = useHeaders()
const isAuth = useIsAuthorized()
@ -53,12 +55,20 @@ const NotificationSettings = () => {
/>
</div>
) : (
<div className="relative top-1/2 flex -translate-y-1/2 flex-col justify-center px-6 pb-20">
<div className="flex flex-col items-center justify-center text-center">
<h3 className="mb-1 text-base">
{t('settings:sign-to-notifications')}
</h3>
</div>
<div className="mb-8 rounded-lg border border-th-bkg-3 p-6">
{connected ? (
<div className="flex flex-col items-center">
<BellIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
<p className="mb-4">{t('notifications:unauth-desc')}</p>
<Button onClick={() => createSolanaMessage(wallet, setCookie)}>
<div className="flex items-center">
{t('notifications:sign-message')}
</div>
</Button>
</div>
) : (
<ConnectEmptyState text={t('settings:connect-notifications')} />
)}
</div>
)}
</>

View File

@ -1,12 +1,18 @@
import ButtonGroup from '@components/forms/ButtonGroup'
import Input from '@components/forms/Input'
import Button from '@components/shared/Button'
import Switch from '@components/forms/Switch'
import { useWallet } from '@solana/wallet-adapter-react'
import mangoStore from '@store/mangoStore'
import useLocalStorageState from 'hooks/useLocalStorageState'
import { useTranslation } from 'next-i18next'
import { ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react'
import { PRIORITY_FEE_KEY, RPC_PROVIDER_KEY } from 'utils/constants'
import {
PRIORITY_FEE_KEY,
RPC_PROVIDER_KEY,
USE_ORDERBOOK_FEED_KEY,
} from 'utils/constants'
import Tooltip from '@components/shared/Tooltip'
const RPC_URLS = [
{
@ -42,6 +48,8 @@ const RpcSettings = () => {
PRIORITY_FEE_KEY,
DEFAULT_PRIORITY_FEE.value
)
const [storedUseOrderbookFeed, setStoredUseOrderbookFeed] =
useLocalStorageState(USE_ORDERBOOK_FEED_KEY, true)
const rpcEndpoint = useMemo(() => {
return (
@ -167,6 +175,22 @@ const RpcSettings = () => {
) : null} */}
</div>
</div>
<div className="flex items-center justify-between border-t border-th-bkg-3 p-4">
<Tooltip
content={t('settings:tooltip-orderbook-bandwidth-saving')}
maxWidth="25rem"
placement="top-start"
delay={100}
>
<p className="tooltip-underline">
{t('settings:orderbook-bandwidth-saving')}
</p>
</Tooltip>
<Switch
checked={storedUseOrderbookFeed}
onChange={() => setStoredUseOrderbookFeed(!storedUseOrderbookFeed)}
/>
</div>
</>
)
}

View File

@ -14,15 +14,15 @@ const SettingsPage = () => {
<div className="col-span-12 border-b border-th-bkg-3 pt-8 lg:col-span-10 lg:col-start-2 xl:col-span-8 xl:col-start-3">
<DisplaySettings />
</div>
<div className="col-span-12 border-b border-th-bkg-3 pt-8 lg:col-span-10 lg:col-start-2 xl:col-span-8 xl:col-start-3">
<NotificationSettings />
</div>
<div className="col-span-12 border-b border-th-bkg-3 pt-8 lg:col-span-10 lg:col-start-2 xl:col-span-8 xl:col-start-3">
<AnimationSettings />
</div>
<div className="col-span-12 border-b border-th-bkg-3 pt-8 lg:col-span-10 lg:col-start-2 xl:col-span-8 xl:col-start-3">
<SoundSettings />
</div>
<div className="col-span-12 border-b border-th-bkg-3 pt-8 lg:col-span-10 lg:col-start-2 xl:col-span-8 xl:col-start-3">
<NotificationSettings />
</div>
<div className="col-span-12 pt-8 lg:col-span-10 lg:col-start-2 xl:col-span-8 xl:col-start-3">
<PreferredExplorerSettings />
</div>

View File

@ -153,6 +153,16 @@ const DetailedAreaChart: FunctionComponent<DetailedAreaChartProps> = ({
</SheenLoader>
) : filteredData.length ? (
<div className="relative">
{setDaysToShow ? (
<div className="mb-4 sm:absolute sm:-top-1 sm:right-0 sm:mb-0 sm:-mb-2 sm:flex sm:justify-end">
<ChartRangeButtons
activeValue={daysToShow}
names={['24H', '7D', '30D']}
values={['1', '7', '30']}
onChange={(v) => setDaysToShow(v)}
/>
</div>
) : null}
<div className="flex items-start justify-between">
<div className="flex flex-col md:flex-row md:items-start md:space-x-6">
{hideChart ? (
@ -165,18 +175,20 @@ const DetailedAreaChart: FunctionComponent<DetailedAreaChartProps> = ({
</IconButton>
) : null}
<div>
{tooltipContent ? (
<Tooltip content={tooltipContent}>
<p
className={`${titleClasses}
{title ? (
tooltipContent ? (
<Tooltip content={tooltipContent}>
<p
className={`${titleClasses}
tooltip-underline`}
>
{title}
</p>
</Tooltip>
) : (
<p className={titleClasses}>{title}</p>
)}
>
{title}
</p>
</Tooltip>
) : (
<p className={titleClasses}>{title}</p>
)
) : null}
{mouseData ? (
<div>
<div
@ -294,16 +306,6 @@ const DetailedAreaChart: FunctionComponent<DetailedAreaChartProps> = ({
<div
className={`-mt-1 ${heightClass ? heightClass : 'h-96'} w-auto`}
>
{setDaysToShow ? (
<div className="absolute -top-1 right-0 -mb-2 flex justify-end">
<ChartRangeButtons
activeValue={daysToShow}
names={['24H', '7D', '30D']}
values={['1', '7', '30']}
onChange={(v) => setDaysToShow(v)}
/>
</div>
) : null}
<div className="-mx-6 mt-6 h-full">
<ResponsiveContainer width="100%" height="100%">
<AreaChart

View File

@ -1,5 +1,5 @@
import dayjs from 'dayjs'
import { ReactNode } from 'react'
import { ReactNode, forwardRef } from 'react'
export const Table = ({
children,
@ -21,13 +21,17 @@ export const Th = ({
children,
className,
id,
xBorder = false,
}: {
children?: ReactNode
className?: string
id?: string
xBorder?: boolean
}) => (
<th
className={`whitespace-nowrap px-2 py-3 text-xs font-normal text-th-fgd-3 first:pl-6 last:pr-6 xl:px-4 ${className}`}
className={`whitespace-nowrap px-2 py-3 text-xs font-normal text-th-fgd-3 first:pl-6 last:pr-6 xl:px-4 ${
xBorder ? 'border-x border-th-bkg-3' : ''
} ${className}`}
id={id}
scope="col"
>
@ -35,28 +39,43 @@ export const Th = ({
</th>
)
export const TrBody = ({
children,
className,
onClick,
}: {
interface TrBodyProps {
children: ReactNode
className?: string
onClick?: () => void
}) => (
<tr className={`border-y border-th-bkg-3 ${className}`} onClick={onClick}>
{children}
</tr>
}
export const TrBody = forwardRef<HTMLTableRowElement, TrBodyProps>(
(props, ref) => {
const { children, className, onClick } = props
return (
<tr
className={`border-y border-th-bkg-3 ${className}`}
onClick={onClick}
ref={ref}
>
{children}
</tr>
)
}
)
TrBody.displayName = 'TrBody'
export const Td = ({
children,
className,
xBorder = false,
}: {
children: ReactNode
className?: string
xBorder?: boolean
}) => (
<td className={`px-2 py-3 first:pl-6 last:pr-6 xl:px-4 ${className}`}>
<td
className={`px-2 py-3 first:pl-6 last:pr-6 xl:px-4 ${
xBorder ? 'border-x border-th-bkg-3' : ''
} ${className}`}
>
{children}
</td>
)

View File

@ -1,5 +1,6 @@
import { ChevronDownIcon } from '@heroicons/react/20/solid'
import Image from 'next/image'
import { formatTokenSymbol } from 'utils/tokens'
const TokenListButton = ({
logoUri,
@ -13,7 +14,7 @@ const TokenListButton = ({
return (
<button
onClick={() => setShowList(true)}
className="flex h-full w-full items-center rounded-lg rounded-r-none border border-th-input-border bg-th-input-bkg py-2 px-3 text-th-fgd-2 hover:cursor-pointer hover:bg-th-bkg-2 hover:text-th-fgd-1 focus-visible:border-th-fgd-4"
className="flex h-full w-full items-center rounded-lg rounded-r-none border border-r-0 border-th-input-border bg-th-input-bkg py-2 px-3 text-th-fgd-2 focus-visible:bg-th-bkg-2 md:hover:cursor-pointer md:hover:bg-th-bkg-2 md:hover:text-th-fgd-1"
>
<div className="mr-2.5 flex min-w-[24px] items-center">
<Image
@ -24,7 +25,7 @@ const TokenListButton = ({
/>
</div>
<div className="flex w-full items-center justify-between">
<div className="text-xl font-bold">{token}</div>
<div className="text-xl font-bold">{formatTokenSymbol(token)}</div>
<ChevronDownIcon className="h-6 w-6" />
</div>
</button>

View File

@ -35,9 +35,9 @@ const PerpMarketDetailsTable = () => {
<Th className="text-right">{t('trade:tick-size')}</Th>
<Th className="text-right">{t('trade:init-leverage')}</Th>
<Th className="text-right">{t('trade:max-leverage')}</Th>
<Th className="text-right">{t('fees')}</Th>
<Th className="text-right">{t('trade:maker-fee')}</Th>
<Th className="text-right">{t('trade:taker-fee')}</Th>
<Th className="text-right">{t('trade:funding-limits')}</Th>
<Th className="text-right">{t('trade:oracle')}</Th>
<Th className="text-right">
<Tooltip
content={
@ -59,6 +59,7 @@ const PerpMarketDetailsTable = () => {
</span>
</Tooltip>
</Th>
<Th className="text-right">{t('trade:oracle')}</Th>
<Th />
</TrHead>
</thead>
@ -110,15 +111,12 @@ const PerpMarketDetailsTable = () => {
</Td>
<Td>
<p className="text-right">
{(100 * makerFee.toNumber()).toFixed(2)}%{' '}
<span className="font-body text-th-fgd-3">
{t('trade:maker')}
</span>
<span className="mx-1">|</span>
{(100 * takerFee.toNumber()).toFixed(2)}%{' '}
<span className="font-body text-th-fgd-3">
{t('trade:taker')}
</span>
{(100 * makerFee.toNumber()).toFixed(2)}%
</p>
</Td>
<Td>
<p className="text-right">
{(100 * takerFee.toNumber()).toFixed(2)}%
</p>
</Td>
<Td>
@ -128,6 +126,11 @@ const PerpMarketDetailsTable = () => {
{(100 * maxFunding.toNumber()).toFixed(2)}%
</p>
</Td>
<Td>
<p className="text-right">
{groupInsuranceFund ? t('yes') : t('no')}
</p>
</Td>
<Td>
{oracleLinkPath ? (
<a
@ -145,11 +148,6 @@ const PerpMarketDetailsTable = () => {
<p className="text-right font-body">{oracleProvider}</p>
)}
</Td>
<Td>
<p className="text-right">
{groupInsuranceFund ? t('yes') : t('no')}
</p>
</Td>
<Td>
<div className="flex justify-end">
<ChevronRightIcon className="h-5 w-5 text-th-fgd-3" />

View File

@ -20,7 +20,6 @@ import Tooltip from '@components/shared/Tooltip'
import { PerpStatsItem } from 'types'
import useMangoGroup from 'hooks/useMangoGroup'
import { NextRouter, useRouter } from 'next/router'
import { InformationCircleIcon } from '@heroicons/react/24/outline'
import SimpleAreaChart from '@components/shared/SimpleAreaChart'
export const getOneDayPerpStats = (
@ -189,13 +188,15 @@ const PerpMarketsOverviewTable = () => {
</Td>
<Td>
<div className="flex items-center justify-end">
<p className="">{fundingRate}</p>
<Tooltip
content={
<>
{fundingRateApr ? (
<div className="">
The 1hr rate as an APR is {fundingRateApr}.
The 1hr rate as an APR is{' '}
<span className="font-mono text-th-fgd-2">
{fundingRateApr}
</span>
</div>
) : null}
<div className="mt-2">
@ -210,7 +211,7 @@ const PerpMarketsOverviewTable = () => {
</>
}
>
<InformationCircleIcon className="ml-2 h-4 w-4" />
<p className="tooltip-underline">{fundingRate}</p>
</Tooltip>
</div>
</Td>

View File

@ -47,9 +47,28 @@ const TokenDetailsTable = () => {
</Tooltip>
</div>
</Th>
<Th className="text-right">{t('borrow-fee')}</Th>
<Th className="text-right">{t('activity:liquidation-fee')}</Th>
<Th className="text-right">{t('trade:oracle')}</Th>
<Th>
<div className="flex justify-end text-right">
<Tooltip content={t('tooltip-borrow-fee')}>
<span className="tooltip-underline">
{t('borrow-fee')}
</span>
</Tooltip>
</div>
</Th>
<Th>
<div className="flex justify-end text-right">
<Tooltip
content={t('token:tooltip-liquidation-fee', {
symbol: t('tokens').toLowerCase(),
})}
>
<span className="tooltip-underline">
{t('activity:liquidation-fee')}
</span>
</Tooltip>
</div>
</Th>
<Th className="text-right">
<Tooltip
content={
@ -71,6 +90,7 @@ const TokenDetailsTable = () => {
</span>
</Tooltip>
</Th>
<Th className="text-right">{t('trade:oracle')}</Th>
<Th />
</TrHead>
</thead>
@ -139,6 +159,11 @@ const TokenDetailsTable = () => {
{(bank.liquidationFee.toNumber() * 100).toFixed(2)}%
</p>
</Td>
<Td>
<p className="text-right">
{mintInfo?.groupInsuranceFund ? t('yes') : t('no')}
</p>
</Td>
<Td>
{oracleLinkPath ? (
<a
@ -156,11 +181,6 @@ const TokenDetailsTable = () => {
<p className="text-right font-body">{oracleProvider}</p>
)}
</Td>
<Td>
<p className="text-right">
{mintInfo?.groupInsuranceFund ? t('yes') : t('no')}
</p>
</Td>
<Td>
<div className="flex justify-end">
<ChevronRightIcon className="h-5 w-5 text-th-fgd-3" />
@ -224,7 +244,10 @@ const TokenDetailsTable = () => {
<Disclosure.Panel>
<div className="mx-4 grid grid-cols-2 gap-4 border-t border-th-bkg-3 pt-4 pb-4">
<div className="col-span-1">
<Tooltip content={t('asset-liability-weight-desc')}>
<Tooltip
content={t('asset-liability-weight-desc')}
placement="top-start"
>
<p className="tooltip-underline text-xs text-th-fgd-3">
{t('asset-liability-weight')}
</p>
@ -240,7 +263,14 @@ const TokenDetailsTable = () => {
</div>
</div>
<div className="col-span-1">
<p className="text-xs">{t('borrow-fee')}</p>
<Tooltip
content={t('tooltip-borrow-fee')}
placement="top-start"
>
<p className="tooltip-underline text-xs">
{t('borrow-fee')}
</p>
</Tooltip>
<p className="font-mono text-th-fgd-1">
{(
100 * bank.loanOriginationFeeRate.toNumber()
@ -249,9 +279,16 @@ const TokenDetailsTable = () => {
</p>
</div>
<div className="col-span-1">
<p className="text-xs">
{t('activity:liquidation-fee')}
</p>
<Tooltip
content={t('token:tooltip-liquidation-fee', {
symbol: bank.name,
})}
placement="top-start"
>
<p className="tooltip-underline text-xs">
{t('activity:liquidation-fee')}
</p>
</Tooltip>
<p className="font-mono text-th-fgd-1">
{(bank.liquidationFee.toNumber() * 100).toFixed(
2
@ -296,6 +333,7 @@ const TokenDetailsTable = () => {
</a>
</div>
}
placement="top-start"
>
<span className="tooltip-underline text-xs">
{t('trade:insured', { token: '' })}

View File

@ -4,22 +4,14 @@ import { useEffect, useMemo, useState } from 'react'
import dayjs from 'dayjs'
import { formatYAxis } from 'utils/formatting'
import useBanksWithBalances from 'hooks/useBanksWithBalances'
import { TokenStatsItem } from 'types'
import useMangoGroup from 'hooks/useMangoGroup'
import { toUiDecimals } from '@blockworks-foundation/mango-v4'
import DetailedAreaChart from '@components/shared/DetailedAreaChart'
interface TotalValueItem {
date: string
borrowValue: number
depositValue: number
feesCollected: number
}
const TokenStatsCharts = () => {
const { t } = useTranslation(['common', 'token', 'trade'])
const { group } = useMangoGroup()
const tokenStats = mangoStore((s) => s.tokenStats.data)
const mangoStats = mangoStore((s) => s.tokenStats.mangoStats)
const initialStatsLoad = mangoStore((s) => s.tokenStats.initialLoad)
const loadingStats = mangoStore((s) => s.tokenStats.loading)
const [borrowDaysToShow, setBorrowDaysToShow] = useState('30')
@ -33,38 +25,6 @@ const TokenStatsCharts = () => {
}
}, [group, initialStatsLoad])
const tokenStatsValues = useMemo(() => {
if (!tokenStats || !banks.length) return []
const values: TotalValueItem[] = tokenStats.reduce(
(a: TotalValueItem[], c: TokenStatsItem) => {
const bank = banks.find(
(b) => b.bank.tokenIndex === c.token_index
)?.bank
const hasDate = a.find((d: TotalValueItem) => d.date === c.date_hour)
if (!hasDate) {
a.push({
date: c.date_hour,
depositValue: Math.floor(c.total_deposits * c.price),
borrowValue: Math.floor(c.total_borrows * c.price),
feesCollected: c.collected_fees * bank!.uiPrice,
})
} else {
hasDate.depositValue =
hasDate.depositValue + Math.floor(c.total_deposits * c.price)
hasDate.borrowValue =
hasDate.borrowValue + Math.floor(c.total_borrows * c.price)
hasDate.feesCollected =
hasDate.feesCollected + c.collected_fees * bank!.uiPrice
}
return a.sort(
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()
)
},
[]
)
return values
}, [banks, tokenStats])
const [
currentTotalDepositValue,
currentTotalBorrowValue,
@ -86,11 +46,11 @@ const TokenStatsCharts = () => {
return [0, 0, 0]
}, [banks])
return tokenStatsValues.length ? (
return (
<>
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1 md:border-r">
<DetailedAreaChart
data={tokenStatsValues.concat([
data={mangoStats.concat([
{
date: dayjs().toISOString(),
depositValue: Math.floor(currentTotalDepositValue),
@ -112,7 +72,7 @@ const TokenStatsCharts = () => {
</div>
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1 md:pl-6">
<DetailedAreaChart
data={tokenStatsValues.concat([
data={mangoStats.concat([
{
date: dayjs().toISOString(),
borrowValue: Math.floor(currentTotalBorrowValue),
@ -134,7 +94,7 @@ const TokenStatsCharts = () => {
</div>
<div className="col-span-2 border-b border-th-bkg-3 py-4 px-6 md:col-span-1 md:border-r md:pl-6">
<DetailedAreaChart
data={tokenStatsValues.concat([
data={mangoStats.concat([
{
date: dayjs().toISOString(),
borrowValue: Math.floor(currentTotalBorrowValue),
@ -156,7 +116,7 @@ const TokenStatsCharts = () => {
/>
</div>
</>
) : null
)
}
export default TokenStatsCharts

View File

@ -28,7 +28,8 @@ const MaxSwapAmount = ({
return (
<div className="flex flex-wrap justify-end pl-6 text-xs">
{tokenMax.lt(amountWithBorrow) ? (
{tokenMax.lt(amountWithBorrow) ||
(tokenMax.eq(amountWithBorrow) && !useMargin) ? (
<MaxAmountButton
className="mb-0.5"
decimals={decimals}

View File

@ -457,7 +457,9 @@ const SwapForm = () => {
{group && inputBank ? (
<TokenVaultWarnings bank={inputBank} type="swap" />
) : null}
{inputBank && inputBank.reduceOnly ? (
{inputBank &&
inputBank.areBorrowsReduceOnly() &&
inputBank.areDepositsReduceOnly() ? (
<div className="pb-4">
<InlineNotification
type="warning"
@ -467,7 +469,9 @@ const SwapForm = () => {
/>
</div>
) : null}
{outputBank && outputBank.reduceOnly ? (
{outputBank &&
outputBank.areBorrowsReduceOnly() &&
outputBank.areDepositsReduceOnly() ? (
<div className="pb-4">
<InlineNotification
type="warning"
@ -538,7 +542,8 @@ const SwapFormSubmitButton = ({
(!amountIn.toNumber() ||
showInsufficientBalance ||
!amountOut ||
!selectedRoute)
!selectedRoute ||
isDelegatedAccount)
const onClick = connected ? () => setShowConfirm(true) : handleConnect

View File

@ -59,6 +59,13 @@ const TokenItem = ({
return group.getFirstBankByMint(new PublicKey(address))
}, [address])
const isReduceOnly = useMemo(() => {
if (!bank) return false
const borrowsReduceOnly = bank.areBorrowsReduceOnly()
const depositsReduceOnly = bank.areDepositsReduceOnly()
return borrowsReduceOnly && depositsReduceOnly
}, [bank])
return (
<div>
<button
@ -74,7 +81,7 @@ const TokenItem = ({
<div className="ml-2.5">
<p className="text-left text-th-fgd-2">
{bank?.name ? formatTokenSymbol(bank.name) : symbol || 'unknown'}
{bank?.reduceOnly ? (
{isReduceOnly ? (
<span className="ml-1.5 text-xxs text-th-warning">
{t('reduce-only')}
</span>

View File

@ -70,7 +70,13 @@ const SwapHistoryTable = () => {
<Th className="text-left">{t('swap:received')}</Th>
<Th className="text-right">{t('value')}</Th>
<Th className="text-right">{t('borrow')}</Th>
<Th className="text-right">{t('borrow-fee')}</Th>
<Th>
<div className="flex justify-end text-right">
<Tooltip content={t('tooltip-borrow-fee')}>
<span className="tooltip-underline">{t('borrow-fee')}</span>
</Tooltip>
</div>
</Th>
<Th />
</TrHead>
</thead>

View File

@ -367,7 +367,7 @@ const SwapReviewRouteInfo = ({
<ArrowLeftIcon className="h-5 w-5" />
</IconButton>
<div className="flex justify-center bg-gradient-to-t from-th-bkg-1 to-th-bkg-2 p-6 pb-0">
<div className="mb-4 flex w-full flex-col items-center border-b border-th-bkg-3 pb-4">
<div className="mb-3 flex w-full flex-col items-center border-b border-th-bkg-3 pb-4">
<div className="relative mb-2 w-[72px]">
<Image alt="" width="40" height="40" src={inputTokenIconUri} />
<div className="absolute right-0 top-0">
@ -385,7 +385,7 @@ const SwapReviewRouteInfo = ({
<FormatNumericValue value={amountIn} />
</span>{' '}
{inputTokenInfo?.symbol}
<ArrowRightIcon className="mx-2 h-5 w-5 text-th-fgd-4" />
<ArrowRightIcon className="mx-2 h-5 w-5 text-th-fgd-2" />
<span className="mr-1 font-mono text-th-fgd-1">
<FormatNumericValue value={amountOut} />
</span>{' '}
@ -511,9 +511,7 @@ const SwapReviewRouteInfo = ({
</>
}
>
<span className="tooltip-underline">
{t('swap:price-impact')}
</span>
<p className="tooltip-underline">{t('swap:price-impact')}</p>
</Tooltip>
<p className="text-right font-mono text-sm text-th-fgd-2">
{selectedRoute?.priceImpactPct * 100 < 0.1
@ -522,39 +520,67 @@ const SwapReviewRouteInfo = ({
</p>
</div>
{borrowAmount ? (
<div className="flex justify-between">
<Tooltip
content={
balance
? t('swap:tooltip-borrow-balance', {
balance: formatNumericValue(balance),
borrowAmount: formatNumericValue(borrowAmount),
token: inputTokenInfo?.symbol,
rate: formatNumericValue(
inputBank!.getBorrowRateUi(),
2
),
})
: t('swap:tooltip-borrow-no-balance', {
borrowAmount: formatNumericValue(borrowAmount),
token: inputTokenInfo?.symbol,
rate: formatNumericValue(
inputBank!.getBorrowRateUi(),
2
),
})
}
delay={100}
>
<p className="tooltip-underline text-sm text-th-fgd-3">
{t('borrow-amount')}
<>
<div className="flex justify-between">
<Tooltip
content={
balance
? t('swap:tooltip-borrow-balance', {
balance: formatNumericValue(balance),
borrowAmount: formatNumericValue(borrowAmount),
token: inputTokenInfo?.symbol,
rate: formatNumericValue(
inputBank!.getBorrowRateUi(),
2
),
})
: t('swap:tooltip-borrow-no-balance', {
borrowAmount: formatNumericValue(borrowAmount),
token: inputTokenInfo?.symbol,
rate: formatNumericValue(
inputBank!.getBorrowRateUi(),
2
),
})
}
delay={100}
>
<p className="tooltip-underline">{t('borrow-amount')}</p>
</Tooltip>
<p className="text-right font-mono text-sm text-th-fgd-2">
<FormatNumericValue value={borrowAmount} />{' '}
<span className="font-body text-th-fgd-3">
{inputTokenInfo?.symbol}
</span>
</p>
</Tooltip>
<p className="text-right font-mono text-sm text-th-fgd-2">
~<FormatNumericValue value={borrowAmount} />{' '}
<span className="font-body">{inputTokenInfo?.symbol}</span>
</p>
</div>
</div>
<div className="flex justify-between">
<Tooltip
content={t('loan-origination-fee-tooltip', {
fee: `${(
inputBank!.loanOriginationFeeRate.toNumber() * 100
).toFixed(3)}%`,
})}
delay={100}
>
<p className="tooltip-underline">
{t('loan-origination-fee')}
</p>
</Tooltip>
<p className="text-right font-mono text-th-fgd-2">
<FormatNumericValue
value={
borrowAmount *
inputBank!.loanOriginationFeeRate.toNumber()
}
decimals={inputBank!.mintDecimals}
/>{' '}
<span className="font-body text-th-fgd-3">
{inputBank!.name}
</span>
</p>
</div>
</>
) : null}
</div>
</div>
@ -584,7 +610,7 @@ const SwapReviewRouteInfo = ({
open ? 'mb-2 rounded-b-none' : ''
}`}
>
<p>{open ? t('swap:hide-fees') : t('swap:show-fees')}</p>
<p>{t('swap:route-info')}</p>
<ChevronDownIcon
className={`${
open ? 'rotate-180' : 'rotate-360'
@ -592,35 +618,6 @@ const SwapReviewRouteInfo = ({
/>
</Disclosure.Button>
<Disclosure.Panel className="space-y-2 p-3 pt-0">
{borrowAmount ? (
<div className="flex justify-between">
<Tooltip
content={t('loan-origination-fee-tooltip', {
fee: `${(
inputBank!.loanOriginationFeeRate.toNumber() * 100
).toFixed(3)}%`,
})}
delay={100}
>
<p className="tooltip-underline">
{t('loan-origination-fee')}
</p>
</Tooltip>
<p className="text-right font-mono text-th-fgd-2">
~
<FormatNumericValue
value={
borrowAmount *
inputBank!.loanOriginationFeeRate.toNumber()
}
decimals={inputBank!.mintDecimals}
/>{' '}
<span className="font-body text-th-fgd-4">
{inputBank!.name}
</span>
</p>
</div>
) : null}
<div className="flex items-center justify-between">
<p className="text-sm text-th-fgd-3">
{t('swap:swap-route')}

View File

@ -31,7 +31,7 @@ const TokenSelect = ({ bank, showTokenList, type }: TokenSelectProps) => {
return (
<button
onClick={() => showTokenList(type)}
className="flex h-full w-full items-center rounded-lg rounded-r-none border border-th-input-border bg-th-input-bkg py-2 px-3 text-th-fgd-2 hover:cursor-pointer hover:bg-th-bkg-2 hover:text-th-fgd-1 focus-visible:border-th-fgd-4"
className="flex h-full w-full items-center rounded-lg rounded-r-none border border-r-0 border-th-input-border bg-th-input-bkg py-2 px-3 text-th-fgd-2 focus-visible:bg-th-bkg-2 md:hover:cursor-pointer md:hover:bg-th-bkg-2 md:hover:text-th-fgd-1"
>
<div className="mr-2.5 flex min-w-[24px] items-center">
{logoURI ? (

View File

@ -45,6 +45,9 @@ export const getTokenInMax = (
}
}
const inputReduceOnly = inputBank.areBorrowsReduceOnly()
const outputReduceOnly = outputBank.areDepositsReduceOnly()
const inputTokenBalance = new Decimal(
mangoAccount.getTokenBalanceUi(inputBank)
)
@ -54,8 +57,8 @@ export const getTokenInMax = (
)
const maxAmountWithoutMargin =
(inputTokenBalance.gt(0) && !outputBank.reduceOnly) ||
(outputBank.reduceOnly && outputTokenBalance.lt(0))
(inputTokenBalance.gt(0) && !outputReduceOnly) ||
(outputReduceOnly && outputTokenBalance.lt(0))
? inputTokenBalance
: new Decimal(0)
@ -66,8 +69,7 @@ export const getTokenInMax = (
)
const maxUiAmountWithBorrow =
outputBank.reduceOnly &&
(outputTokenBalance.gt(0) || outputTokenBalance.eq(0))
outputReduceOnly && (outputTokenBalance.gt(0) || outputTokenBalance.eq(0))
? new Decimal(0)
: rawMaxUiAmountWithBorrow > 0
? floorToDecimal(rawMaxUiAmountWithBorrow, inputBank.mintDecimals)
@ -92,7 +94,7 @@ export const getTokenInMax = (
maxUiAmountWithBorrow
)
const maxAmountWithBorrow = inputBank.reduceOnly
const maxAmountWithBorrow = inputReduceOnly
? Decimal.min(maxAmountWithoutMargin, inputBankVaultBalance)
: Decimal.min(maxUiAmountWithBorrow, inputBankVaultBalance)

View File

@ -1,3 +1,4 @@
import { Bank } from '@blockworks-foundation/mango-v4'
import TabButtons from '@components/shared/TabButtons'
import mangoStore from '@store/mangoStore'
import useMangoGroup from 'hooks/useMangoGroup'
@ -7,7 +8,7 @@ import { TokenStatsItem } from 'types'
import { formatYAxis } from 'utils/formatting'
import DetailedAreaChart from '@components/shared/DetailedAreaChart'
const ChartTabs = ({ token }: { token: string }) => {
const ChartTabs = ({ bank }: { bank: Bank }) => {
const { t } = useTranslation('token')
const [activeDepositsTab, setActiveDepositsTab] = useState('token:deposits')
const [activeBorrowsTab, setActiveBorrowsTab] = useState('token:borrows')
@ -30,11 +31,7 @@ const ChartTabs = ({ token }: { token: string }) => {
const statsHistory = useMemo(() => {
if (!tokenStats?.length) return []
return tokenStats.reduce((a: TokenStatsItem[], c: TokenStatsItem) => {
if (
c.symbol === token ||
// ETH needs to be renamed ETH (Portal) in tokenStats db
(c.symbol === 'ETH' && token === 'ETH (Portal)')
) {
if (c.token_index === bank.tokenIndex) {
const copy = { ...c }
copy.deposit_apr = copy.deposit_apr * 100
copy.borrow_apr = copy.borrow_apr * 100
@ -45,22 +42,7 @@ const ChartTabs = ({ token }: { token: string }) => {
new Date(a.date_hour).getTime() - new Date(b.date_hour).getTime()
)
}, [])
}, [tokenStats])
// const filterStats = (daysToShow: string) => {
// if (!statsHistory.length) return []
// if (daysToShow !== '30') {
// const seconds = Number(daysToShow) * 86400
// const data = statsHistory.filter((d) => {
// const dataTime = new Date(d.date_hour).getTime() / 1000
// const now = new Date().getTime() / 1000
// const limit = now - seconds
// return dataTime >= limit
// })
// return data
// }
// return statsHistory
// }
}, [tokenStats, bank])
return (
<div className="grid grid-cols-1 md:grid-cols-2">
@ -87,7 +69,7 @@ const ChartTabs = ({ token }: { token: string }) => {
loading={loadingTokenStats}
small
tickFormat={(x) => formatYAxis(x)}
title={`${token} ${t('token:deposits')}`}
title={`${bank?.name} ${t('token:deposits')}`}
xKey="date_hour"
yKey={'total_deposits'}
/>
@ -98,13 +80,12 @@ const ChartTabs = ({ token }: { token: string }) => {
setDaysToShow={setDepositRateDaysToShow}
heightClass="h-64"
loaderHeightClass="h-[334px]"
// domain={[0, 'dataMax']}
loading={loadingTokenStats}
hideChange
small
suffix="%"
tickFormat={(x) => `${x.toFixed(2)}%`}
title={`${token} ${t('token:deposit-rates')} APR`}
title={`${bank?.name} ${t('token:deposit-rates')} APR`}
xKey="date_hour"
yKey={'deposit_apr'}
/>
@ -135,7 +116,7 @@ const ChartTabs = ({ token }: { token: string }) => {
loading={loadingTokenStats}
small
tickFormat={(x) => formatYAxis(x)}
title={`${token} ${t('token:borrows')}`}
title={`${bank?.name} ${t('token:borrows')}`}
xKey="date_hour"
yKey={'total_borrows'}
/>
@ -146,13 +127,12 @@ const ChartTabs = ({ token }: { token: string }) => {
setDaysToShow={setBorrowRateDaysToShow}
heightClass="h-64"
loaderHeightClass="h-[334px]"
// domain={[0, 'dataMax']}
loading={loadingTokenStats}
small
hideChange
suffix="%"
tickFormat={(x) => `${x.toFixed(2)}%`}
title={`${token} ${t('token:borrow-rates')} APR`}
title={`${bank?.name} ${t('token:borrow-rates')} APR`}
xKey="date_hour"
yKey={'borrow_apr'}
/>

View File

@ -21,6 +21,7 @@ import { useQuery } from '@tanstack/react-query'
import FormatNumericValue from '@components/shared/FormatNumericValue'
import TopTokenAccounts from './TopTokenAccounts'
import TokenParams from './TokenParams'
import { formatTokenSymbol } from 'utils/tokens'
const DEFAULT_COINGECKO_VALUES = {
ath: 0,
@ -136,7 +137,9 @@ const TokenPage = () => {
{coingeckoTokenInfo ? (
<h1 className="text-base font-normal">
{coingeckoTokenInfo.name}{' '}
<span className="text-th-fgd-4">{bank.name}</span>
<span className="text-th-fgd-4">
{formatTokenSymbol(bank.name)}
</span>
</h1>
) : (
<h1 className="text-base font-normal">{bank.name}</h1>
@ -173,7 +176,7 @@ const TokenPage = () => {
</div>
<ActionPanel bank={bank} />
</div>
<ChartTabs token={bankName} />
<ChartTabs bank={bank} />
<div className="flex items-center justify-center border-y border-th-bkg-3 px-6 py-4 text-center">
<Tooltip
content={'The percentage of deposits that have been lent out.'}

View File

@ -7,6 +7,7 @@ import {
import Tooltip from '@components/shared/Tooltip'
import { ArrowTopRightOnSquareIcon } from '@heroicons/react/20/solid'
import { BN } from '@project-serum/anchor'
import mangoStore from '@store/mangoStore'
import { getOracleProvider } from 'hooks/useOracleProvider'
import { useTranslation } from 'next-i18next'
import { useMemo } from 'react'
@ -20,6 +21,12 @@ const TokenParams = ({ bank }: { bank: Bank }) => {
return getOracleProvider(bank)
}, [bank])
const mintInfo = useMemo(() => {
const group = mangoStore.getState().group
if (!bank || !group) return
return group.mintInfosMapByMint.get(bank.mint.toString())
}, [bank])
return (
<div className="grid grid-cols-1 border-b border-th-bkg-3 md:grid-cols-2">
<div className="col-span-1 border-b border-th-bkg-3 px-6 py-4 md:col-span-2">
@ -84,6 +91,20 @@ const TokenParams = ({ bank }: { bank: Bank }) => {
{(bank.liquidationFee.toNumber() * 100).toFixed(2)}%
</p>
</div>
{mintInfo ? (
<div className="flex justify-between border-t border-th-bkg-3 py-4">
<Tooltip
content={t('trade:tooltip-insured', { tokenOrMarket: bank.name })}
>
<p className="tooltip-underline">
{t('trade:insured', { token: '' })}
</p>
</Tooltip>
<p className="text-th-fgd-2">
{mintInfo.groupInsuranceFund ? t('yes') : t('no')}
</p>
</div>
) : null}
<div className="flex justify-between border-y border-th-bkg-3 py-4 md:border-b-0">
<Tooltip content={t('token:tooltip-deposit-borrow-scaling-start')}>
<p className="tooltip-underline">

View File

@ -101,7 +101,9 @@ const AdvancedMarketHeader = ({
<OraclePrice setChangePrice={setChangePrice} />
</>
<div className="ml-6 flex-col whitespace-nowrap">
<div className="text-xs text-th-fgd-4">{t('rolling-change')}</div>
<div className="mb-0.5 text-xs text-th-fgd-4">
{t('rolling-change')}
</div>
{!loadingPrices && !loadingPerpStats ? (
<Change change={change} size="small" suffix="%" />
) : (
@ -114,7 +116,7 @@ const AdvancedMarketHeader = ({
<>
<PerpFundingRate />
<div className="ml-6 flex-col whitespace-nowrap text-xs">
<div className="text-th-fgd-4">
<div className="mb-0.5 text-th-fgd-4 ">
{t('trade:open-interest')}
</div>
<span className="font-mono">

View File

@ -70,10 +70,10 @@ const INPUT_SUFFIX_CLASSNAMES =
const INPUT_PREFIX_CLASSNAMES =
'absolute left-2 top-1/2 h-5 w-5 flex-shrink-0 -translate-y-1/2'
const DEFAULT_CHECKBOX_SETTINGS = {
export const DEFAULT_CHECKBOX_SETTINGS = {
ioc: false,
post: false,
margin: true,
margin: false,
}
const AdvancedTradeForm = () => {
@ -288,36 +288,18 @@ const AdvancedTradeForm = () => {
)
const [tickDecimals, tickSize] = useMemo(() => {
const group = mangoStore.getState().group
if (!group || !selectedMarket) return [1, 0.1]
let tickSize: number
if (selectedMarket instanceof Serum3Market) {
const market = group.getSerum3ExternalMarket(
selectedMarket.serumMarketExternal
)
tickSize = market.tickSize
} else {
tickSize = selectedMarket.tickSize
}
if (!serumOrPerpMarket) return [1, 0.1]
const tickSize = serumOrPerpMarket.tickSize
const tickDecimals = getDecimalCount(tickSize)
return [tickDecimals, tickSize]
}, [selectedMarket])
}, [serumOrPerpMarket])
const [minOrderDecimals, minOrderSize] = useMemo(() => {
const group = mangoStore.getState().group
if (!group || !selectedMarket) return [1, 0.1]
let minOrderSize: number
if (selectedMarket instanceof Serum3Market) {
const market = group.getSerum3ExternalMarket(
selectedMarket.serumMarketExternal
)
minOrderSize = market.minOrderSize
} else {
minOrderSize = selectedMarket.minOrderSize
}
if (!serumOrPerpMarket) return [1, 0.1]
const minOrderSize = serumOrPerpMarket.minOrderSize
const minOrderDecimals = getDecimalCount(minOrderSize)
return [minOrderDecimals, minOrderSize]
}, [selectedMarket])
}, [serumOrPerpMarket])
/*
* Updates the limit price on page load
@ -600,13 +582,16 @@ const AdvancedTradeForm = () => {
/>
<div className={INPUT_SUFFIX_CLASSNAMES}>{quoteSymbol}</div>
</div>
{serumOrPerpMarket &&
{minOrderSize &&
tradeForm.baseSize &&
parseFloat(tradeForm.baseSize) < serumOrPerpMarket.minOrderSize ? (
parseFloat(tradeForm.baseSize) < minOrderSize ? (
<div className="mt-1">
<InlineNotification
type="error"
desc={`Min order size is ${minOrderSize} ${baseSymbol}`}
desc={t('trade:min-order-size-error', {
minSize: minOrderSize,
symbol: baseSymbol,
})}
hideBorder
hidePadding
/>
@ -725,7 +710,7 @@ const AdvancedTradeForm = () => {
? ''
: tradeForm.side === 'buy'
? 'bg-th-up-dark text-white md:hover:bg-th-up-dark md:hover:brightness-90'
: 'bg-th-down text-white md:hover:bg-th-down md:hover:brightness-90'
: 'bg-th-down-dark text-white md:hover:bg-th-down-dark md:hover:brightness-90'
}`}
disabled={disabled}
size="large"

View File

@ -16,6 +16,12 @@ import { DEFAULT_MARKET_NAME } from 'utils/constants'
import { floorToDecimal, getDecimalCount } from 'utils/numbers'
import MarketLogos from './MarketLogos'
const MARKET_LINK_WRAPPER_CLASSES =
'flex items-center justify-between px-4 md:pl-6 md:pr-4'
const MARKET_LINK_CLASSES =
'mr-2 -ml-3 flex w-full items-center justify-between rounded-md py-2 px-3 focus:outline-none focus-visible:text-th-active md:hover:cursor-pointer md:hover:bg-th-bkg-3 md:hover:text-th-fgd-1'
const MarketSelectDropdown = () => {
const { t } = useTranslation('common')
const { selectedMarket } = useSelectedMarket()
@ -85,24 +91,24 @@ const MarketSelectDropdown = () => {
: 0
return (
<div
className="flex items-center justify-between py-2 px-4 md:px-6"
className={MARKET_LINK_WRAPPER_CLASSES}
key={m.publicKey.toString()}
onClick={() => {
close()
}}
>
<Link
className="flex items-center hover:cursor-pointer focus:outline-none focus-visible:text-th-active md:hover:text-th-fgd-3"
className={MARKET_LINK_CLASSES}
href={{
pathname: '/trade',
query: { name: m.name },
}}
shallow={true}
>
<MarketLogos market={m} />
<span>{m.name}</span>
</Link>
<div className="flex items-center space-x-3">
<div className="flex items-center">
<MarketLogos market={m} />
<span>{m.name}</span>
</div>
{!loadingPerpStats ? (
<Change change={change} suffix="%" />
) : (
@ -110,8 +116,8 @@ const MarketSelectDropdown = () => {
<div className="h-3.5 w-12 bg-th-bkg-2" />
</SheenLoader>
)}
<FavoriteMarketButton market={m} />
</div>
</Link>
<FavoriteMarketButton market={m} />
</div>
)
})
@ -153,24 +159,24 @@ const MarketSelectDropdown = () => {
: 0
return (
<div
className="flex items-center justify-between py-2 px-4 md:px-6"
className={MARKET_LINK_WRAPPER_CLASSES}
key={m.publicKey.toString()}
onClick={() => {
close()
}}
>
<Link
className="flex items-center hover:cursor-pointer focus:outline-none focus-visible:text-th-active md:hover:text-th-fgd-3"
className={MARKET_LINK_CLASSES}
href={{
pathname: '/trade',
query: { name: m.name },
}}
shallow={true}
>
<MarketLogos market={m} />
<span>{m.name}</span>
</Link>
<div className="flex items-center space-x-3">
<div className="flex items-center">
<MarketLogos market={m} />
<span>{m.name}</span>
</div>
{!loadingPrices ? (
change ? (
<Change change={change} suffix="%" />
@ -182,8 +188,8 @@ const MarketSelectDropdown = () => {
<div className="h-3.5 w-12 bg-th-bkg-2" />
</SheenLoader>
)}
<FavoriteMarketButton market={m} />
</div>
</Link>
<FavoriteMarketButton market={m} />
</div>
)
})}

View File

@ -15,10 +15,12 @@ const MaxSizeButton = ({
minOrderDecimals,
tickDecimals,
useMargin,
large,
}: {
minOrderDecimals: number
tickDecimals: number
useMargin: boolean
large?: boolean
}) => {
const { t } = useTranslation(['common', 'trade'])
const { mangoAccount } = useMangoAccount()
@ -114,10 +116,12 @@ const MaxSizeButton = ({
return (
<div className="mb-2 mt-3 flex items-center justify-between">
<p className="text-xs text-th-fgd-3">{t('trade:size')}</p>
<p className={`${large ? 'text-sm' : 'text-xs'} text-th-fgd-3`}>
{t('trade:size')}
</p>
<FadeInFadeOut show={!!price && !isUnownedAccount && connected}>
<MaxAmountButton
className="text-xs"
className={large ? 'text-sm' : 'text-xs'}
decimals={minOrderDecimals}
label={t('max')}
onClick={handleMax}

View File

@ -42,7 +42,9 @@ import MarketLogos from './MarketLogos'
import PerpSideBadge from './PerpSideBadge'
import TableMarketName from './TableMarketName'
const findSerum3MarketPkInOpenOrders = (o: Order): string | undefined => {
export const findSerum3MarketPkInOpenOrders = (
o: Order
): string | undefined => {
const openOrders = mangoStore.getState().mangoAccount.openOrders
let foundedMarketPk: string | undefined = undefined
for (const [marketPk, orders] of Object.entries(openOrders)) {
@ -267,7 +269,6 @@ const OpenOrders = () => {
let tickSize: number
let minOrderSize: number
let expiryTimestamp: number | undefined
let side: string
if (o instanceof PerpOrder) {
market = group.getPerpMarketByMarketIndex(o.perpMarketIndex)
tickSize = market.tickSize
@ -276,7 +277,6 @@ const OpenOrders = () => {
o.expiryTimestamp === U64_MAX_BN
? 0
: o.expiryTimestamp.toNumber()
side = 'bid' in o.side ? 'buy' : 'sell'
} else {
market = group.getSerum3MarketByExternalMarket(
new PublicKey(marketPk)
@ -286,7 +286,6 @@ const OpenOrders = () => {
)
tickSize = serumMarket.tickSize
minOrderSize = serumMarket.minOrderSize
side = o.side
}
return (
<TrBody
@ -297,7 +296,13 @@ const OpenOrders = () => {
<TableMarketName market={market} />
</Td>
<Td className="w-[16.67%] text-right">
<SideBadge side={side} />
{o instanceof PerpOrder ? (
<PerpSideBadge
basePosition={'bid' in o.side ? 1 : -1}
/>
) : (
<SideBadge side={o.side} />
)}
</Td>
{modifyOrderId !== o.orderId.toString() ? (
<>

View File

@ -1,7 +1,4 @@
import {
ExclamationTriangleIcon,
InformationCircleIcon,
} from '@heroicons/react/24/outline'
import { ExclamationTriangleIcon } from '@heroicons/react/24/outline'
import useSelectedMarket from 'hooks/useSelectedMarket'
import Tooltip from '@components/shared/Tooltip'
import { useTranslation } from 'next-i18next'
@ -63,11 +60,10 @@ const OraclePrice = ({
}
const coder = new BorshAccountsCoder(client.program.idl)
setPrice(stalePrice)
const subId = connection.onAccountChange(
marketOrBank.oracle,
async (info, context) => {
// selectedMarket = mangoStore.getState().selectedMarket.current
// if (!(selectedMarket instanceof PerpMarket)) return
const { price, uiPrice, lastUpdatedSlot } =
await group.decodePriceFromOracleAi(
coder,
@ -122,7 +118,14 @@ const OraclePrice = ({
connection.removeAccountChangeListener(subId)
}
}
}, [connection, selectedMarket])
}, [
connection,
selectedMarket,
serumOrPerpMarket,
setChangePrice,
quoteBank,
stalePrice,
])
return (
<>
@ -167,14 +170,12 @@ const OraclePrice = ({
}
>
<div className="flex items-center">
<div className="text-xs text-th-fgd-4">
<div className="tooltip-underline mb-0.5 text-xs text-th-fgd-4">
{t('trade:oracle-price')}
</div>
{isStale ? (
<ExclamationTriangleIcon className="ml-1 h-4 w-4 text-th-warning" />
) : (
<InformationCircleIcon className="ml-1 h-4 w-4 text-th-fgd-4" />
)}
) : null}
</div>
</Tooltip>
<div className="font-mono text-xs text-th-fgd-2">

View File

@ -10,7 +10,7 @@ import {
formatNumericValue,
getDecimalCount,
} from 'utils/numbers'
import { ANIMATION_SETTINGS_KEY } from 'utils/constants'
import { ANIMATION_SETTINGS_KEY, USE_ORDERBOOK_FEED_KEY } from 'utils/constants'
import { useTranslation } from 'next-i18next'
import Decimal from 'decimal.js'
import { OrderbookL2 } from 'types'
@ -219,7 +219,11 @@ const Orderbook = () => {
const [tickSize, setTickSize] = useState(0)
const [showBids, setShowBids] = useState(true)
const [showAsks, setShowAsks] = useState(true)
const [useOrderbookFeed, setUseOrderbookFeed] = useState(true)
const [useOrderbookFeed, setUseOrderbookFeed] = useState(
localStorage.getItem(USE_ORDERBOOK_FEED_KEY) !== null
? localStorage.getItem(USE_ORDERBOOK_FEED_KEY) === 'true'
: true
)
const currentOrderbookData = useRef<OrderbookL2>()
const orderbookElRef = useRef<HTMLDivElement>(null)
@ -234,6 +238,8 @@ const Orderbook = () => {
return Array(depth).fill(0)
}, [depth])
const orderbookFeed = useRef<OrderbookFeed | null>(null)
useEffect(() => {
if (market && market.tickSize !== tickSize) {
setTickSize(market.tickSize)
@ -377,7 +383,6 @@ const Orderbook = () => {
// subscribe to the bids and asks orderbook accounts
useEffect(() => {
console.log('setting up orderbook websockets')
const set = mangoStore.getState().set
const client = mangoStore.getState().client
const group = mangoStore.getState().group
@ -385,23 +390,27 @@ const Orderbook = () => {
if (!group || !market) return
if (useOrderbookFeed) {
if (!orderbookFeed.current) {
orderbookFeed.current = new OrderbookFeed(
`wss://api.mngo.cloud/orderbook/v1/`,
{
reconnectionIntervalMs: 5_000,
reconnectionMaxAttempts: 6,
}
)
}
let hasConnected = false
const orderbookFeed = new OrderbookFeed(
`wss://api.mngo.cloud/orderbook/v1/`,
{
reconnectionIntervalMs: 5_000,
reconnectionMaxAttempts: 6,
}
)
orderbookFeed.onConnect(() => {
orderbookFeed.current.onConnect(() => {
if (!orderbookFeed.current) return
console.log('[OrderbookFeed] connected')
hasConnected = true
orderbookFeed.subscribe({
orderbookFeed.current.subscribe({
marketId: market.publicKey.toBase58(),
})
})
orderbookFeed.onDisconnect((reconnectionAttemptsExhausted) => {
orderbookFeed.current.onDisconnect((reconnectionAttemptsExhausted) => {
// fallback to rpc if we couldn't reconnect or if we never connected
if (reconnectionAttemptsExhausted || !hasConnected) {
console.warn('[OrderbookFeed] disconnected')
@ -412,9 +421,10 @@ const Orderbook = () => {
})
let lastWriteVersion = 0
orderbookFeed.onL2Update((update) => {
orderbookFeed.current.onL2Update((update) => {
const selectedMarket = mangoStore.getState().selectedMarket
if (!selectedMarket || !selectedMarket.current) return
if (!useOrderbookFeed || !selectedMarket || !selectedMarket.current)
return
if (update.market != selectedMarket.current.publicKey.toBase58()) return
// ensure updates are applied in the correct order by checking slot and writeVersion
@ -457,7 +467,7 @@ const Orderbook = () => {
if (levelIndex !== -1) {
new_bookside.splice(levelIndex, 1)
} else {
console.warn('tried to remove missing level')
console.warn('[OrderbookFeed] tried to remove missing level')
}
}
}
@ -473,9 +483,12 @@ const Orderbook = () => {
}
})
})
orderbookFeed.onL2Checkpoint((checkpoint) => {
if (checkpoint.market !== market.publicKey.toBase58()) return
orderbookFeed.current.onL2Checkpoint((checkpoint) => {
if (
!useOrderbookFeed ||
checkpoint.market !== market.publicKey.toBase58()
)
return
set((state) => {
state.selectedMarket.lastSeenSlot.bids = checkpoint.slot
state.selectedMarket.lastSeenSlot.asks = checkpoint.slot
@ -485,16 +498,16 @@ const Orderbook = () => {
state.selectedMarket.orderbook.asks = checkpoint.asks
})
})
orderbookFeed.onStatus((update) => {
console.log('[OrderbookFeed] status', update)
})
return () => {
console.log('[OrderbookFeed] unsubscribe')
orderbookFeed.unsubscribe(market.publicKey.toBase58())
if (!orderbookFeed.current) return
console.log(
`[OrderbookFeed] unsubscribe ${market.publicKey.toBase58()}`
)
orderbookFeed.current.unsubscribe(market.publicKey.toBase58())
}
} else {
console.log('using rpc orderbook feed')
console.log(`[OrderbookRPC] subscribe ${market.publicKey.toBase58()}`)
let bidSubscriptionId: number | undefined = undefined
let askSubscriptionId: number | undefined = undefined
@ -570,7 +583,7 @@ const Orderbook = () => {
)
}
return () => {
console.log('rpc orderbook unsubscribe')
console.log(`[OrderbookRPC] unsubscribe ${market.publicKey.toBase58()}`)
if (typeof bidSubscriptionId !== 'undefined') {
connection.removeAccountChangeListener(bidSubscriptionId)
}
@ -581,6 +594,15 @@ const Orderbook = () => {
}
}, [bidAccountAddress, askAccountAddress, connection, useOrderbookFeed])
useEffect(() => {
const market = getMarket()
if (!orderbookFeed.current || !market) return
console.log(`[OrderbookFeed] subscribe ${market.publicKey.toBase58()}`)
orderbookFeed.current.subscribe({
marketId: market.publicKey.toBase58(),
})
}, [bidAccountAddress])
useEffect(() => {
window.addEventListener('resize', verticallyCenterOrderbook)
}, [verticallyCenterOrderbook])

View File

@ -1,5 +1,4 @@
import { BookSide, PerpMarket } from '@blockworks-foundation/mango-v4'
import { InformationCircleIcon } from '@heroicons/react/24/outline'
import { useQuery } from '@tanstack/react-query'
import useMangoGroup from 'hooks/useMangoGroup'
import useSelectedMarket from 'hooks/useSelectedMarket'
@ -9,6 +8,7 @@ import Tooltip from '@components/shared/Tooltip'
import { useTranslation } from 'next-i18next'
import mangoStore from '@store/mangoStore'
import { OrderbookL2 } from 'types'
import Link from 'next/link'
const fetchFundingRate = async (groupPk: string | undefined) => {
const res = await fetch(
@ -139,22 +139,34 @@ const PerpFundingRate = () => {
{typeof fundingRate === 'number' ? (
<div className="mt-2">
The 1hr rate as an APR is{' '}
{formatFunding.format(fundingRate * 8760)}.
<span className="font-mono text-th-fgd-2">
{formatFunding.format(fundingRate * 8760)}
</span>
</div>
) : null}
{instantaneousRate ? (
<div className="mt-1">
The latest instantaneous rate is {instantaneousRate}%
<div className="mt-2">
The latest instantaneous rate is{' '}
<span className="font-mono text-th-fgd-2">
{instantaneousRate}%
</span>
</div>
) : null}
<Link
className="mt-2 block"
href={`/stats?market=${selectedMarket?.name}`}
shallow={true}
>
View Chart
</Link>
</>
}
>
<div className="flex items-center">
<div className="text-xs text-th-fgd-4">
<div className="tooltip-underline mb-0.5 text-xs text-th-fgd-4">
{t('trade:funding-rate')}
</div>
<InformationCircleIcon className="ml-1 h-4 w-4 text-th-fgd-4" />
{/* <InformationCircleIcon className="ml-1 h-4 w-4 text-th-fgd-4" /> */}
</div>
</Tooltip>
<p className="font-mono text-xs text-th-fgd-2">

View File

@ -60,14 +60,17 @@ const PerpSlider = ({
: Number(s.tradeForm.price)
if (s.tradeForm.side === 'buy') {
s.tradeForm.quoteSize = val
if (Number(price)) {
s.tradeForm.baseSize = floorToDecimal(
const baseSize = floorToDecimal(
parseFloat(val) / price,
minOrderDecimals
).toString()
)
const quoteSize = floorToDecimal(baseSize.mul(price), tickDecimals)
s.tradeForm.baseSize = baseSize.toFixed()
s.tradeForm.quoteSize = quoteSize.toFixed()
} else {
s.tradeForm.baseSize = ''
s.tradeForm.quoteSize = val
}
} else if (s.tradeForm.side === 'sell') {
s.tradeForm.baseSize = val
@ -75,7 +78,7 @@ const PerpSlider = ({
s.tradeForm.quoteSize = floorToDecimal(
parseFloat(val) * price,
tickDecimals
).toString()
).toFixed()
}
}
})

View File

@ -83,14 +83,17 @@ const SpotSlider = ({
: Number(s.tradeForm.price)
if (s.tradeForm.side === 'buy') {
s.tradeForm.quoteSize = val
if (Number(price)) {
s.tradeForm.baseSize = floorToDecimal(
const baseSize = floorToDecimal(
parseFloat(val) / price,
minOrderDecimals
).toString()
)
const quoteSize = floorToDecimal(baseSize.mul(price), tickDecimals)
s.tradeForm.baseSize = baseSize.toFixed()
s.tradeForm.quoteSize = quoteSize.toFixed()
} else {
s.tradeForm.baseSize = ''
s.tradeForm.quoteSize = val
}
} else if (s.tradeForm.side === 'sell') {
s.tradeForm.baseSize = val
@ -98,7 +101,7 @@ const SpotSlider = ({
s.tradeForm.quoteSize = floorToDecimal(
parseFloat(val) * price,
tickDecimals
).toString()
).toFixed()
}
}
})

View File

@ -3,7 +3,6 @@ import {
MangoAccount,
PerpMarket,
Serum3Market,
toUiDecimalsForQuote,
} from '@blockworks-foundation/mango-v4'
import FormatNumericValue from '@components/shared/FormatNumericValue'
import HealthImpact from '@components/shared/HealthImpact'
@ -15,6 +14,8 @@ import useSelectedMarket from 'hooks/useSelectedMarket'
import { useTranslation } from 'next-i18next'
import { useMemo } from 'react'
import Slippage from './Slippage'
import { floorToDecimal, formatNumericValue } from 'utils/numbers'
import { formatTokenSymbol } from 'utils/tokens'
const TradeSummary = ({
mangoAccount,
@ -88,8 +89,8 @@ const TradeSummary = ({
}
}, [group, selectedMarket, tradeForm.side])
const borrowAmount = useMemo(() => {
if (!balanceBank || !mangoAccount) return 0
const [balance, borrowAmount] = useMemo(() => {
if (!balanceBank || !mangoAccount) return [0, 0]
let borrowAmount
const balance = mangoAccount.getTokenDepositsUi(balanceBank)
if (tradeForm.side === 'buy') {
@ -100,21 +101,25 @@ const TradeSummary = ({
borrowAmount = remainingBalance < 0 ? Math.abs(remainingBalance) : 0
}
return borrowAmount
return [balance, borrowAmount]
}, [balanceBank, mangoAccount, tradeForm])
const orderValue = useMemo(() => {
if (
!quoteBank ||
!tradeForm.price ||
!Number.isNaN(tradeForm.price) ||
!Number.isNaN(tradeForm.baseSize)
!tradeForm.baseSize ||
Number.isNaN(tradeForm.price) ||
Number.isNaN(tradeForm.baseSize)
)
return 0
const basePriceDecimal = new Decimal(tradeForm.price)
const quotePriceDecimal = new Decimal(quoteBank.uiPrice)
const sizeDecimal = new Decimal(tradeForm.baseSize)
return basePriceDecimal.mul(quotePriceDecimal).mul(sizeDecimal)
return floorToDecimal(
basePriceDecimal.mul(quotePriceDecimal).mul(sizeDecimal),
2
)
}, [quoteBank, tradeForm])
return (
@ -122,39 +127,74 @@ const TradeSummary = ({
<div className="flex justify-between text-xs">
<p>{t('trade:order-value')}</p>
<p className="text-th-fgd-2">
{orderValue ? (
<FormatNumericValue value={orderValue} decimals={2} isUsd />
) : (
''
)}
{orderValue ? <FormatNumericValue value={orderValue} isUsd /> : ''}
</p>
</div>
<HealthImpact maintProjectedHealth={maintProjectedHealth} small />
{borrowAmount ? (
<div className="flex justify-between text-xs">
<Tooltip
content={t('loan-origination-fee-tooltip', {
fee: `${(
balanceBank!.loanOriginationFeeRate.toNumber() * 100
).toFixed(3)}%`,
})}
delay={100}
>
<p className="tooltip-underline">{t('loan-origination-fee')}</p>
</Tooltip>
<p className="text-right font-mono text-th-fgd-2">
~
<FormatNumericValue
value={
borrowAmount * balanceBank!.loanOriginationFeeRate.toNumber()
{borrowAmount && balanceBank ? (
<>
<div className="flex justify-between text-xs">
<Tooltip
content={
balance
? t('trade:tooltip-borrow-balance', {
balance: formatNumericValue(balance),
borrowAmount: formatNumericValue(borrowAmount),
token: formatTokenSymbol(balanceBank.name),
rate: formatNumericValue(
balanceBank.getBorrowRateUi(),
2
),
})
: t('trade:tooltip-borrow-no-balance', {
borrowAmount: formatNumericValue(borrowAmount),
token: formatTokenSymbol(balanceBank.name),
rate: formatNumericValue(
balanceBank.getBorrowRateUi(),
2
),
})
}
decimals={balanceBank!.mintDecimals}
/>{' '}
<span className="font-body text-th-fgd-4">{balanceBank!.name}</span>
</p>
</div>
delay={100}
>
<p className="tooltip-underline">{t('borrow-amount')}</p>
</Tooltip>
<p className="text-right font-mono text-th-fgd-2">
<FormatNumericValue
value={borrowAmount}
decimals={balanceBank.mintDecimals}
/>{' '}
<span className="font-body text-th-fgd-4">
{formatTokenSymbol(balanceBank.name)}
</span>
</p>
</div>
<div className="flex justify-between text-xs">
<Tooltip
content={t('loan-origination-fee-tooltip', {
fee: `${(
balanceBank.loanOriginationFeeRate.toNumber() * 100
).toFixed(3)}%`,
})}
delay={100}
>
<p className="tooltip-underline">{t('loan-origination-fee')}</p>
</Tooltip>
<p className="text-right font-mono text-th-fgd-2">
<FormatNumericValue
value={
borrowAmount * balanceBank.loanOriginationFeeRate.toNumber()
}
decimals={balanceBank.mintDecimals}
/>{' '}
<span className="font-body text-th-fgd-4">
{formatTokenSymbol(balanceBank.name)}
</span>
</p>
</div>
</>
) : null}
<div className="flex justify-between text-xs">
{/* <div className="flex justify-between text-xs">
<Tooltip content="The amount of capital you have to use for trades and loans. When your free collateral reaches $0 you won't be able to trade, borrow or withdraw.">
<p className="tooltip-underline">{t('free-collateral')}</p>
</Tooltip>
@ -171,7 +211,7 @@ const TradeSummary = ({
''
)}
</p>
</div>
</div> */}
<Slippage />
</div>
)

View File

@ -6,7 +6,7 @@ import {
IChartingLibraryWidget,
ResolutionString,
IOrderLineAdapter,
EntityId,
AvailableSaveloadVersions,
IExecutionLineAdapter,
Direction,
} from '@public/charting_library'
@ -15,7 +15,7 @@ import { useViewport } from 'hooks/useViewport'
import {
DEFAULT_MARKET_NAME,
SHOW_ORDER_LINES_KEY,
SHOW_STABLE_PRICE_KEY,
TV_USER_ID_KEY,
} from 'utils/constants'
import { breakpoints } from 'utils/theme'
import { COLORS } from 'styles/colors'
@ -24,10 +24,7 @@ import { notify } from 'utils/notifications'
import {
PerpMarket,
PerpOrder,
PerpOrderType,
Serum3Market,
Serum3OrderType,
Serum3SelfTradeBehavior,
Serum3Side,
} from '@blockworks-foundation/mango-v4'
import { Order } from '@project-serum/serum/lib/market'
@ -37,11 +34,12 @@ import { formatNumericValue, getDecimalCount } from 'utils/numbers'
import { BN } from '@project-serum/anchor'
import Datafeed from 'apis/datafeed'
// import PerpDatafeed from 'apis/mngo/datafeed'
import useStablePrice from 'hooks/useStablePrice'
import { CombinedTradeHistory, isMangoError } from 'types'
import { formatPrice } from 'apis/birdeye/helpers'
import useTradeHistory from 'hooks/useTradeHistory'
import dayjs from 'dayjs'
import ModifyTvOrderModal from '@components/modals/ModifyTvOrderModal'
import { findSerum3MarketPkInOpenOrders } from './OpenOrders'
export interface ChartContainerProps {
container: ChartingLibraryWidgetOptions['container']
@ -75,7 +73,10 @@ const TradingViewChart = () => {
const { width } = useViewport()
const [chartReady, setChartReady] = useState(false)
const [headerReady, setHeaderReady] = useState(false)
const [spotOrPerp, setSpotOrPerp] = useState('spot')
const [orderToModify, setOrderToModify] = useState<Order | PerpOrder | null>(
null
)
const [modifiedPrice, setModifiedPrice] = useState('')
const [showOrderLinesLocalStorage, toggleShowOrderLinesLocalStorage] =
useLocalStorageState(SHOW_ORDER_LINES_KEY, true)
const [showOrderLines, toggleShowOrderLines] = useState(
@ -87,14 +88,7 @@ const TradingViewChart = () => {
const [showTradeExecutions, toggleShowTradeExecutions] = useState(false)
const [cachedTradeHistory, setCachedTradeHistory] =
useState(combinedTradeHistory)
const [showStablePriceLocalStorage, toggleShowStablePriceLocalStorage] =
useLocalStorageState(SHOW_STABLE_PRICE_KEY, false)
const [showStablePrice, toggleShowStablePrice] = useState(
showStablePriceLocalStorage
)
const stablePrice = useStablePrice()
const stablePriceLine = mangoStore((s) => s.tradingView.stablePriceLine)
const [userId] = useLocalStorageState(TV_USER_ID_KEY, '')
const selectedMarketName = mangoStore((s) => s.selectedMarket.current?.name)
const isMobile = width ? width < breakpoints.sm : false
@ -105,6 +99,9 @@ const TradingViewChart = () => {
theme: 'Dark',
container: 'tv_chart_container',
libraryPath: '/charting_library/',
chartsStorageUrl: 'https://tv-backend-v4.herokuapp.com',
chartsStorageApiVersion: '1.1' as AvailableSaveloadVersions,
clientId: 'mango.markets',
fullscreen: false,
autosize: true,
studiesOverrides: {
@ -117,10 +114,9 @@ const TradingViewChart = () => {
)
const tvWidgetRef = useRef<IChartingLibraryWidget>()
const stablePriceButtonRef = useRef<HTMLElement>()
const orderLinesButtonRef = useRef<HTMLElement>()
const selectedMarket = useMemo(() => {
const selectedMarketPk = useMemo(() => {
const group = mangoStore.getState().group
if (!group || !selectedMarketName)
return '8BnEgHoWFysVcuFFX7QztDmzuH8r5ZFvyP3sYwn1XTh6'
@ -136,12 +132,17 @@ const TradingViewChart = () => {
useEffect(() => {
const group = mangoStore.getState().group
if (tvWidgetRef.current && chartReady && selectedMarket && group) {
if (tvWidgetRef.current && chartReady && selectedMarketPk && group) {
try {
tvWidgetRef.current.setSymbol(
selectedMarket,
selectedMarketPk,
tvWidgetRef.current.activeChart().resolution(),
() => {
if (showOrderLinesLocalStorage) {
const openOrders = mangoStore.getState().mangoAccount.openOrders
deleteLines()
drawLinesForMarket(openOrders)
}
return
}
)
@ -149,101 +150,7 @@ const TradingViewChart = () => {
console.warn('Trading View change symbol error: ', e)
}
}
}, [selectedMarket, chartReady])
useEffect(() => {
if (
selectedMarketName?.toLowerCase().includes('perp') &&
spotOrPerp !== 'perp'
) {
setSpotOrPerp('perp')
} else if (
!selectedMarketName?.toLowerCase().includes('perp') &&
spotOrPerp !== 'spot'
) {
setSpotOrPerp('spot')
}
}, [selectedMarketName, spotOrPerp])
useEffect(() => {
if (showStablePrice !== showStablePriceLocalStorage) {
toggleShowStablePriceLocalStorage(showStablePrice)
}
}, [
showStablePrice,
showStablePriceLocalStorage,
toggleShowStablePriceLocalStorage,
theme,
])
const drawStablePriceLine = useCallback(
(price: number) => {
if (!tvWidgetRef?.current?.chart()) return
const now = Date.now() / 1000
try {
const oldId = mangoStore.getState().tradingView.stablePriceLine
if (oldId) {
tvWidgetRef.current.chart().removeEntity(oldId)
}
const id = tvWidgetRef.current.chart().createShape(
{ time: now, price: price },
{
shape: 'horizontal_line',
overrides: {
linecolor: COLORS.FGD4[theme],
linestyle: 1,
linewidth: 1,
},
}
)
if (id) {
return id
} else {
console.warn('failed to create stable price line')
}
} catch {
console.warn('failed to create stable price line')
}
},
[theme]
)
const removeStablePrice = useCallback((id: EntityId) => {
if (!tvWidgetRef?.current?.chart()) return
const set = mangoStore.getState().set
try {
tvWidgetRef.current.chart().removeEntity(id)
} catch (error) {
console.warn('stable price could not be removed')
}
set((s) => {
s.tradingView.stablePriceLine = undefined
})
}, [])
// remove stable price line when toggling off
useEffect(() => {
if (tvWidgetRef.current && chartReady) {
if (!showStablePrice && stablePriceLine) {
removeStablePrice(stablePriceLine)
}
}
}, [showStablePrice, chartReady, removeStablePrice, stablePriceLine])
// update stable price line when toggled on
useEffect(() => {
if (tvWidgetRef.current && chartReady) {
if (showStablePrice && stablePrice) {
const set = mangoStore.getState().set
set((s) => {
s.tradingView.stablePriceLine = drawStablePriceLine(stablePrice)
})
}
}
}, [stablePrice, chartReady, showStablePrice, drawStablePriceLine])
}, [chartReady, selectedMarketPk, showOrderLinesLocalStorage])
useEffect(() => {
if (showOrderLines !== showOrderLinesLocalStorage) {
@ -291,91 +198,6 @@ const TradingViewChart = () => {
return [minOrderDecimals, tickSizeDecimals]
}, [])
const findSerum3MarketPkInOpenOrders = useCallback(
(o: Order): string | undefined => {
const openOrders = mangoStore.getState().mangoAccount.openOrders
let foundedMarketPk: string | undefined = undefined
for (const [marketPk, orders] of Object.entries(openOrders)) {
for (const order of orders) {
if (order.orderId.eq(o.orderId)) {
foundedMarketPk = marketPk
break
}
}
if (foundedMarketPk) {
break
}
}
return foundedMarketPk
},
[]
)
const modifyOrder = useCallback(
async (o: PerpOrder | Order, price: number) => {
const client = mangoStore.getState().client
const group = mangoStore.getState().group
const mangoAccount = mangoStore.getState().mangoAccount.current
const actions = mangoStore.getState().actions
const baseSize = o.size
if (!group || !mangoAccount) return
try {
let tx = ''
if (o instanceof PerpOrder) {
tx = await client.modifyPerpOrder(
group,
mangoAccount,
o.perpMarketIndex,
o.orderId,
o.side,
price,
Math.abs(baseSize),
undefined, // maxQuoteQuantity
Date.now(),
PerpOrderType.limit,
undefined,
undefined
)
} else {
const marketPk = findSerum3MarketPkInOpenOrders(o)
if (!marketPk) return
const market = group.getSerum3MarketByExternalMarket(
new PublicKey(marketPk)
)
tx = await client.modifySerum3Order(
group,
o.orderId,
mangoAccount,
market.serumMarketExternal,
o.side === 'buy' ? Serum3Side.bid : Serum3Side.ask,
price,
baseSize,
Serum3SelfTradeBehavior.decrementTake,
Serum3OrderType.limit,
Date.now(),
10
)
}
actions.fetchOpenOrders()
notify({
type: 'success',
title: 'Transaction successful',
txid: tx,
})
} catch (e) {
console.error('Error canceling', e)
if (!isMangoError(e)) return
notify({
title: 'Unable to modify order',
description: e.message,
txid: e.txid,
type: 'error',
})
}
},
[findSerum3MarketPkInOpenOrders]
)
const cancelSpotOrder = useCallback(
async (o: Order) => {
const client = mangoStore.getState().client
@ -457,10 +279,12 @@ const TradingViewChart = () => {
typeof order.side === 'string'
? t(order.side)
: 'bid' in order.side
? t('buy')
: t('sell')
const isLong = side.toLowerCase() === 'buy'
const isShort = side.toLowerCase() === 'sell'
? t('long')
: t('short')
const isLong =
side.toLowerCase() === 'buy' || side.toLowerCase() === 'long'
const isShort =
side.toLowerCase() === 'sell' || side.toLowerCase() === 'short'
const [minOrderDecimals, tickSizeDecimals] = getOrderDecimals()
const orderSizeUi: string = formatNumericValue(
order.size,
@ -498,29 +322,10 @@ const TradingViewChart = () => {
},
})
} else {
tvWidgetRef.current?.showConfirmDialog({
title: t('tv-chart:modify-order'),
body: t('tv-chart:modify-order-details', {
marketName: selectedMarketName,
orderSize: orderSizeUi,
orderSide: side.toUpperCase(),
currentOrderPrice: formatNumericValue(
currentOrderPrice,
tickSizeDecimals
),
updatedOrderPrice: formatNumericValue(
updatedOrderPrice,
tickSizeDecimals
),
}),
callback: (res) => {
if (res) {
modifyOrder(order, updatedOrderPrice)
} else {
this.setPrice(currentOrderPrice)
}
},
})
setOrderToModify(order)
setModifiedPrice(
formatNumericValue(updatedOrderPrice, tickSizeDecimals)
)
}
})
.onCancel(function () {
@ -573,7 +378,6 @@ const TradingViewChart = () => {
[
cancelPerpOrder,
cancelSpotOrder,
modifyOrder,
selectedMarketName,
t,
theme,
@ -625,6 +429,13 @@ const TradingViewChart = () => {
[drawLinesForMarket, deleteLines, theme]
)
const closeModifyOrderModal = useCallback(() => {
const openOrders = mangoStore.getState().mangoAccount.openOrders
setOrderToModify(null)
deleteLines()
drawLinesForMarket(openOrders)
}, [deleteLines, drawLinesForMarket])
const toggleTradeExecutions = useCallback(
(el: HTMLElement) => {
toggleShowTradeExecutions((prevState) => !prevState)
@ -637,31 +448,6 @@ const TradingViewChart = () => {
[theme]
)
const createStablePriceButton = useCallback(() => {
const toggleStablePrice = (button: HTMLElement) => {
toggleShowStablePrice((prevState: boolean) => !prevState)
if (button.style.color === hexToRgb(COLORS.ACTIVE[theme])) {
button.style.color = COLORS.FGD4[theme]
} else {
button.style.color = COLORS.ACTIVE[theme]
}
}
const button = tvWidgetRef?.current?.createButton()
if (!button) {
return
}
button.textContent = 'SP'
button.setAttribute('title', t('tv-chart:toggle-stable-price'))
button.addEventListener('click', () => toggleStablePrice(button))
if (showStablePriceLocalStorage) {
button.style.color = COLORS.ACTIVE[theme]
} else {
button.style.color = COLORS.FGD4[theme]
}
stablePriceButtonRef.current = button
}, [theme, t, showStablePriceLocalStorage])
const createOLButton = useCallback(() => {
const button = tvWidgetRef?.current?.createButton()
if (!button) {
@ -728,9 +514,13 @@ const TradingViewChart = () => {
[`mainSeriesProperties.${prop}.wickDownColor`]: COLORS.DOWN[theme],
}
})
const mkt = mangoStore.getState().selectedMarket.current
const marketAddress =
mangoStore.getState().selectedMarket.current?.publicKey.toString() ||
(mkt instanceof Serum3Market
? mkt?.serumMarketExternal.toString()
: mkt?.publicKey.toString()) ||
'8BnEgHoWFysVcuFFX7QztDmzuH8r5ZFvyP3sYwn1XTh6'
const widgetOptions: ChartingLibraryWidgetOptions = {
// debug: true,
symbol: marketAddress,
@ -741,7 +531,10 @@ const TradingViewChart = () => {
defaultProps.container as ChartingLibraryWidgetOptions['container'],
library_path: defaultProps.libraryPath as string,
locale: 'en',
enabled_features: ['hide_left_toolbar_by_default'],
enabled_features: [
'hide_left_toolbar_by_default',
// userId ? 'study_templates' : '',
],
disabled_features: [
'use_localstorage_for_settings',
'timeframes_toolbar',
@ -755,7 +548,7 @@ const TradingViewChart = () => {
'header_screenshot',
// 'header_widget_dom_node',
// 'header_widget',
'header_saveload',
!userId ? 'header_saveload' : '',
'header_undo_redo',
'header_interval_dialog_button',
'show_interval_dialog_on_key_press',
@ -774,6 +567,10 @@ const TradingViewChart = () => {
}
},
},
charts_storage_url: defaultProps.chartsStorageUrl,
charts_storage_api_version: defaultProps.chartsStorageApiVersion,
client_id: defaultProps.clientId,
user_id: userId ? userId : undefined,
fullscreen: defaultProps.fullscreen,
autosize: defaultProps.autosize,
studies_overrides: defaultProps.studiesOverrides,
@ -801,27 +598,42 @@ const TradingViewChart = () => {
setHeaderReady(true)
})
}
}, [theme, defaultProps, isMobile])
}, [theme, defaultProps, isMobile, userId])
// set a limit price from right click context menu
useEffect(() => {
if (chartReady && tvWidgetRef.current) {
tvWidgetRef.current.onContextMenu(function (unixtime, price) {
return [
{
position: 'top',
text: `Set limit price (${formatPrice(price)})`,
click: function () {
const set = mangoStore.getState().set
set((s) => {
s.tradeForm.price = price.toFixed(12)
})
},
},
{
position: 'top',
text: '-',
click: function () {
return
},
},
]
})
}
}, [chartReady, tvWidgetRef])
// draw custom buttons when chart is ready
useEffect(() => {
if (
chartReady &&
headerReady &&
!orderLinesButtonRef.current &&
!stablePriceButtonRef.current
) {
if (chartReady && headerReady && !orderLinesButtonRef.current) {
createOLButton()
createTEButton()
createStablePriceButton()
}
}, [
createOLButton,
createTEButton,
chartReady,
createStablePriceButton,
headerReady,
])
}, [createOLButton, createTEButton, chartReady, headerReady])
// update order lines if a user's open orders change
useEffect(() => {
@ -881,7 +693,7 @@ const TradingViewChart = () => {
)
}
return subscription
}, [chartReady, showOrderLines, deleteLines, drawLinesForMarket])
}, [chartReady, deleteLines, drawLinesForMarket, showOrderLines])
const drawTradeExecutions = useCallback(
(trades: CombinedTradeHistory) => {
@ -971,7 +783,20 @@ const TradingViewChart = () => {
}, [cachedTradeHistory, selectedMarketName, showTradeExecutions])
return (
<div id={defaultProps.container as string} className="tradingview-chart" />
<>
<div
id={defaultProps.container as string}
className="tradingview-chart"
/>
{orderToModify ? (
<ModifyTvOrderModal
isOpen={!!orderToModify}
onClose={closeModifyOrderModal}
price={modifiedPrice}
order={orderToModify}
/>
) : null}
</>
)
}

View File

@ -12,6 +12,7 @@ import React, {
} from 'react'
import { notify } from 'utils/notifications'
import useLocalStorageState from 'hooks/useLocalStorageState'
import { TV_USER_ID_KEY } from 'utils/constants'
interface EnhancedWalletContextState {
displayedWallets: Wallet[]
@ -63,6 +64,8 @@ export default function EnhancedWalletProvider({
const [preselectedWalletName, setPreselectedWalletName] =
useLocalStorageState<string>('preselectedWalletName', '')
const [tvUserId, setTvUserId] = useLocalStorageState(TV_USER_ID_KEY, '')
useEffect(() => {
if (wallet) {
setPreselectedWalletName(wallet.adapter.name)
@ -102,6 +105,9 @@ export default function EnhancedWalletProvider({
try {
console.log('connecting')
await connect()
if (!tvUserId && wallet.adapter.publicKey) {
setTvUserId(wallet.adapter.publicKey.toString())
}
} catch (e) {
// Error will be handled by WalletProvider#onError
select(null)

View File

@ -5,6 +5,7 @@ import mangoStore from '@store/mangoStore'
import { useMemo } from 'react'
import useMangoAccount from './useMangoAccount'
import useMangoGroup from './useMangoGroup'
import { floorToDecimal } from 'utils/numbers'
export interface BankWithBalance {
balance: number
@ -37,7 +38,15 @@ export default function useBanksWithBalances(
})
).map((b) => {
const bank = b.value[0]
const balance = mangoAccount ? mangoAccount.getTokenBalanceUi(bank) : 0
const rawBalance = mangoAccount
? mangoAccount.getTokenBalanceUi(bank)
: 0
// For some reason if you don't use an abs value in floorToDecimal it returns incorrectly
const isBorrowMultiplier = rawBalance < 0 ? -1 : 1
const balance =
floorToDecimal(Math.abs(rawBalance), bank.mintDecimals).toNumber() *
isBorrowMultiplier
const maxBorrow = mangoAccount
? getMaxWithdrawForBank(group, bank, mangoAccount, true).toNumber()
: 0
@ -45,7 +54,10 @@ export default function useBanksWithBalances(
? getMaxWithdrawForBank(group, bank, mangoAccount).toNumber()
: 0
const borrowedAmount = mangoAccount
? mangoAccount.getTokenBorrowsUi(bank)
? floorToDecimal(
mangoAccount.getTokenBorrowsUi(bank),
bank.mintDecimals
).toNumber()
: 0
const walletBalance =
walletBalanceForToken(walletTokens, bank.name)?.maxAmount || 0

View File

@ -19,7 +19,7 @@
},
"dependencies": {
"@blockworks-foundation/mango-feeds": "0.1.6",
"@blockworks-foundation/mango-v4": "^0.14.1",
"@blockworks-foundation/mango-v4": "^0.15.12",
"@headlessui/react": "1.6.6",
"@heroicons/react": "2.0.10",
"@metaplex-foundation/js": "0.18.3",

View File

@ -21,6 +21,8 @@ import { Disclosure } from '@headlessui/react'
import MarketLogos from '@components/trade/MarketLogos'
import Button from '@components/shared/Button'
import BN from 'bn.js'
import { useRouter } from 'next/router'
import Link from 'next/link'
export async function getStaticProps({ locale }: { locale: string }) {
return {
@ -56,9 +58,9 @@ const Dashboard: NextPage = () => {
<div className="col-span-12 lg:col-span-8 lg:col-start-3">
<div className="p-8 pb-20 md:pb-16 lg:p-10">
<h1>Dashboard</h1>
<DashboardNavbar />
{group ? (
<div className="mt-4">
<h2 className="mb-2">Group</h2>
<ExplorerLink
address={group?.publicKey.toString()}
anchorData
@ -742,4 +744,53 @@ const VaultData = ({ bank }: { bank: Bank }) => {
)
}
export const DashboardNavbar = () => {
const { asPath } = useRouter()
return (
<div className="mt-4 mb-2 flex border border-th-bkg-3">
<div>
<Link href={'/dashboard'} shallow={true}>
<h4
className={`${
asPath === '/dashboard' ? 'bg-th-bkg-2 text-th-active' : ''
} cursor-pointer border-r border-th-bkg-3 px-6 py-4`}
>
Group
</h4>
</Link>
</div>
<div>
<Link href={'/dashboard/risks'} shallow={true}>
<h4
className={`${
asPath === '/dashboard/risks' ? 'bg-th-bkg-2 text-th-active' : ''
} cursor-pointer border-r border-th-bkg-3 px-6 py-4`}
>
Risks
</h4>
</Link>
</div>
<div>
<Link
href={
'/dashboard/mangoaccount?address=DNjtajTW6PZps3gCerWEPBRvu1vZPEieVEoqXFrXWn3k'
}
shallow={true}
>
<h4
className={`${
asPath.includes('/dashboard/mangoaccount')
? 'bg-th-bkg-2 text-th-active'
: ''
} cursor-pointer border-r border-th-bkg-3 px-6 py-4`}
>
Mango Account
</h4>
</Link>
</div>
</div>
)
}
export default Dashboard

View File

@ -1,7 +1,7 @@
import ExplorerLink from '@components/shared/ExplorerLink'
import useMangoGroup from 'hooks/useMangoGroup'
import type { NextPage } from 'next'
import { ReactNode, useCallback, useEffect, useState } from 'react'
import { ChangeEvent, ReactNode, useCallback, useEffect, useState } from 'react'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import useMangoAccount from 'hooks/useMangoAccount'
import {
@ -10,6 +10,11 @@ import {
PerpOrder,
} from '@blockworks-foundation/mango-v4'
import mangoStore from '@store/mangoStore'
import { DashboardNavbar } from '.'
import Input from '@components/forms/Input'
import Button from '@components/shared/Button'
import { useRouter } from 'next/router'
import { MagnifyingGlassIcon } from '@heroicons/react/20/solid'
export async function getStaticProps({ locale }: { locale: string }) {
return {
@ -19,19 +24,17 @@ export async function getStaticProps({ locale }: { locale: string }) {
}
}
const Dashboard: NextPage = () => {
const MangoAccountDashboard: NextPage = () => {
const { group } = useMangoGroup()
// const { mangoTokens } = useJupiterMints()
// const client = mangoStore(s => s.client)
const { mangoAccount } = useMangoAccount()
const client = mangoStore((s) => s.client)
const [openOrders, setOpenOrders] = useState<Record<string, PerpOrder[]>>()
const router = useRouter()
console.log('router.query', router.query)
useEffect(() => {
if (mangoAccount) {
loadOpenOrders()
}
}, [mangoAccount])
const [searchString, setSearchString] = useState<string>(
router.query['address'] as string
)
const loadOpenOrders = useCallback(async () => {
if (!mangoAccount || !group) return
@ -49,11 +52,42 @@ const Dashboard: NextPage = () => {
setOpenOrders(openOrders)
}, [mangoAccount, client, group])
useEffect(() => {
if (mangoAccount) {
loadOpenOrders()
}
}, [mangoAccount, loadOpenOrders])
return (
<div className="grid grid-cols-12">
<div className="col-span-12 border-b border-th-bkg-3 lg:col-span-8 lg:col-start-3 xl:col-span-6 xl:col-start-4">
<div className="col-span-12 lg:col-span-8 lg:col-start-3">
<div className="p-8 pb-20 md:pb-16 lg:p-10">
<h1>Dashboard</h1>
<DashboardNavbar />
<div className="">
<div className="flex space-x-2">
<Input
type="text"
name="search"
id="search"
value={searchString}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
setSearchString(e.target.value)
}
/>
<Button
className="flex items-center"
onClick={() =>
router.push(`/dashboard/mangoaccount?address=${searchString}`)
}
disabled={!searchString}
size="large"
>
<MagnifyingGlassIcon className="mr-2 h-5 w-5" />
Search
</Button>
</div>
</div>
{group && mangoAccount ? (
<div className="mt-4">
<h2 className="mb-6">Mango Account</h2>
@ -342,7 +376,7 @@ const Dashboard: NextPage = () => {
: null}
</div>
) : (
'Loading'
<div className="m-4 flex items-center">Loading account data...</div>
)}
</div>
</div>
@ -365,4 +399,4 @@ const KeyValuePair = ({
)
}
export default Dashboard
export default MangoAccountDashboard

156
pages/dashboard/risks.tsx Normal file
View File

@ -0,0 +1,156 @@
import useMangoGroup from 'hooks/useMangoGroup'
import type { NextPage } from 'next'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import { DashboardNavbar } from '.'
import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements'
import { useQuery } from '@tanstack/react-query'
import { emptyWallet } from '@store/mangoStore'
import {
MANGO_V4_ID,
MangoClient,
getRiskStats,
} from '@blockworks-foundation/mango-v4'
import { PublicKey } from '@solana/web3.js'
import { formatNumericValue } from 'utils/numbers'
import { AnchorProvider, web3 } from '@project-serum/anchor'
export async function getStaticProps({ locale }: { locale: string }) {
return {
props: {
...(await serverSideTranslations(locale, ['common'])),
},
}
}
type TableData = {
title: string
data: Array<Record<string, string | number | PublicKey>>
}
const formatValue = (val: string | number | PublicKey) => {
if (val instanceof PublicKey) {
return val.toString()
}
if (typeof val === 'string') {
return val
} else {
return formatNumericValue(val)
}
}
const RiskDashboard: NextPage = () => {
const { group } = useMangoGroup()
const { data, isLoading, isFetching } = useQuery(
['risks'],
() => {
const provider = new AnchorProvider(
new web3.Connection(
'https://mango.rpcpool.com/0f9acc0d45173b51bf7d7e09c1e5',
'processed'
),
emptyWallet,
AnchorProvider.defaultOptions()
)
const client = MangoClient.connect(
provider,
'mainnet-beta',
MANGO_V4_ID['mainnet-beta']
)
if (group) {
return getRiskStats(client, group)
}
},
{
cacheTime: 1000 * 60 * 5,
staleTime: 1000 * 60 * 5,
retry: 1,
refetchOnWindowFocus: false,
enabled: !!group,
}
)
console.log('resp', isLoading, isFetching, data)
return (
<div className="grid grid-cols-12">
<div className="col-span-12 xl:col-span-8 xl:col-start-3">
<div className="p-8 pb-20 text-th-fgd-1 md:pb-16 xl:p-10">
<h1>Dashboard</h1>
<DashboardNavbar />
{group && data ? (
<div className="mt-4">
{Object.entries(data).map(
([tableType, table]: [string, TableData]) => {
return (
<div className="mt-12" key={tableType}>
<div className="mb-4">
<p className="text-th-fgd-4">{table.title}</p>
</div>
<Table>
<thead>
<TrHead className="border">
{Object.keys(table.data[0]).map(
(colName: string) => {
return (
<Th
xBorder
className="text-left"
key={colName}
>
{colName}{' '}
{colName.toLowerCase().includes('fee') ||
colName.toLowerCase().includes('slippage')
? '(bps)'
: ''}
{colName.toLowerCase().includes('assets') ||
colName.toLowerCase().includes('liabs') ||
colName.toLowerCase().includes('equity') ||
colName.toLowerCase().includes('price') ||
colName.toLowerCase().includes('position')
? '($)'
: ''}
</Th>
)
}
)}
</TrHead>
</thead>
<tbody>
{table.data.map((rowData, index: number) => {
console.log('rowData', Object.values(rowData))
return (
<TrBody key={index}>
{Object.values(rowData).map(
(
val: string | number | PublicKey,
idx: number
) => {
return (
<Td xBorder className={''} key={idx}>
{formatValue(val)}
</Td>
)
}
)}
</TrBody>
)
})}
</tbody>
</Table>
</div>
)
}
)}
</div>
) : (
'Loading'
)}
</div>
</div>
</div>
)
}
export default RiskDashboard

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

View File

@ -42,6 +42,7 @@
"close-account-desc": "Are you sure? Closing your account is irreversible",
"closing-account": "Closing your account...",
"collateral-value": "Collateral Value",
"confirm": "Confirm",
"connect": "Connect",
"connect-balances": "Connect to view your balances",
"connect-helper": "Connect to get started",
@ -51,7 +52,7 @@
"country-not-allowed-tooltip": "You are using an open-source frontend facilitated by the Mango DAO. As such, it restricts access to certain regions out of an abundance of caution, due to regulatory uncertainty.",
"create-account": "Create Account",
"creating-account": "Creating Account...",
"cumulative-interest-value": "Cumulative Interest Earned",
"cumulative-interest-value": "Cumulative Interest",
"daily-volume": "24h Volume",
"date": "Date",
"date-from": "Date From",
@ -93,7 +94,7 @@
"liquidity": "Liquidity",
"loading": "Loading",
"loan-origination-fee": "Borrow Fee",
"loan-origination-fee-tooltip": "{{fee}} fee for opening a borrow.",
"loan-origination-fee-tooltip": "{{fee}} fee for opening a borrow",
"mango": "Mango",
"mango-stats": "Mango Stats",
"market": "Market",
@ -145,7 +146,7 @@
"token": "Token",
"tokens": "Tokens",
"token-collateral-multiplier": "{{token}} Collateral Multiplier",
"tooltip-borrow-fee": "The fee for originating a loan.",
"tooltip-borrow-fee": "The fee for opening a new borrow.",
"tooltip-borrow-rate": "The variable interest rate you'll pay on your borrowed balance",
"tooltip-collateral-value": "The USD amount you can trade or borrow against",
"total": "Total",
@ -166,6 +167,7 @@
"utilization": "Utilization",
"value": "Value",
"view-transaction": "View Transaction",
"wallet": "Wallet",
"wallet-address": "Wallet Address",
"wallet-balance": "Wallet Balance",
"wallet-disconnected": "Disconnected from wallet",

View File

@ -2,6 +2,8 @@
"list-token": "List Token",
"cancel": "Cancel",
"connect-wallet": "Connect Wallet",
"delegate": "Delegate",
"none": "None",
"on-boarding-title": "Looks like currently connected wallet doesn't have any MNGO deposited inside realms",
"on-boarding-description": "Before you continue. Deposit at least {{amount}} MNGO into",
"on-boarding-deposit-info": "Your MNGO will be locked for the duration of the proposal.",
@ -70,5 +72,7 @@
"your-votes": "Your Votes:",
"yes": "Yes",
"no": "No",
"current-vote": "Current Vote:"
"current-vote": "Current Vote:",
"use-own-wallet": "Use own wallet",
"select-delegate": "Select delegate"
}

View File

@ -4,6 +4,6 @@
"empty-state-title": "Nothing to see here",
"notifications": "Notifications",
"sign-message": "Sign Message",
"unauth-desc": "You need to verify your wallet to start receiving notifications.",
"unauth-desc": "Sign with your wallet to start receiving notifications",
"unauth-title": "Notifications Inbox"
}

View File

@ -12,6 +12,7 @@
"chart-right": "Chart Right",
"chinese": "简体中文",
"chinese-traditional": "繁體中文",
"connect-notifications": "Connect to update your notification settings",
"custom": "Custom",
"dark": "Dark",
"display": "Display",
@ -52,6 +53,7 @@
"trade-chart": "Trade Chart",
"trading-view": "Trading View",
"notifications": "Notifications",
"limit-order-filled": "Limit order filled",
"sign-to-notifications": "Sign to notifications center to change settings"
"limit-order-filled": "Limit Order Fills",
"orderbook-bandwidth-saving": "Orderbook Bandwidth Saving",
"tooltip-orderbook-bandwidth-saving": "Use an off-chain service for Orderbook updates to decrease data usage by ~1000x. Disable this if open orders are not highlighted in the book correctly."
}

View File

@ -23,6 +23,7 @@
"receive": "You Receive",
"received": "Received",
"review-swap": "Review Swap",
"route-info": "Route Info",
"show-fees": "Show Fees",
"show-swap-history": "Show Swap History Prices shown are from CoinGecko and may not match your swap price",
"slippage": "Slippage",

View File

@ -36,7 +36,7 @@
"tooltip-deposit-borrow-scaling-start": "This acts as a soft limit for deposits and borrows. For deposits, if this value is exceeded the asset weight for deposits is scaled down. For borrows, the liability weight for borrows is scaled up.",
"tooltip-init-asset-liability-weight": "The contribution a token has to your initial account health. Asset weight is applied to deposits that increase health and liability weight is applied to borrows that reduce it. Initial health controls your ability to withdraw and open new positions and is shown as an account's free collateral.",
"tooltip-insurance-rate-curve": "Interest rates are dependent on token utilization. The more tokens that are lent out the higher the rate. The curve scales up and down with long term trends in utilization.",
"tooltip-liquidation-fee": "The fee paid to liqudators when {{symbol}} is liquidated.",
"tooltip-liquidation-fee": "The fee paid to liqudators for liquidating {{symbol}}.",
"tooltip-maint-asset-liability-weight": "The contribution a token has to your maintenance account health. Asset weight is applied to deposits that increase health and liability weight is applied to borrows that reduce it. Maintenance health is what's displayed on your account page. If this value reaches zero your account will be liquidated.",
"tooltip-net-borrow-period": "As a safety feature, the amount of new borrows is limited. The limit resets at regular periods. This is the amount of time between resets.",
"tooltip-net-borrow-limit-in-period": "The maximum value of allowed net borrows. Once this value is reached, new borrows will be disabled until the end of the period.",

View File

@ -13,6 +13,7 @@
"connect-unsettled": "Connect to view your unsettled funds",
"copy-and-share": "Copy Image to Clipboard",
"current-price": "Current Price",
"edit-order": "Edit Order",
"entry-price": "Entry Price",
"est-slippage": "Est. Slippage",
"funding-limits": "Funding Limits",
@ -27,13 +28,16 @@
"interval-seconds": "Interval (seconds)",
"insured": "{{token}} Insured",
"last-updated": "Last updated",
"limit": "Limit",
"limit-price": "Limit Price",
"long": "Long",
"maker": "Maker",
"maker-fee": "Maker Fee",
"margin": "Margin",
"market-details": "{{market}} Market Details",
"max-leverage": "Max Leverage",
"min-order-size": "Min Order Size",
"min-order-size-error": "Min order size is {{minSize}} {{symbol}}",
"no-balances": "No balances",
"no-orders": "No open orders",
"no-positions": "No perp positions",
@ -69,7 +73,10 @@
"spread": "Spread",
"stable-price": "Stable Price",
"taker": "Taker",
"taker-fee": "Taker Fee",
"tick-size": "Tick Size",
"tooltip-borrow-balance": "You'll use your {{balance}} {{token}} balance and borrow {{borrowAmount}} {{token}} to execute this trade. The current {{token}} variable borrow rate is {{rate}}%",
"tooltip-borrow-no-balance": "You'll borrow {{borrowAmount}} {{token}} to execute this trade. The current {{token}} variable borrow rate is {{rate}}%",
"tooltip-enable-margin": "Enable spot margin for this trade",
"tooltip-ioc": "Immediate-Or-Cancel (IOC) orders are guaranteed to be the taker and must be executed immediately. Any portion of the order that can't be filled immediately will be cancelled",
"tooltip-insured": "Whether or not {{tokenOrMarket}} losses can be recovered from the insurance fund in the event of bankruptcies.",

View File

@ -42,6 +42,7 @@
"close-account-desc": "Are you sure? Closing your account is irreversible",
"closing-account": "Closing your account...",
"collateral-value": "Collateral Value",
"confirm": "Confirm",
"connect": "Connect",
"connect-balances": "Connect to view your balances",
"connect-helper": "Connect to get started",
@ -51,7 +52,7 @@
"country-not-allowed-tooltip": "You are using an open-source frontend facilitated by the Mango DAO. As such, it restricts access to certain regions out of an abundance of caution, due to regulatory uncertainty.",
"create-account": "Create Account",
"creating-account": "Creating Account...",
"cumulative-interest-value": "Cumulative Interest Earned",
"cumulative-interest-value": "Cumulative Interest",
"daily-volume": "24h Volume",
"date": "Date",
"date-from": "Date From",
@ -93,7 +94,7 @@
"liquidity": "Liquidity",
"loading": "Loading",
"loan-origination-fee": "Borrow Fee",
"loan-origination-fee-tooltip": "{{fee}} fee for opening a borrow.",
"loan-origination-fee-tooltip": "{{fee}} fee for opening a borrow",
"mango": "Mango",
"mango-stats": "Mango Stats",
"market": "Market",
@ -145,7 +146,7 @@
"token": "Token",
"tokens": "Tokens",
"token-collateral-multiplier": "{{token}} Collateral Multiplier",
"tooltip-borrow-fee": "The fee for originating a loan.",
"tooltip-borrow-fee": "The fee for opening a new borrow.",
"tooltip-borrow-rate": "The variable interest rate you'll pay on your borrowed balance",
"tooltip-collateral-value": "The USD amount you can trade or borrow against",
"total": "Total",
@ -166,6 +167,7 @@
"utilization": "Utilization",
"value": "Value",
"view-transaction": "View Transaction",
"wallet": "Wallet",
"wallet-address": "Wallet Address",
"wallet-balance": "Wallet Balance",
"wallet-disconnected": "Disconnected from wallet",

View File

@ -2,6 +2,8 @@
"list-token": "List Token",
"cancel": "Cancel",
"connect-wallet": "Connect Wallet",
"delegate": "Delegate",
"none": "None",
"on-boarding-title": "Looks like currently connected wallet doesn't have any MNGO deposited inside realms",
"on-boarding-description": "Before you continue. Deposit at least {{amount}} MNGO into",
"on-boarding-deposit-info": "Your MNGO will be locked for the duration of the proposal.",
@ -70,5 +72,7 @@
"your-votes": "Your Votes:",
"yes": "Yes",
"no": "No",
"current-vote": "Current Vote:"
"current-vote": "Current Vote:",
"use-own-wallet": "Use own wallet",
"select-delegate": "Select delegate"
}

View File

@ -4,6 +4,6 @@
"empty-state-title": "Nothing to see here",
"notifications": "Notifications",
"sign-message": "Sign Message",
"unauth-desc": "You need to verify your wallet to start receiving notifications.",
"unauth-desc": "Sign with your wallet to start receiving notifications",
"unauth-title": "Notifications Inbox"
}

View File

@ -12,6 +12,7 @@
"chart-right": "Chart Right",
"chinese": "简体中文",
"chinese-traditional": "繁體中文",
"connect-notifications": "Connect to update your notification settings",
"custom": "Custom",
"dark": "Dark",
"display": "Display",
@ -52,6 +53,7 @@
"trade-chart": "Trade Chart",
"trading-view": "Trading View",
"notifications": "Notifications",
"limit-order-filled": "Limit order filled",
"sign-to-notifications": "Sign to notifications center to change settings"
"limit-order-filled": "Limit Order Fills",
"orderbook-bandwidth-saving": "Orderbook Bandwidth Saving",
"tooltip-orderbook-bandwidth-saving": "Use an off-chain service for Orderbook updates to decrease data usage by ~1000x. Disable this if open orders are not highlighted in the book correctly."
}

View File

@ -23,6 +23,7 @@
"receive": "You Receive",
"received": "Received",
"review-swap": "Review Swap",
"route-info": "Route Info",
"show-fees": "Show Fees",
"show-swap-history": "Show Swap History Prices shown are from CoinGecko and may not match your swap price",
"slippage": "Slippage",

View File

@ -36,7 +36,7 @@
"tooltip-deposit-borrow-scaling-start": "This acts as a soft limit for deposits and borrows. For deposits, if this value is exceeded the asset weight for deposits is scaled down. For borrows, the liability weight for borrows is scaled up.",
"tooltip-init-asset-liability-weight": "The contribution a token has to your initial account health. Asset weight is applied to deposits that increase health and liability weight is applied to borrows that reduce it. Initial health controls your ability to withdraw and open new positions and is shown as an account's free collateral.",
"tooltip-insurance-rate-curve": "Interest rates are dependent on token utilization. The more tokens that are lent out the higher the rate. The curve scales up and down with long term trends in utilization.",
"tooltip-liquidation-fee": "The fee paid to liqudators when {{symbol}} is liquidated.",
"tooltip-liquidation-fee": "The fee paid to liqudators for liquidating {{symbol}}.",
"tooltip-maint-asset-liability-weight": "The contribution a token has to your maintenance account health. Asset weight is applied to deposits that increase health and liability weight is applied to borrows that reduce it. Maintenance health is what's displayed on your account page. If this value reaches zero your account will be liquidated.",
"tooltip-net-borrow-period": "As a safety feature, the amount of new borrows is limited. The limit resets at regular periods. This is the amount of time between resets.",
"tooltip-net-borrow-limit-in-period": "The maximum value of allowed net borrows. Once this value is reached, new borrows will be disabled until the end of the period.",

View File

@ -13,6 +13,7 @@
"connect-unsettled": "Connect to view your unsettled funds",
"copy-and-share": "Copy Image to Clipboard",
"current-price": "Current Price",
"edit-order": "Edit Order",
"entry-price": "Entry Price",
"est-slippage": "Est. Slippage",
"funding-limits": "Funding Limits",
@ -27,13 +28,16 @@
"interval-seconds": "Interval (seconds)",
"insured": "{{token}} Insured",
"last-updated": "Last updated",
"limit": "Limit",
"limit-price": "Limit Price",
"long": "Long",
"maker": "Maker",
"maker-fee": "Maker Fee",
"margin": "Margin",
"market-details": "{{market}} Market Details",
"max-leverage": "Max Leverage",
"min-order-size": "Min Order Size",
"min-order-size-error": "Min order size is {{minSize}} {{symbol}}",
"no-balances": "No balances",
"no-orders": "No open orders",
"no-positions": "No perp positions",
@ -69,7 +73,10 @@
"spread": "Spread",
"stable-price": "Stable Price",
"taker": "Taker",
"taker-fee": "Taker Fee",
"tick-size": "Tick Size",
"tooltip-borrow-balance": "You'll use your {{balance}} {{token}} balance and borrow {{borrowAmount}} {{token}} to execute this trade. The current {{token}} variable borrow rate is {{rate}}%",
"tooltip-borrow-no-balance": "You'll borrow {{borrowAmount}} {{token}} to execute this trade. The current {{token}} variable borrow rate is {{rate}}%",
"tooltip-enable-margin": "Enable spot margin for this trade",
"tooltip-ioc": "Immediate-Or-Cancel (IOC) orders are guaranteed to be the taker and must be executed immediately. Any portion of the order that can't be filled immediately will be cancelled",
"tooltip-insured": "Whether or not {{tokenOrMarket}} losses can be recovered from the insurance fund in the event of bankruptcies.",

View File

@ -42,6 +42,7 @@
"close-account-desc": "Are you sure? Closing your account is irreversible",
"closing-account": "Closing your account...",
"collateral-value": "Collateral Value",
"confirm": "Confirm",
"connect": "Connect",
"connect-balances": "Connect to view your balances",
"connect-helper": "Connect to get started",
@ -51,7 +52,7 @@
"country-not-allowed-tooltip": "You are using an open-source frontend facilitated by the Mango DAO. As such, it restricts access to certain regions out of an abundance of caution, due to regulatory uncertainty.",
"create-account": "Create Account",
"creating-account": "Creating Account...",
"cumulative-interest-value": "Cumulative Interest Earned",
"cumulative-interest-value": "Cumulative Interest",
"daily-volume": "24h Volume",
"date": "Date",
"date-from": "Date From",
@ -93,7 +94,7 @@
"liquidity": "Liquidity",
"loading": "Loading",
"loan-origination-fee": "Borrow Fee",
"loan-origination-fee-tooltip": "{{fee}} fee for opening a borrow.",
"loan-origination-fee-tooltip": "{{fee}} fee for opening a borrow",
"mango": "Mango",
"mango-stats": "Mango Stats",
"market": "Market",
@ -145,7 +146,7 @@
"token": "Token",
"tokens": "Tokens",
"token-collateral-multiplier": "{{token}} Collateral Multiplier",
"tooltip-borrow-fee": "The fee for originating a loan.",
"tooltip-borrow-fee": "The fee for opening a new borrow.",
"tooltip-borrow-rate": "The variable interest rate you'll pay on your borrowed balance",
"tooltip-collateral-value": "The USD amount you can trade or borrow against",
"total": "Total",
@ -166,6 +167,7 @@
"utilization": "Utilization",
"value": "Value",
"view-transaction": "View Transaction",
"wallet": "Wallet",
"wallet-address": "Wallet Address",
"wallet-balance": "Wallet Balance",
"wallet-disconnected": "Disconnected from wallet",

View File

@ -2,6 +2,8 @@
"list-token": "List Token",
"cancel": "Cancel",
"connect-wallet": "Connect Wallet",
"delegate": "Delegate",
"none": "None",
"on-boarding-title": "Looks like currently connected wallet doesn't have any MNGO deposited inside realms",
"on-boarding-description": "Before you continue. Deposit at least {{amount}} MNGO into",
"on-boarding-deposit-info": "Your MNGO will be locked for the duration of the proposal.",
@ -70,5 +72,7 @@
"your-votes": "Your Votes:",
"yes": "Yes",
"no": "No",
"current-vote": "Current Vote:"
"current-vote": "Current Vote:",
"use-own-wallet": "Use own wallet",
"select-delegate": "Select delegate"
}

View File

@ -4,6 +4,6 @@
"empty-state-title": "Nothing to see here",
"notifications": "Notifications",
"sign-message": "Sign Message",
"unauth-desc": "You need to verify your wallet to start receiving notifications.",
"unauth-desc": "Sign with your wallet to start receiving notifications",
"unauth-title": "Notifications Inbox"
}

View File

@ -12,6 +12,7 @@
"chart-right": "Chart Right",
"chinese": "简体中文",
"chinese-traditional": "繁體中文",
"connect-notifications": "Connect to update your notification settings",
"custom": "Custom",
"dark": "Dark",
"display": "Display",
@ -52,6 +53,7 @@
"trade-chart": "Trade Chart",
"trading-view": "Trading View",
"notifications": "Notifications",
"limit-order-filled": "Limit order filled",
"sign-to-notifications": "Sign to notifications center to change settings"
"limit-order-filled": "Limit Order Fills",
"orderbook-bandwidth-saving": "Orderbook Bandwidth Saving",
"tooltip-orderbook-bandwidth-saving": "Use an off-chain service for Orderbook updates to decrease data usage by ~1000x. Disable this if open orders are not highlighted in the book correctly."
}

View File

@ -23,6 +23,7 @@
"receive": "You Receive",
"received": "Received",
"review-swap": "Review Swap",
"route-info": "Route Info",
"show-fees": "Show Fees",
"show-swap-history": "Show Swap History Prices shown are from CoinGecko and may not match your swap price",
"slippage": "Slippage",

View File

@ -36,7 +36,7 @@
"tooltip-deposit-borrow-scaling-start": "This acts as a soft limit for deposits and borrows. For deposits, if this value is exceeded the asset weight for deposits is scaled down. For borrows, the liability weight for borrows is scaled up.",
"tooltip-init-asset-liability-weight": "The contribution a token has to your initial account health. Asset weight is applied to deposits that increase health and liability weight is applied to borrows that reduce it. Initial health controls your ability to withdraw and open new positions and is shown as an account's free collateral.",
"tooltip-insurance-rate-curve": "Interest rates are dependent on token utilization. The more tokens that are lent out the higher the rate. The curve scales up and down with long term trends in utilization.",
"tooltip-liquidation-fee": "The fee paid to liqudators when {{symbol}} is liquidated.",
"tooltip-liquidation-fee": "The fee paid to liqudators for liquidating {{symbol}}.",
"tooltip-maint-asset-liability-weight": "The contribution a token has to your maintenance account health. Asset weight is applied to deposits that increase health and liability weight is applied to borrows that reduce it. Maintenance health is what's displayed on your account page. If this value reaches zero your account will be liquidated.",
"tooltip-net-borrow-period": "As a safety feature, the amount of new borrows is limited. The limit resets at regular periods. This is the amount of time between resets.",
"tooltip-net-borrow-limit-in-period": "The maximum value of allowed net borrows. Once this value is reached, new borrows will be disabled until the end of the period.",

View File

@ -13,6 +13,7 @@
"connect-unsettled": "Connect to view your unsettled funds",
"copy-and-share": "Copy Image to Clipboard",
"current-price": "Current Price",
"edit-order": "Edit Order",
"entry-price": "Entry Price",
"est-slippage": "Est. Slippage",
"funding-limits": "Funding Limits",
@ -27,13 +28,16 @@
"interval-seconds": "Interval (seconds)",
"insured": "{{token}} Insured",
"last-updated": "Last updated",
"limit": "Limit",
"limit-price": "Limit Price",
"long": "Long",
"maker": "Maker",
"maker-fee": "Maker Fee",
"margin": "Margin",
"market-details": "{{market}} Market Details",
"max-leverage": "Max Leverage",
"min-order-size": "Min Order Size",
"min-order-size-error": "Min order size is {{minSize}} {{symbol}}",
"no-balances": "No balances",
"no-orders": "No open orders",
"no-positions": "No perp positions",
@ -69,7 +73,10 @@
"spread": "Spread",
"stable-price": "Stable Price",
"taker": "Taker",
"taker-fee": "Taker Fee",
"tick-size": "Tick Size",
"tooltip-borrow-balance": "You'll use your {{balance}} {{token}} balance and borrow {{borrowAmount}} {{token}} to execute this trade. The current {{token}} variable borrow rate is {{rate}}%",
"tooltip-borrow-no-balance": "You'll borrow {{borrowAmount}} {{token}} to execute this trade. The current {{token}} variable borrow rate is {{rate}}%",
"tooltip-enable-margin": "Enable spot margin for this trade",
"tooltip-ioc": "Immediate-Or-Cancel (IOC) orders are guaranteed to be the taker and must be executed immediately. Any portion of the order that can't be filled immediately will be cancelled",
"tooltip-insured": "Whether or not {{tokenOrMarket}} losses can be recovered from the insurance fund in the event of bankruptcies.",

View File

@ -1,17 +1,17 @@
{
"assets": "Assets",
"assets-liabilities": "Assets & Liabilities",
"export": "Export {{dataType}}",
"liabilities": "Liabilities",
"no-pnl-history": "No PnL History",
"pnl-chart": "PnL Chart",
"pnl-history": "PnL History",
"tooltip-free-collateral": "The amount of capital you have to use for trades and loans. When your free collateral reaches $0 you won't be able to trade, borrow or withdraw",
"tooltip-leverage": "Total assets value divided by account equity value",
"tooltip-pnl": "The amount your account has profited or lost",
"tooltip-total-collateral": "Total value of collateral for trading and borrowing (including unsettled PnL)",
"tooltip-total-funding": "The sum of perp position funding earned and paid",
"tooltip-total-interest": "The value of interest earned (deposits) minus interest paid (borrows)",
"total-funding-earned": "Total Funding Earned",
"week-starting": "Week starting {{week}}"
"assets": "资产",
"export": "导出{{dataType}}",
"assets-liabilities": "资产和债务",
"liabilities": "债务",
"no-pnl-history": "无盈亏历史",
"pnl-chart": "盈亏图表",
"pnl-history": "盈亏历史",
"tooltip-free-collateral": "你可用于交易和借贷的余额。当你可用的质押品达到0元时你将不能交易、借贷或取款",
"tooltip-leverage": "总资价值除以账户余额",
"tooltip-pnl": "你帐户的盈亏",
"tooltip-total-collateral": "可用于交易和借贷的质押品(包括未结清的盈亏)",
"tooltip-total-funding": "赚取和支付的合约资金费总和",
"tooltip-total-interest": "你获取的利息(存款)减你付出的利息(借贷)",
"total-funding-earned": "总资金费",
"week-starting": "从{{week}}来算的一周"
}

View File

@ -1,46 +1,46 @@
{
"activity": "Activity",
"activity-feed": "Activity Feed",
"activity-type": "Activity Type",
"advanced-filters": "Advanced Filters",
"asset-liquidated": "Asset Liquidated",
"asset-returned": "Asset Returned",
"connect-activity": "Connect to view your account activity",
"counterparty": "Counterparty",
"credit": "Credit",
"debit": "Debit",
"deposit": "Deposit",
"deposits": "Deposits",
"execution-price": "Execution Price",
"filter-results": "Filter",
"liquidate_perp_base_position_or_positive_pnl": "Perp Liquidation",
"liquidate_token_with_token": "Spot Liquidation",
"liquidated": "Liquidated",
"liquidation": "Liquidation",
"liquidation-fee": "Liquidation Fee",
"liquidation-type": "Liquidation Type",
"liquidation-side": "Liquidation Side",
"liquidations": "Liquidations",
"liquidation-details": "Liquidation Details",
"liquidator": "Liquidator",
"net-price": "Net Price",
"net-price-desc": "The trade price inclusive of fees",
"no-activity": "No account activity",
"openbook_trade": "Spot Trade",
"perp-details": "Perp Trade Details",
"perps": "Perps",
"perp_trade": "Perp Trade",
"reset-filters": "Reset Filters",
"select-tokens": "Select Tokens",
"spot-trade": "Spot Trade",
"swap": "Swap",
"swaps": "Swaps",
"tooltip-fee": "Swap fees paid to other DEXs are not displayed",
"trades": "Trades",
"update": "Update",
"value-from": "Value From",
"value-to": "Value To",
"view-account": "View Account",
"withdraw": "Withdraw",
"withdrawals": "Withdrawals"
"activity": "活动",
"activity-feed": "活动历史",
"activity-type": "活动类别",
"advanced-filters": "高级筛选",
"asset-liquidated": "清算资产",
"asset-returned": "归还资产",
"connect-activity": "连接钱包来查看帐户",
"counterparty": "交易对方",
"credit": "贷方",
"debit": "借方",
"deposit": "存款",
"deposits": "存款",
"execution-price": "成交价格",
"filter-results": "筛选",
"liquidate_perp_base_position_or_positive_pnl": "合约清算",
"liquidate_token_with_token": "现货清算",
"liquidated": "被清算",
"liquidation": "清算",
"liquidation-details": "清算细节",
"liquidation-fee": "清算费用",
"liquidation-side": "清算方向",
"liquidation-type": "清算类别",
"liquidations": "清算",
"liquidator": "清算者",
"net-price": "净价",
"net-price-desc": "包含费用的交易价格",
"no-activity": "无帐户历史",
"openbook_trade": "现货交易",
"perp-details": "合约交易细节",
"perp_trade": "合约交易",
"perps": "永续合约",
"reset-filters": "重置筛选",
"select-tokens": "选择币种",
"spot-trade": "现货交易",
"swap": "换币",
"swaps": "换币",
"tooltip-fee": "缴给其他DEX的费用这里不显示",
"trades": "交易",
"update": "更新",
"value-from": "价值多于",
"value-to": "价值少于",
"view-account": "查看帐户",
"withdraw": "取款",
"withdrawals": "取款"
}

View File

@ -1,11 +1,11 @@
{
"assets-to-borrow": "Assets to Borrow",
"available-to-borrow": "Available to Borrow",
"borrowed-amount": "Borrowed Amount",
"connect-borrows": "Connect to see your borrows",
"current-borrow-value": "Current Borrow Value",
"liability-weight-desc": "Liability weight adds to the value of the liability in your account health calculation.",
"no-borrows": "No Borrows",
"tooltip-available": "The max amount you can borrow",
"your-borrows": "Your Borrows"
"assets-to-borrow": "可借资产",
"available-to-borrow": "可借余额",
"borrowed-amount": "借贷数量",
"connect-borrows": "连接而查看你的借贷",
"current-borrow-value": "借贷价值",
"liability-weight-desc": "计算帐户健康时债务权重会增加债务价值。",
"no-borrows": "无借贷",
"tooltip-available": "你可借的最多资产",
"your-borrows": "你的借贷"
}

View File

@ -1,19 +1,19 @@
{
"are-you-sure": "Are you sure? Closing your account will",
"before-you-continue": "Before you can continue",
"close-account": "Close Account",
"closing-account": "Closing your account...",
"close-all-borrows": "Repay all borrows",
"close-open-orders": "Close all open orders",
"close-perp-positions": "Close and settle all perp positons",
"closing-account-will": "Closing your Mango Account will:",
"delete-your-account": "Delete your Mango Account",
"error-deleting-account": "Error deleting your Mango Account",
"goodbye": "Until next time 👋",
"recover-x-sol": "Recover {{amount}} SOL (rent for your account)",
"settle-balances": "Settle all balances",
"transaction-confirmed": "Transaction Confirmed",
"withdraw-assets-worth": "Withdraw assets worth {{value}}",
"you-must": "Before you can close your account",
"account-closed": "Account Closed 👋"
"are-you-sure": "你确定吗? 关户将会",
"before-you-continue": "继续之前",
"close-account": "关户",
"closing-account": "正在关闭帐户...",
"close-all-borrows": "归还借贷",
"close-open-orders": "取消订单",
"close-perp-positions": "取消及结清所有合约持仓",
"closing-account-will": "关闭Mango帐户将会",
"delete-your-account": "删除您的Mango帐户",
"error-deleting-account": "删除Mango帐户出错",
"goodbye": "下次见👋",
"recover-x-sol": "恢复{{amount}} SOL (帐户押金)",
"settle-balances": "结清余额",
"transaction-confirmed": "交易成功",
"withdraw-assets-worth": "提取价值{{value}}的资产",
"you-must": "关户之前",
"account-closed": "帐户关闭了👋"
}

View File

@ -1,178 +1,180 @@
{
"404-heading": "This page was liquidated",
"404-description": "or, never existed...",
"accept-terms": "Accept Terms",
"accept-terms-desc": "By continuing, you accept the Mango",
"account": "Account",
"account-closed": "Account Closed 👋",
"account-balance": "Account Balance",
"account-name": "Account Name",
"account-name-desc": "Organize your accounts by giving them useful names",
"account-settings": "Account Settings",
"account-update-failed": "Failed to update account",
"account-update-success": "Account updated successfully",
"account-value": "Account Value",
"accounts": "Accounts",
"actions": "Actions",
"add-new-account": "Add New Account",
"agree-and-continue": "Agree and Continue",
"all": "All",
"amount": "Amount",
"amount-owed": "Amount Owed",
"asset-liability-weight": "Asset/Liability Weights",
"asset-liability-weight-desc": "Asset weight applies a haircut to the value of the collateral in your account health calculation. The lower the asset weight, the less the asset counts towards collateral. Liability weight does the opposite (adds to the value of the liability in your health calculation).",
"asset-weight": "Asset Weight",
"asset-weight-desc": "Asset weight applies a haircut to the value of the collateral in your account health calculation. The lower the asset weight, the less the asset counts towards collateral.",
"available": "Available",
"available-balance": "Available Balance",
"balance": "Balance",
"bal": "Bal",
"balances": "Balances",
"borrow": "Borrow",
"borrow-amount": "Borrow Amount",
"borrow-fee": "Borrow Fee",
"borrow-funds": "Borrow Funds",
"borrow-rate": "Borrow APR",
"buy": "Buy",
"cancel": "Cancel",
"chart-unavailable": "Chart Unavailable",
"clear-all": "Clear All",
"close": "Close",
"close-account": "Close Account",
"close-account-desc": "Are you sure? Closing your account is irreversible",
"closing-account": "Closing your account...",
"collateral-value": "Collateral Value",
"connect": "Connect",
"connect-balances": "Connect to view your balances",
"connect-helper": "Connect to get started",
"copy-address": "Copy Address",
"copy-address-success": "Copied address: {{pk}}",
"country-not-allowed": "Country {{country}} Not Allowed",
"country-not-allowed-tooltip": "You are using an open-source frontend facilitated by the Mango DAO. As such, it restricts access to certain regions out of an abundance of caution, due to regulatory uncertainty.",
"create-account": "Create Account",
"creating-account": "Creating Account...",
"cumulative-interest-value": "Cumulative Interest Earned",
"daily-volume": "24h Volume",
"date": "Date",
"date-from": "Date From",
"date-to": "Date To",
"delegate": "Delegate",
"delegate-account": "Delegate Account",
"delegate-account-info": "Account delegated to {{address}}",
"delegate-desc": "Delegate your Mango account to another wallet address",
"delegate-placeholder": "Enter a wallet address to delegate to",
"delete": "Delete",
"deposit": "Deposit",
"deposit-amount": "Deposit Amount",
"deposit-more-sol": "Your SOL wallet balance is too low. Add more to pay for transactions",
"deposit-rate": "Deposit APR",
"details": "Details",
"disconnect": "Disconnect",
"documentation": "Documentation",
"edit": "Edit",
"edit-account": "Edit Account Name",
"edit-profile-image": "Edit Profile Image",
"explorer": "Explorer",
"fee": "Fee",
"fees": "Fees",
"free-collateral": "Free Collateral",
"get-started": "Get Started",
"governance": "Governance",
"health": "Health",
"health-impact": "Health Impact",
"health-tooltip": "Projects the health of your account before you make a trade. The first value is your current account health and the second your projected account health.",
"history": "History",
"funding": "Funding",
"insufficient-sol": "Solana requires 0.04454 SOL rent to create a Mango Account. This will be returned if you close your account.",
"interest-earned": "Interest Earned",
"interest-earned-paid": "Interest Earned",
"leaderboard": "Leaderboard",
"learn": "Learn",
"leverage": "Leverage",
"liability-weight": "Liability Weight",
"liquidity": "Liquidity",
"loading": "Loading",
"loan-origination-fee": "Borrow Fee",
"loan-origination-fee-tooltip": "{{fee}} fee for opening a borrow.",
"404-description": "或者从来不存在...",
"404-heading": "此页被清算了",
"accept-terms": "接受条款",
"accept-terms-desc": "继续即表示您接受Mango",
"account": "帐户",
"account-balance": "帐户余额",
"account-closed": "关户成功👋",
"account-name": "帐户标签",
"account-name-desc": "以标签来整理帐户",
"account-settings": "帐户设定",
"account-update-failed": "更新帐户出错",
"account-update-success": "更新帐户成功",
"account-value": "帐户价值",
"accounts": "帐户",
"actions": "动作",
"add-new-account": "开户",
"agree-and-continue": "同意并继续",
"all": "全部",
"amount": "数量",
"amount-owed": "欠款",
"asset-liability-weight": "资产/债务权重",
"asset-liability-weight-desc": "资产权重在账户健康计算中对质押品价值进行扣减。资产权重越低,资产对质押品的影响越小。债务权重恰恰相反(在健康计算中增加债务价值)。",
"asset-weight": "资产权重",
"asset-weight-desc": "资产权重在账户健康计算中对质押品价值进行扣减。资产权重越低,资产对质押品的影响越小。",
"available": "可用",
"available-balance": "可用余额",
"bal": "余额",
"balance": "余额",
"balances": "余额",
"borrow": "借贷",
"borrow-amount": "借贷数量",
"borrow-fee": "借贷费用",
"borrow-funds": "进行借贷",
"borrow-rate": "借贷APR",
"buy": "买",
"cancel": "取消",
"chart-unavailable": "无法显示图表",
"clear-all": "清除全部",
"close": "关闭",
"close-account": "关户",
"close-account-desc": "你确定吗? 关户就无法恢复",
"closing-account": "正在关闭帐户...",
"collateral-value": "质押品价值",
"confirm": "Confirm",
"connect": "连接",
"connect-balances": "连接而查看资产余额",
"connect-helper": "连接来开始",
"copy-address": "拷贝地址",
"copy-address-success": "地址被拷贝: {{pk}}",
"country-not-allowed": "你的国家{{country}}不允许使用Mango",
"country-not-allowed-tooltip": "你正在使用MangoDAO提供的开源介面。由于监管的不确定性因此处于谋些地区的人的行动会受到限制。",
"create-account": "开户",
"creating-account": "正在开户...",
"cumulative-interest-value": "总累积利息",
"daily-volume": "24小时交易量",
"date": "日期",
"date-from": "从",
"date-to": "至",
"delegate": "委托",
"delegate-account": "委托帐户",
"delegate-account-info": "帐户委托给 {{address}}",
"delegate-desc": "以Mango帐户委托给别的钱包地址",
"delegate-placeholder": "输入受委钱包地执",
"delete": "删除",
"deposit": "存款",
"deposit-amount": "存款数量",
"deposit-more-sol": "您的SOL钱包余额太低。请多存入以支付交易",
"deposit-rate": "存款APR",
"details": "细节",
"disconnect": "断开连接",
"documentation": "文档",
"edit": "编辑",
"edit-account": "编辑帐户标签",
"edit-profile-image": "切换头像",
"explorer": "浏览器",
"fee": "费用",
"fees": "费用",
"free-collateral": "可用的质押品",
"funding": "资金费",
"get-started": "开始",
"governance": "治理",
"health": "健康度",
"health-impact": "健康影响",
"health-tooltip": "此在您进行交易之前预测您账户的健康状况。第一个值是您当前的帐户健康状况,第二个值是您的预测帐户健康状况。",
"history": "纪录",
"insufficient-sol": "Solana需要0.04454 SOL租金才能创建Mango账户。您关闭帐户时租金将被退还。",
"interest-earned": "获取利息",
"interest-earned-paid": "获取利息",
"leaderboard": "排行榜",
"learn": "学",
"leverage": "杠杆",
"liability-weight": "债务权重",
"liquidity": "流动性",
"loading": "加载中",
"loan-origination-fee": "借贷费用",
"loan-origination-fee-tooltip": "执行借贷费用是{{fee}}。",
"mango": "Mango",
"mango-stats": "Mango Stats",
"market": "Market",
"max": "Max",
"max-borrow": "Max Borrow",
"more": "More",
"new": "New",
"new-account": "New Account",
"new-account-failed": "Failed to create account",
"new-account-success": "Your new account is ready 😎",
"new-version": "New version available",
"mango-stats": "Mango统计",
"market": "市场",
"max": "最多",
"max-borrow": "最多借贷",
"more": "更多",
"new": "",
"new-account": "开户",
"new-account-failed": "开户出错",
"new-account-success": "您的新帐户准备好了😎",
"new-version": "新版本出来了",
"no": "No",
"optional": "Optional",
"outstanding-balance": "Outstanding Balance",
"overview": "Overview",
"perp": "Perp",
"perp-markets": "Perp Markets",
"pnl": "PnL",
"price": "Price",
"quantity": "Quantity",
"rate": "Rate (APR)",
"rates": "Rates (APR)",
"remove": "Remove",
"remove-delegate": "Remove Delegate",
"repay": "Repay",
"repay-deposit": "Repay & Deposit",
"repay-borrow": "Repay Borrow",
"repayment-amount": "Repayment Amount",
"rolling-change": "24h Change",
"save": "Save",
"select": "Select",
"select-borrow-token": "Select Borrow Token",
"select-deposit-token": "Select Deposit Token",
"select-repay-token": "Select Repay Token",
"select-token": "Select Token",
"select-withdraw-token": "Select Withdraw Token",
"sell": "Sell",
"settings": "Settings",
"show-more": "Show More",
"show-zero-balances": "Show Zero Balances",
"optional": "可选",
"outstanding-balance": "未结余额",
"overview": "摘要",
"perp": "合约",
"perp-markets": "合约市场",
"pnl": "盈亏",
"price": "价格",
"quantity": "数量",
"rate": "利率(APR)",
"rates": "利率(APR)",
"remove": "删除",
"remove-delegate": "铲除委托",
"repay": "归还",
"repay-borrow": "还贷",
"repay-deposit": "归还及存入",
"repayment-amount": "还贷额",
"rolling-change": "24小时变化",
"save": "",
"select": "选择",
"select-borrow-token": "选借入币种",
"select-deposit-token": "选存入币种",
"select-repay-token": "选环贷币种",
"select-token": "选币种",
"select-withdraw-token": "选提出币种",
"sell": "",
"settings": "设置",
"show-more": "显示更多",
"show-zero-balances": "显示零余额",
"solana-tps": "Solana TPS",
"spot": "Spot",
"spot-markets": "Spot Markets",
"stats": "Stats",
"swap": "Swap",
"terms-of-use": "Terms of Use",
"time": "Time",
"today": "Today",
"token": "Token",
"tokens": "Tokens",
"token-collateral-multiplier": "{{token}} Collateral Multiplier",
"tooltip-borrow-fee": "The fee for originating a loan.",
"tooltip-borrow-rate": "The variable interest rate you'll pay on your borrowed balance",
"tooltip-collateral-value": "The USD amount you can trade or borrow against",
"total": "Total",
"total-borrows": "Total Borrows",
"total-borrow-value": "Total Borrow Value",
"total-collateral": "Total Collateral",
"total-deposits": "Total Deposits",
"total-deposit-value": "Total Deposit Value",
"total-interest-earned": "Total Interest Earned",
"trade": "Trade",
"trade-history": "Trade History",
"transaction": "Transaction",
"unavailable": "Unavailable",
"unowned-helper": "Currently viewing account {{accountPk}}",
"update": "Update",
"update-delegate": "Update Delegate",
"updating-account-name": "Updating Account Name...",
"utilization": "Utilization",
"value": "Value",
"view-transaction": "View Transaction",
"wallet-address": "Wallet Address",
"wallet-balance": "Wallet Balance",
"wallet-disconnected": "Disconnected from wallet",
"withdraw": "Withdraw",
"withdraw-amount": "Withdraw Amount",
"list-token": "List Token",
"vote": "Vote",
"yes": "Yes"
"spot": "现货",
"spot-markets": "现货市场",
"stats": "统计",
"swap": "换币",
"terms-of-use": "使用条款",
"time": "时间",
"today": "今天",
"token": "币种",
"token-collateral-multiplier": "{{token}}质押品乘数",
"tokens": "币种",
"tooltip-borrow-fee": "借贷费用。",
"tooltip-borrow-rate": "您将为借入余额支付的可变利率",
"tooltip-collateral-value": "您可以交易或借入的美元金额",
"total": "总计",
"total-borrow-value": "总借贷价值",
"total-borrows": "总借贷",
"total-collateral": "总质押品",
"total-deposit-value": "总存款价值",
"total-deposits": "总存款",
"total-interest-earned": "总获取利息",
"trade": "交易",
"trade-history": "交易纪录",
"transaction": "交易",
"unavailable": "不可用",
"unowned-helper": "目前查看帐户 {{accountPk}}",
"update": "更新",
"update-delegate": "改委托帐户",
"updating-account-name": "正载改帐户标签...",
"utilization": "利用率",
"value": "价值",
"view-transaction": "查看交易",
"wallet": "Wallet",
"wallet-address": "钱包地址",
"wallet-balance": "钱包余额",
"wallet-disconnected": "已断开钱包连接",
"withdraw": "取款",
"withdraw-amount": "取款额",
"list-token": "列表代币",
"vote": "投票",
"yes": "是"
}

View File

@ -2,6 +2,8 @@
"list-token": "List Token",
"cancel": "Cancel",
"connect-wallet": "Connect Wallet",
"delegate": "Delegate",
"none": "None",
"on-boarding-title": "Looks like currently connected wallet doesn't have any MNGO deposited inside realms",
"on-boarding-description": "Before you continue. Deposit at least {{amount}} MNGO into",
"on-boarding-deposit-info": "Your MNGO will be locked for the duration of the proposal.",
@ -70,5 +72,7 @@
"your-votes": "Your Votes:",
"yes": "Yes",
"no": "No",
"current-vote": "Current Vote:"
"current-vote": "Current Vote:",
"use-own-wallet": "Use own wallet",
"select-delegate": "Select delegate"
}

View File

@ -4,6 +4,6 @@
"empty-state-title": "Nothing to see here",
"notifications": "Notifications",
"sign-message": "Sign Message",
"unauth-desc": "You need to verify your wallet to start receiving notifications.",
"unauth-desc": "Sign with your wallet to start receiving notifications",
"unauth-title": "Notifications Inbox"
}

View File

@ -1,58 +1,58 @@
{
"account-dashboard": "Account Dashboard",
"account-dashboard-desc": "Here you'll find the important information related to your account.",
"account-value": "Account Value",
"account-value-desc": "The value of your assets (deposits) minus the value of your liabilities (borrows).",
"free-collateral": "Free Collateral",
"free-collateral-desc": "The amount of capital you have to trade or borrow against. When your free collateral reaches $0 you won't be able to make withdrawals.",
"health": "Health",
"health-desc": "If your account health reaches 0% your account will be liquidated. You can increase the health of your account by making a deposit.",
"account-summary": "Account Summary",
"account-summary-desc": "Check your key account information from any screen in the app.",
"health-impact": "Health Impact",
"health-impact-desc": "Projects the health of your account before you make a swap. The first value is your current account health and the second, your projected account health.",
"interest-earned": "Interest Earned",
"interest-earned-desc": "The sum of interest earned and interest paid for each token.",
"ioc": "Immediate or Cancel (IoC)",
"ioc-desc": "An order condition that attempts to execute all or part of an order immediately and then cancels any unfilled portion.",
"leverage": "Leverage",
"leverage-desc": "The total size of your positions divided by your total collateral.",
"margin": "Margin",
"margin-desc": "When margin is on you can trade with more size than your token balance. Using margin increases your risk of loss. If you're not an experienced trader, use it with caution.",
"market-selector": "Market Selector",
"market-selector-desc": "Chose the market you want to trade.",
"oracle-price": "Oracle Price",
"oracle-price-desc": "The oracle price uses an average of price data from many sources. It's used to avoid price manipulation which could lead to liquidations.",
"orderbook-grouping": "Orderbook Grouping",
"orderbook-grouping-desc": "Adjust the price intervals to change how orders are grouped. Small intervals will show more small orders in the book",
"pay-token": "Pay Token",
"pay-token-desc": "Select the token you want to swap from (pay/sell). If you have margin switched on and your size is above your token balance a loan will be opened to cover the shortfall. Check the borrow rate before making a margin swap.",
"pnl": "PnL (Profit and Loss)",
"pnl-desc": "The amount your account has made or lost.",
"account-dashboard": "帐户概要",
"account-dashboard-desc": "在这里可以看到关于帐户的重要资料。",
"account-value": "帐户价值",
"account-value-desc": "你的资产价值(存款)减去你的债务价值(借贷)。",
"free-collateral": "可用的质押品",
"free-collateral-desc": "您可以用于交易或借入的资本金额。当您的可用质押品达到零时,您将无法取款。",
"health": "健康",
"health-desc": "若您的账户健康度达到零,您的账户将被清算。您可以存款而提高账户的健康度。",
"account-summary": "帐户概要",
"account-summary-desc": "从APP的任何页面检查您的主要帐户信息。",
"health-impact": "健康影响",
"health-impact-desc": "此在您进行换币之前预测您账户的健康状况。第一个值是您当前的帐户健康状况,第二个值是您的预测帐户健康状况。",
"interest-earned": "获取利息",
"interest-earned-desc": "每个币种赚取及支付的利息总和。",
"ioc": "立刻或取消IoC",
"ioc-desc": "尝试立刻执行全部或部分订单然后取消任何未执行部分的订单条件。",
"leverage": "杠杆",
"leverage-desc": "您持仓的总价值除以您的质押品总价值。",
"margin": "保证金",
"margin-desc": "当保证金开启时,您能使用比您的帐户余额更大的规模进行交易。利用保证金会增加您的损失风险。若您的交易经验不多,请谨慎使用。",
"market-selector": "市场选择器",
"market-selector-desc": "选择您想交易的市场。",
"oracle-price": "预言机价格",
"oracle-price-desc": "预言机价格是来自许多来源的价格数据的平均。它用于避免可能导致清算的市场操纵。",
"orderbook-grouping": "挂单薄分组",
"orderbook-grouping-desc": "调整价格区间以更改订单的分组方式。小间隔会在挂单簿中显示更多的小单",
"pay-token": "支付币种",
"pay-token-desc": "选择您要换的币种(支付/出售)。若您开启了保证金功能并且支付数量多于您的币种余额,将借贷以弥补差额。在进行杠杆换币之前检查借贷利率。",
"pnl": "盈亏",
"pnl-desc": "您的账户赚取或损失的金额。",
"post-only": "Post Only",
"post-only-desc": "An order condition that will only allow your order to enter the orderbook as a maker order. If the condition can't be met the order will be cancelled.",
"profile-menu": "Profile Menu",
"profile-menu-desc": "If you haven't chosen a profile name yet, you'll see your assigned one here. You can edit it and change your profile image from this menu.",
"rates": "Rates",
"rates-desc": "The interest rates (per year) for depositing (green/left) and borrowing (red/right).",
"receive-token": "Receive Token",
"receive-token-desc": "The token you'll receive in your Mango Account after making a swap. You can think of this token as the one you're buying/longing.",
"recent-trades": "Recent Trades",
"recent-trades-desc": "Shows the most recent trades for a market across all accounts.",
"spread": "Spread",
"spread-desc": "The difference between the prices quoted for an immediate sell (ask) and an immediate buy (bid). Or, in other words, the difference between the lowest sell price and the highest buy price.",
"swap": "We've Juiced Swap",
"swap-desc": "The swap you know and love + leverage. Swap lets you trade tokens on their relative strength. Let's say your thesis is BTC will see diminishing returns relative to SOL. You can sell BTC and buy SOL. Now you are long SOL/BTC",
"swap-settings": "Swap Settings",
"swap-settings-desc": "Edit your slippage settings and toggle margin on and off. When margin is off your swaps will be limited by your balance for each token.",
"toggle-orderbook": "Toggle Orderbook",
"toggle-orderbook-desc": "Use these buttons if you only want to see one side of the orderbook. Looking to bid/buy? Toggle off the buy orders to only see the sells and vice versa.",
"total-interest-earned": "Total Interest Earned",
"total-interest-earned-desc": "The value of interest earned (deposits) minus interest paid (borrows).",
"trade": "Trade 100s of Tokens...",
"trade-desc": "A refined interface without listing limits. The tokens you want to trade are now on Mango and no longer only quoted in USDC.",
"unsettled-balance": "Unsettled Balance",
"unsettled-balance-desc": "When a limit order is filled, the funds are placed in your unsettled balances. When you have an unsettled balance you'll see a 'Settle All' button above this table. Use it to move the funds to your account balance.",
"your-accounts": "Your Accounts",
"your-accounts-desc": "Switch between accounts and create new ones. Use multiple accounts to trade isolated margin and protect your capital from liquidation."
"post-only-desc": "一个订单条件,只允许您作为挂单者进入挂单簿。如果不能满足条件,订单将被取消。",
"profile-menu": "个人档案",
"profile-menu-desc": "若您还没选择个人档案名称您会在此处看到Mango创造给您的名称。您可以从此页面编辑它并更改您的个人头像。",
"rates": "利率",
"rates-desc": "存款(绿色/左)和借款(红色/右)的利率(每年)。",
"receive-token": "获取币种",
"receive-token-desc": "换币后您将在Mango账户中收到的币种。您可以将此币种视为您正在购买/做多的币种。",
"recent-trades": "最近交易",
"recent-trades-desc": "显示一个市场所有账户的最近交易。",
"spread": "差价",
"spread-desc": "即时卖出和即时买入的差价。换句话说,最低卖出价和最高买入价之间的差额。",
"swap": "新换币版本",
"swap-desc": "你已熟悉的换币加杠杆。换币功能让您可以根据币种的相对强度进行币种交易。举个例子您认为BTC相对于SOL的价值将降低您可以出售BTC并购买SOL。您就做多SOL/BTC",
"swap-settings": "换币设定",
"swap-settings-desc": "编辑您的滑点设置并开启和关闭保证金功能。保证金功能关闭时,换币将受到每个代币余额的限制。",
"toggle-orderbook": "切换挂单薄",
"toggle-orderbook-desc": "若您只想看到挂单簿的一侧,请用这些按钮。想要出价/购买?关闭买单以仅查看卖单,反之亦然。",
"total-interest-earned": "总利息",
"total-interest-earned-desc": "赚取的利息(存款)减去支付的利息(借款)的价值。",
"trade": "买卖数百个币种...",
"trade-desc": "没有限制的交易界面。您想要买卖的币种现在在Mango上不再仅以USDC报价。",
"unsettled-balance": "未结算余额",
"unsettled-balance-desc": "执行限价单后,资金将存入您的未结算余额中。当您有未结算余额时,您会在该表格上方看到“全部结算”按钮。用它来将资金转移到您的账户余额。",
"your-accounts": "您的帐户",
"your-accounts-desc": "在帐户之间切换并创建新帐户。使用多个账户进行逐仓保证金交易,保护您的资金免遭清算。"
}

View File

@ -1,19 +1,19 @@
{
"bullet-1": "Swap any token pair with margin.",
"bullet-2": "Trade spot & perpetuals up to 10x leverage.",
"bullet-3": "Earn interest on all deposits.",
"bullet-4": "Borrow any listed token.",
"choose-wallet": "Choose Wallet",
"connect-wallet": "Connect Wallet",
"create-account": "Create Your Account",
"create-account-desc": "You need a Mango Account to get started",
"fund-account": "Fund Your Account",
"intro-desc": "A magical new way to interact with DeFi. Groundbreaking safety features designed to keep your funds secure. The easiest way to margin trade any token pair. All powered by flashloans.",
"intro-heading": "Safer. Smarter. Faster.",
"lets-go": "Let's Go",
"profile-desc": "Add an NFT profile image and edit your assigned name",
"save-finish": "Save and Finish",
"skip": "Skip for now",
"skip-finish": "Skip and Finish",
"your-profile": "Your Profile"
"bullet-1": "以保证金来交换任何币种对",
"bullet-2": "以高达十倍的杠杆率买卖现货和永续合约。",
"bullet-3": "所有存款可以赚取利息。",
"bullet-4": "借入任何被列出币种",
"choose-wallet": "选择钱包",
"connect-wallet": "连接钱包",
"create-account": "开户",
"create-account-desc": "开始之前您必须开Mango帐户",
"fund-account": "为您的账户注资",
"intro-desc": "一种与DeFi交互的神奇新方式。突破性的安全功能确保您的资金安全。保证金交易任何币种对的最简单方法。全部由闪电贷支持。",
"intro-heading": "更安全。更聪明。更迅速。",
"lets-go": "开始喽",
"profile-desc": "加个NFT个人档案头像并编辑您的帐户标签",
"save-finish": "保存并完成",
"skip": "暂时跳过",
"skip-finish": "跳过并完成",
"your-profile": "您的个人档案"
}

View File

@ -10,7 +10,7 @@
"follow": "追踪",
"following": "追踪中",
"invalid-characters": "限制于字母、数字和空格",
"length-error": "Names must be less than 20 characters",
"length-error": "标签必须少于20个字符",
"market-maker": "做市商",
"no-followers": "无追踪者",
"no-followers-desc": "以隐身模式交易😎",
@ -19,7 +19,7 @@
"no-nfts": "😞 未找到NFT...",
"no-profile-exists": "此帐户不存在...",
"profile": "帐户",
"profile-api-error": "Profile update is unavailable. Please try again later",
"profile-api-error": "无法更新帐户。请稍后再尝试",
"profile-fetch-fail": "查帐户细节出错",
"profile-name": "帐户标签",
"profile-pic-failure": "设置头像失败",
@ -32,13 +32,13 @@
"save-profile": "保存帐户",
"set-profile-pic": "设置头像",
"swing-trader": "摆动交易者",
"total-pnl": "组合总盈亏",
"total-value": "组合总价值",
"total-pnl": "总合盈亏",
"total-value": "总合价值",
"trader": "交易者",
"trader-category": "交易模式",
"unfollow": "取消追踪",
"uniqueness-api-fail": "Failed to check profile name uniqueness",
"uniqueness-fail": "Profile name is taken. Try another one",
"uniqueness-api-fail": "无法检查帐户标签的唯一性",
"uniqueness-fail": "帐户标签已被使用。试试另一个",
"yolo": "YOLO",
"your-profile": "您的帐户"
}

View File

@ -1,14 +1,14 @@
{
"mango-account": "Account Address",
"mango-account-name": "Account Name",
"no-results": "No Results",
"no-results-desc": "Try another search. Searches are case-sensitive",
"profile-name": "Profile Name",
"results": "Results",
"search": "Search",
"search-accounts": "Search Accounts",
"search-by": "Search By",
"search-for": "Search For",
"search-failed": "Something went wrong. Try again later",
"wallet-pk": "Wallet Address"
"mango-account": "帐户地址",
"mango-account-name": "帐户标签",
"no-results": "无结果",
"no-results-desc": "尝试其他搜寻。搜寻区分大小写",
"profile-name": "个人档案标签",
"results": "结果",
"search": "搜寻",
"search-accounts": "搜寻帐户",
"search-by": "搜寻方式",
"search-for": "搜寻",
"search-failed": "出错了。稍后再试。",
"wallet-pk": "钱包地址"
}

View File

@ -1,57 +1,60 @@
{
"animations": "Animations",
"avocado": "Avocado",
"banana": "Banana",
"blueberry": "Blueberry",
"bottom-left": "Bottom-Left",
"bottom-right": "Bottom-Right",
"buttons": "Buttons",
"chart-left": "Chart Left",
"chart-middle-ob-left": "Chart Middle, Orderbook Left",
"chart-middle-ob-right": "Chart Middle, Orderbook Right",
"chart-right": "Chart Right",
"animations": "动画",
"avocado": "酪梨",
"banana": "香蕉",
"blueberry": "蓝莓",
"bottom-left": "左下",
"bottom-right": "右下",
"buttons": "按钮",
"chart-left": "图表左",
"chart-middle-ob-left": "图表中,挂单薄左",
"chart-middle-ob-right": "图表中,挂单薄右",
"chart-right": "图表右",
"chinese": "简体中文",
"chinese-traditional": "繁體中文",
"custom": "Custom",
"dark": "Dark",
"display": "Display",
"connect-notifications": "Connect to update your notification settings",
"custom": "自定",
"dark": "暗",
"display": "显示",
"english": "English",
"high-contrast": "High Contrast",
"language": "Language",
"light": "Light",
"lychee": "Lychee",
"mango": "Mango",
"mango-classic": "Mango Classic",
"medium": "Medium",
"notification-position": "Notification Position",
"number-scroll": "Number Scroll",
"olive": "Olive",
"orderbook-flash": "Orderbook Flash",
"preferred-explorer": "Preferred Explorer",
"recent-trades": "Recent Trades",
"high-contrast": "高对比度",
"language": "语言",
"light": "光",
"limit-order-filled": "限价单成交",
"lychee": "荔枝",
"mango": "芒果",
"mango-classic": "芒果经典",
"medium": "中",
"notification-position": "通知位置",
"notifications": "通知",
"number-scroll": "数字滑动",
"olive": "橄榄",
"orderbook-flash": "挂单薄闪光",
"preferred-explorer": "首选探索器",
"recent-trades": "最近交易",
"rpc": "RPC",
"rpc-provider": "RPC Provider",
"rpc-url": "Enter RPC URL",
"rpc-provider": "RPC提供者",
"rpc-url": "输入RPC URL",
"russian": "Русский",
"save": "Save",
"slider": "Slider",
"save": "存",
"sign-to-notifications": "登录通知中心以更改设置",
"slider": "滑块",
"solana-beach": "Solana Beach",
"solana-explorer": "Solana Explorer",
"solanafm": "SolanaFM",
"solscan": "Solscan",
"sounds": "Sounds",
"sounds": "声响",
"spanish": "Español",
"swap-success": "Swap/Trade Success",
"swap-trade-size-selector": "Swap/Trade Size Selector",
"theme": "Theme",
"top-left": "Top-Left",
"top-right": "Top-Right",
"trade-layout": "Trade Layout",
"transaction-fail": "Transaction Fail",
"transaction-success": "Transaction Success",
"trade-chart": "Trade Chart",
"swap-success": "换币/交易成功",
"swap-trade-size-selector": "换币/交易大小选择器",
"theme": "模式",
"top-left": "左上",
"top-right": "右上",
"trade-chart": "交易图表",
"trade-layout": "交易布局",
"trading-view": "Trading View",
"notifications": "Notifications",
"limit-order-filled": "Limit order filled",
"sign-to-notifications": "Sign to notifications center to change settings"
"transaction-fail": "交易失败",
"transaction-success": "交易成功",
"orderbook-bandwidth-saving": "Orderbook Bandwidth Saving",
"tooltip-orderbook-bandwidth-saving": "Use an off-chain service for Orderbook updates to decrease data usage by ~1000x. Disable this if open orders are not highlighted in the book correctly."
}

View File

@ -1,6 +1,6 @@
{
"base-liquidation-fee": "Base Liquidation Fee",
"perp-details": "{{market}} Stats",
"perp-details": "{{market}}统计",
"pnl-liquidation-fee": "Positive PnL Liquidation Fee",
"settle-pnl-factor": "Settle PnL Factor",
"tooltip-base-liquidation-fee": "The liqee pays this liquidation fee when a liquidator has to take over a perp base position.",

View File

@ -1,39 +1,40 @@
{
"confirm-swap": "Confirm Swap",
"connect-swap": "Connect to view your swap history",
"est-liq-price": "Est. Liq Price",
"fees-paid-to": "Fees Paid to {{route}}",
"health-impact": "Health Impact",
"hide-fees": "Hide Fees",
"hide-swap-history": "Hide Swap History",
"input-reduce-only-warning": "{{symbol}} is in reduce only mode. You can swap your balance to another token",
"insufficient-balance": "Insufficient {{symbol}} Balance",
"insufficient-collateral": "Insufficient Collateral",
"margin-swap": "Margin Swap",
"max-slippage": "Max Slippage",
"maximum-cost": "Maximum Cost",
"minimum-received": "Minimum Received",
"no-history": "No swap history",
"output-reduce-only-warning": "{{symbol}} is in reduce only mode. You can swap to close borrows only",
"paid": "Paid",
"pay": "You Pay",
"preset": "Preset",
"price-impact": "Price Impact",
"rate": "Rate",
"receive": "You Receive",
"received": "Received",
"review-swap": "Review Swap",
"show-fees": "Show Fees",
"show-swap-history": "Show Swap History Prices shown are from CoinGecko and may not match your swap price",
"slippage": "Slippage",
"swap-history": "Swap History",
"swap-into-1": "Borrow against your collateral and swap with up to 5x leverage.",
"swap-into-2": "Swap your Mango assets via the top DEXs on Solana and get the best possible price.",
"swap-into-3": "Use the slider to set your swap size. Margin can be switched off in swap settings.",
"swap-route": "Swap Route",
"tooltip-borrow-balance": "You'll use your {{balance}} {{token}} balance and borrow {{borrowAmount}} {{token}} to execute this swap. The current {{token}} variable borrow rate is {{rate}}%",
"tooltip-borrow-no-balance": "You'll borrow {{borrowAmount}} {{token}} to execute this swap. The current {{token}} variable borrow rate is {{rate}}%",
"tooltip-max-slippage": "If price slips beyond your maximum slippage your swap will not be executed",
"use-margin": "Allow Margin",
"no-swap-found": "No swap found"
"confirm-swap": "确定换币",
"connect-swap": "连接来查换币纪录",
"est-liq-price": "预计清算价格",
"fees-paid-to": "缴给{{route}}的费用",
"health-impact": "健康影响",
"hide-fees": "隐藏费用",
"hide-swap-history": "隐藏换币纪录",
"input-reduce-only-warning": "{{symbol}}处于仅减少模式。您可以将余额换成另一个币种",
"insufficient-balance": "{{symbol}}余额不够",
"insufficient-collateral": "质押品不够",
"margin-swap": "杠杆换币",
"max-slippage": "最多下滑",
"maximum-cost": "最大成本",
"minimum-received": "最小获取",
"no-history": "无换币纪录",
"no-swap-found": "查不到换币",
"output-reduce-only-warning": "{{symbol}}处于仅减少模式。换币限于归还借贷",
"paid": "付出",
"pay": "你将付出",
"preset": "预设",
"price-impact": "价格影响",
"rate": "汇率",
"receive": "你将获取",
"received": "获取",
"review-swap": "检视换币",
"route-info": "Route Info",
"show-fees": "显示费用",
"show-swap-history": "显示换币纪录价格来自CoinGecko而也许跟换币价格不同",
"slippage": "下滑",
"swap-history": "换币纪录",
"swap-into-1": "以你的质押品来借贷并进行高达5倍杠杆换币。",
"swap-into-2": "通过 Solana 上的顶级 DEX 交换您的 Mango 资产,并获得最优惠的价格。",
"swap-into-3": "使用滑块设置交换大小。在换币设置中可以关闭杠杆功能。",
"swap-route": "换币路线",
"tooltip-borrow-balance": "您将使用您的 {{balance}} {{token}} 余额并借入 {{borrowAmount}} {{token}} 来执行此交换。当前的 {{token}} 可变借贷利率为 {{rate}}%",
"tooltip-borrow-no-balance": "您将借入 {{borrowAmount}} {{token}} 来执行此交换。当前的 {{token}} 可变借贷利率为 {{rate}}%",
"tooltip-max-slippage": "如果价格下滑超过您的最大滑点,换币交易将不会被执行",
"use-margin": "允许杠杆"
}

Some files were not shown because too many files have changed in this diff Show More