Merge branch 'main' into update-translations
This commit is contained in:
commit
4e88ef68f0
|
@ -7,11 +7,7 @@ import {
|
|||
ZERO_I80F48,
|
||||
} from '@blockworks-foundation/mango-client'
|
||||
import { useCallback, useState } from 'react'
|
||||
import {
|
||||
ExclamationIcon,
|
||||
ExternalLinkIcon,
|
||||
HeartIcon,
|
||||
} from '@heroicons/react/solid'
|
||||
import { ExclamationIcon, ExternalLinkIcon } from '@heroicons/react/solid'
|
||||
import { BellIcon } from '@heroicons/react/outline'
|
||||
import useMangoStore, { MNGO_INDEX } from '../stores/useMangoStore'
|
||||
import { abbreviateAddress, formatUsdValue, usdFormatter } from '../utils'
|
||||
|
@ -31,6 +27,7 @@ import Loading from './Loading'
|
|||
import CreateAlertModal from './CreateAlertModal'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import { useRouter } from 'next/router'
|
||||
import HealthHeart from './HealthHeart'
|
||||
|
||||
const I80F48_100 = I80F48.fromString('100')
|
||||
|
||||
|
@ -131,11 +128,6 @@ export default function AccountInfo() {
|
|||
? mangoAccount.getHealthRatio(mangoGroup, mangoCache, 'Maint')
|
||||
: I80F48_100
|
||||
|
||||
const initHealthRatio =
|
||||
mangoAccount && mangoGroup && mangoCache
|
||||
? mangoAccount.getHealthRatio(mangoGroup, mangoCache, 'Init')
|
||||
: I80F48_100
|
||||
|
||||
const maintHealth =
|
||||
mangoAccount && mangoGroup && mangoCache
|
||||
? mangoAccount.getHealth(mangoGroup, mangoCache, 'Maint')
|
||||
|
@ -193,7 +185,7 @@ export default function AccountInfo() {
|
|||
) : null}
|
||||
<div>
|
||||
{mangoAccount ? (
|
||||
<div className="-mt-2 flex justify-center text-xs">
|
||||
<div className="-mt-2 mb-2 flex justify-center text-xs">
|
||||
<a
|
||||
className="flex items-center text-th-fgd-4 hover:text-th-primary"
|
||||
href={`https://explorer.solana.com/address/${mangoAccount?.publicKey}`}
|
||||
|
@ -208,12 +200,41 @@ export default function AccountInfo() {
|
|||
<div>
|
||||
<div className="flex justify-between pb-2">
|
||||
<div className="font-normal leading-4 text-th-fgd-3">
|
||||
{t('equity')}
|
||||
{t('value')}
|
||||
</div>
|
||||
<div className="text-th-fgd-1">
|
||||
{initialLoad ? <DataLoader /> : formatUsdValue(+equity)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between pb-2">
|
||||
<Tooltip
|
||||
content={
|
||||
<div>
|
||||
{t('tooltip-account-liquidated')}{' '}
|
||||
<a
|
||||
href="https://docs.mango.markets/mango/health-overview"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t('learn-more')}
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="default-transition cursor-help border-b border-dashed border-th-fgd-3 border-opacity-20 font-normal leading-4 text-th-fgd-3 hover:border-th-bkg-2">
|
||||
{t('health')}
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div className="flex items-center space-x-2">
|
||||
<HealthHeart size={24} health={Number(maintHealthRatio)} />
|
||||
<div className="text-th-fgd-1">
|
||||
{maintHealthRatio.gt(I80F48_100)
|
||||
? '>100'
|
||||
: maintHealthRatio.toFixed(2)}
|
||||
%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between pb-2">
|
||||
<div className="font-normal leading-4 text-th-fgd-3">
|
||||
{t('leverage')}
|
||||
|
@ -326,54 +347,6 @@ export default function AccountInfo() {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-2 flex items-center rounded border border-th-bkg-4 p-2.5 sm:my-1">
|
||||
<div className="flex items-center pr-2">
|
||||
<HeartIcon
|
||||
className="mr-1.5 h-5 w-5 text-th-primary"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>
|
||||
<Tooltip
|
||||
content={
|
||||
<div>
|
||||
{t('tooltip-account-liquidated')}{' '}
|
||||
<a
|
||||
href="https://docs.mango.markets/mango/health-overview"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t('learn-more')}
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="default-transition cursor-help border-b border-dashed border-th-fgd-3 border-opacity-20 font-normal leading-4 text-th-fgd-3 hover:border-th-bkg-2">
|
||||
{t('health')}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex h-1.5 flex-grow rounded bg-th-bkg-4">
|
||||
<div
|
||||
style={{
|
||||
width: `${maintHealthRatio}%`,
|
||||
}}
|
||||
className={`flex rounded ${
|
||||
maintHealthRatio.toNumber() > 30
|
||||
? 'bg-th-green'
|
||||
: initHealthRatio.toNumber() > 0
|
||||
? 'bg-th-orange'
|
||||
: 'bg-th-red'
|
||||
}`}
|
||||
></div>
|
||||
</div>
|
||||
<div className="pl-2 text-right">
|
||||
{maintHealthRatio.gt(I80F48_100)
|
||||
? '>100'
|
||||
: maintHealthRatio.toFixed(2)}
|
||||
%
|
||||
</div>
|
||||
</div>
|
||||
{mangoAccount && mangoAccount.beingLiquidated ? (
|
||||
<div className="flex items-center justify-center pt-0.5 text-xs">
|
||||
<ExclamationIcon className="mr-1.5 h-5 w-5 flex-shrink-0 text-th-red" />
|
||||
|
|
|
@ -54,6 +54,11 @@ const CloseAccountModal: FunctionComponent<CloseAccountModalProps> = ({
|
|||
const openOrders = useMangoStore((s) => s.selectedMangoAccount.openOrders)
|
||||
const setMangoStore = useMangoStore((s) => s.set)
|
||||
const activeAlerts = useMangoStore((s) => s.alerts.activeAlerts)
|
||||
const spotBalances = useMangoStore((s) => s.selectedMangoAccount.spotBalances)
|
||||
|
||||
const unsettledBalances = spotBalances.filter(
|
||||
(bal) => bal.unsettled && bal.unsettled > 0
|
||||
)
|
||||
|
||||
const fetchTotalAccountSOL = useCallback(async () => {
|
||||
if (!mangoAccount) {
|
||||
|
@ -162,7 +167,10 @@ const CloseAccountModal: FunctionComponent<CloseAccountModalProps> = ({
|
|||
}
|
||||
|
||||
const isDisabled =
|
||||
(openOrders && openOrders.length > 0) || hasBorrows || hasOpenPositions
|
||||
(openOrders && openOrders.length > 0) ||
|
||||
hasBorrows ||
|
||||
hasOpenPositions ||
|
||||
!!unsettledBalances.length
|
||||
|
||||
return (
|
||||
<Modal onClose={onClose} isOpen={isOpen && mangoAccount !== undefined}>
|
||||
|
@ -240,6 +248,12 @@ const CloseAccountModal: FunctionComponent<CloseAccountModalProps> = ({
|
|||
{t('close-account:close-open-orders')}
|
||||
</div>
|
||||
) : null}
|
||||
{unsettledBalances.length ? (
|
||||
<div className="flex items-center text-th-fgd-2">
|
||||
<ExclamationCircleIcon className="mr-1.5 h-4 w-4 text-th-red" />
|
||||
{t('close-account:settle-balances')}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import sumBy from 'lodash/sumBy'
|
||||
import useInterval from '../hooks/useInterval'
|
||||
import { SECONDS } from '../stores/useMangoStore'
|
||||
import { CLUSTER, SECONDS } from '../stores/useMangoStore'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { ExclamationIcon } from '@heroicons/react/outline'
|
||||
import { Connection } from '@solana/web3.js'
|
||||
|
@ -43,7 +43,7 @@ const GlobalNotification = () => {
|
|||
getRecentPerformance(setShow, setTps)
|
||||
}, 45 * SECONDS)
|
||||
|
||||
if (show) {
|
||||
if (show && CLUSTER == 'mainnet') {
|
||||
return (
|
||||
<div className="flex items-center bg-th-bkg-4 text-th-fgd-2">
|
||||
<div className="flex w-full items-center justify-center p-1">
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
const HealthHeart = ({ health, size }: { health: number; size: number }) => {
|
||||
const styles = {
|
||||
height: `${size}px`,
|
||||
width: `${size}px`,
|
||||
}
|
||||
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={
|
||||
health > 15 && health < 50
|
||||
? 'text-th-orange'
|
||||
: health > 50
|
||||
? 'text-th-green'
|
||||
: 'text-th-red'
|
||||
}
|
||||
style={styles}
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<g transform-origin="center">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M3.172 5.172a4 4 0 015.656 0L10 6.343l1.172-1.171a4 4 0 115.656 5.656L10 17.657l-6.828-6.829a4 4 0 010-5.656z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
type="scale"
|
||||
keyTimes="0;0.5;1"
|
||||
values="1;1.1;1"
|
||||
dur={health > 15 && health < 50 ? '1s' : health > 50 ? '2s' : '0.33s'}
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
<animate
|
||||
attributeName="opacity"
|
||||
values="0.8;1;0.8"
|
||||
dur={health > 15 && health < 50 ? '1s' : health > 50 ? '2s' : '0.33s'}
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default HealthHeart
|
|
@ -1,20 +1,14 @@
|
|||
import { useEffect, useMemo, useState } from 'react'
|
||||
import dayjs from 'dayjs'
|
||||
import utc from 'dayjs/plugin/utc'
|
||||
import { ExclamationIcon } from '@heroicons/react/solid'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
import useMangoStore, { PerpPosition } from '../../stores/useMangoStore'
|
||||
import { formatUsdValue } from '../../utils'
|
||||
import useMangoStore from '../../stores/useMangoStore'
|
||||
import BalancesTable from '../BalancesTable'
|
||||
import Switch from '../Switch'
|
||||
import useLocalStorageState from '../../hooks/useLocalStorageState'
|
||||
import PerformanceChart from './PerformanceChart'
|
||||
import PositionsTable from '../PerpPositionsTable'
|
||||
import LongShortChart from './LongShortChart'
|
||||
import Tooltip from 'components/Tooltip'
|
||||
import { CalendarIcon, InformationCircleIcon } from '@heroicons/react/outline'
|
||||
import { ZERO_BN } from '@blockworks-foundation/mango-client'
|
||||
import AccountOverviewStats from './AccountOverviewStats'
|
||||
|
||||
dayjs.extend(utc)
|
||||
|
||||
|
@ -46,10 +40,6 @@ export const fetchHourlyPerformanceStats = async (
|
|||
export default function AccountOverview() {
|
||||
const { t } = useTranslation('common')
|
||||
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
|
||||
const spotBalances = useMangoStore((s) => s.selectedMangoAccount.spotBalances)
|
||||
const perpPositions = useMangoStore(
|
||||
(s) => s.selectedMangoAccount.perpPositions
|
||||
)
|
||||
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
const mangoCache = useMangoStore((s) => s.selectedMangoGroup.cache)
|
||||
const [showZeroBalances, setShowZeroBalances] = useLocalStorageState(
|
||||
|
@ -57,7 +47,6 @@ export default function AccountOverview() {
|
|||
true
|
||||
)
|
||||
|
||||
const [pnl, setPnl] = useState(0)
|
||||
const [hourlyPerformanceStats, setHourlyPerformanceStats] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -67,8 +56,6 @@ export default function AccountOverview() {
|
|||
return
|
||||
}
|
||||
const stats = await fetchHourlyPerformanceStats(pubKey, 30)
|
||||
|
||||
setPnl(stats?.length ? stats?.[0]?.['pnl'] : 0)
|
||||
setHourlyPerformanceStats(stats)
|
||||
}
|
||||
if (pubKey) {
|
||||
|
@ -76,218 +63,21 @@ export default function AccountOverview() {
|
|||
}
|
||||
}, [mangoAccount?.publicKey])
|
||||
|
||||
const maintHealthRatio = useMemo(() => {
|
||||
return mangoAccount && mangoGroup && mangoCache
|
||||
? mangoAccount.getHealthRatio(mangoGroup, mangoCache, 'Maint')
|
||||
: 100
|
||||
}, [mangoAccount, mangoGroup, mangoCache])
|
||||
|
||||
const initHealthRatio = useMemo(() => {
|
||||
return mangoAccount && mangoGroup && mangoCache
|
||||
? mangoAccount.getHealthRatio(mangoGroup, mangoCache, 'Init')
|
||||
: 100
|
||||
}, [mangoAccount, mangoGroup, mangoCache])
|
||||
|
||||
const mangoAccountValue = useMemo(() => {
|
||||
return mangoAccount && mangoGroup && mangoCache
|
||||
? +mangoAccount.computeValue(mangoGroup, mangoCache)
|
||||
: 0
|
||||
}, [mangoAccount, mangoGroup, mangoCache])
|
||||
|
||||
const { longData, shortData, longExposure, shortExposure } = useMemo(() => {
|
||||
if (!spotBalances || !perpPositions) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const DUST_THRESHOLD = 0.05
|
||||
const netUnsettledPositionsValue = perpPositions.reduce(
|
||||
(a, c) => a + (c?.unsettledPnl ?? 0),
|
||||
0
|
||||
)
|
||||
|
||||
const longData: any = []
|
||||
const shortData: any = []
|
||||
|
||||
for (const { net, symbol, value } of spotBalances) {
|
||||
let amount = Number(net)
|
||||
let totValue = Number(value)
|
||||
if (symbol === 'USDC') {
|
||||
amount += netUnsettledPositionsValue
|
||||
totValue += netUnsettledPositionsValue
|
||||
}
|
||||
if (totValue > DUST_THRESHOLD) {
|
||||
longData.push({
|
||||
asset: symbol,
|
||||
amount: amount,
|
||||
symbol: symbol,
|
||||
value: totValue,
|
||||
})
|
||||
}
|
||||
if (-totValue > DUST_THRESHOLD) {
|
||||
shortData.push({
|
||||
asset: symbol,
|
||||
amount: Math.abs(amount),
|
||||
symbol: symbol,
|
||||
value: Math.abs(totValue),
|
||||
})
|
||||
}
|
||||
}
|
||||
for (const {
|
||||
marketConfig,
|
||||
basePosition,
|
||||
notionalSize,
|
||||
perpAccount,
|
||||
} of perpPositions.filter((p) => !!p) as PerpPosition[]) {
|
||||
if (notionalSize < DUST_THRESHOLD) continue
|
||||
|
||||
if (perpAccount.basePosition.gt(ZERO_BN)) {
|
||||
longData.push({
|
||||
asset: marketConfig.name,
|
||||
amount: basePosition,
|
||||
symbol: marketConfig.baseSymbol,
|
||||
value: notionalSize,
|
||||
})
|
||||
} else {
|
||||
shortData.push({
|
||||
asset: marketConfig.name,
|
||||
amount: Math.abs(basePosition),
|
||||
symbol: marketConfig.baseSymbol,
|
||||
value: notionalSize,
|
||||
})
|
||||
}
|
||||
}
|
||||
const longExposure = longData.reduce((a, c) => a + c.value, 0)
|
||||
const shortExposure = shortData.reduce((a, c) => a + c.value, 0)
|
||||
const dif = longExposure - shortExposure
|
||||
if (dif > 0) {
|
||||
shortData.push({ symbol: 'spacer', value: dif })
|
||||
}
|
||||
|
||||
return { longData, shortData, longExposure, shortExposure }
|
||||
}, [spotBalances, perpPositions])
|
||||
|
||||
return mangoAccount ? (
|
||||
<>
|
||||
<div className="grid grid-cols-12 md:gap-x-6">
|
||||
<div className="col-span-12 border-y border-th-bkg-4 p-3 sm:p-4 md:col-span-6 xl:col-span-3">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3 sm:text-sm">
|
||||
{t('account-value')}
|
||||
</div>
|
||||
<div className="text-xl font-bold text-th-fgd-1 sm:text-3xl">
|
||||
{formatUsdValue(mangoAccountValue)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-12 border-b border-th-bkg-4 p-3 sm:p-4 md:col-span-6 md:border-t xl:col-span-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex w-full items-center justify-between pb-0.5 text-xs text-th-fgd-3 sm:text-sm">
|
||||
{t('pnl')}{' '}
|
||||
{hourlyPerformanceStats?.length ? (
|
||||
<div className="flex items-center text-xs text-th-fgd-4">
|
||||
<CalendarIcon className="mr-1 h-4 w-4" />
|
||||
{dayjs(hourlyPerformanceStats[0]['time']).format(
|
||||
'MMM D, h:mma'
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xl font-bold text-th-fgd-1 sm:text-3xl">
|
||||
{formatUsdValue(pnl)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-12 border-th-bkg-4 px-3 pt-3 sm:px-4 sm:pt-4 md:col-span-6 xl:col-span-3 xl:border-t">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3 sm:text-sm">
|
||||
{t('health-ratio')}
|
||||
</div>
|
||||
<div className={`text-xl font-bold text-th-fgd-1 sm:text-3xl`}>
|
||||
{maintHealthRatio < 100 ? maintHealthRatio.toFixed(2) : '>100'}%
|
||||
</div>
|
||||
{mangoAccount.beingLiquidated ? (
|
||||
<div className="flex items-center pt-0.5 text-xs sm:pt-2 sm:text-sm">
|
||||
<ExclamationIcon className="mr-1.5 h-5 w-5 flex-shrink-0 text-th-red sm:h-7 sm:w-7" />
|
||||
<span className="text-th-red">{t('being-liquidated')}</span>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="-mx-3 mt-3 flex h-1 rounded bg-th-bkg-3 sm:-mx-4 sm:mt-4">
|
||||
<div
|
||||
style={{
|
||||
width: `${maintHealthRatio}%`,
|
||||
}}
|
||||
className={`flex rounded ${
|
||||
maintHealthRatio > 30
|
||||
? 'bg-th-green'
|
||||
: initHealthRatio > 0
|
||||
? 'bg-th-orange'
|
||||
: 'bg-th-red'
|
||||
}`}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-12 border-b border-th-bkg-4 p-3 sm:p-4 md:col-span-6 xl:col-span-3 xl:border-t">
|
||||
<div className="pb-0.5 text-xs text-th-fgd-3 sm:text-sm">
|
||||
{t('leverage')}
|
||||
</div>
|
||||
{mangoGroup && mangoCache ? (
|
||||
<div className="text-xl font-bold text-th-fgd-1 sm:text-3xl">
|
||||
{mangoAccount.getLeverage(mangoGroup, mangoCache).toFixed(2)}x
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="relative col-span-12 mb-4 mt-8 h-[460px] rounded-md border border-th-bkg-4 p-4 sm:h-[400px] md:p-6 lg:col-span-6 lg:mb-0">
|
||||
<PerformanceChart
|
||||
<div className="relative col-span-12 h-[690px] lg:h-[538px] xl:h-[410px]">
|
||||
<AccountOverviewStats
|
||||
hourlyPerformanceStats={hourlyPerformanceStats}
|
||||
accountValue={mangoAccountValue}
|
||||
chartToShow="Value"
|
||||
/>
|
||||
</div>
|
||||
<div className="relative col-span-12 h-[460px] rounded-md border border-th-bkg-4 p-4 sm:h-[400px] md:p-6 lg:col-span-6 lg:mt-8">
|
||||
<PerformanceChart
|
||||
hourlyPerformanceStats={hourlyPerformanceStats}
|
||||
accountValue={mangoAccountValue}
|
||||
chartToShow="PnL"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-8">
|
||||
<h2 className="mb-4">{t('portfolio-balance')}</h2>
|
||||
<div className="grid grid-flow-col grid-cols-1 grid-rows-2 md:grid-cols-2 md:grid-rows-1 md:gap-6">
|
||||
<div className="border-t border-th-bkg-4 p-3 sm:p-4 md:border-b">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<Tooltip content={t('total-long-tooltip')}>
|
||||
<div className="flex items-center space-x-1.5 pb-0.5">
|
||||
<div className="text-th-fgd-3">{t('total-long')}</div>
|
||||
<InformationCircleIcon className="h-5 w-5 text-th-fgd-3" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
{mangoGroup && mangoCache ? (
|
||||
<div className="text-xl font-bold text-th-fgd-1 md:text-3xl">
|
||||
{formatUsdValue(+longExposure)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<LongShortChart type="long" chartData={longData} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-b border-t border-th-bkg-4 p-3 sm:p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<Tooltip content={t('total-short-tooltip')}>
|
||||
<div className="flex items-center space-x-1.5 pb-0.5">
|
||||
<div className="text-th-fgd-3">{t('total-short')}</div>
|
||||
<InformationCircleIcon className="h-5 w-5 text-th-fgd-3" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
{mangoGroup && mangoCache ? (
|
||||
<div className="text-xl font-bold text-th-fgd-1 md:text-3xl">
|
||||
{formatUsdValue(+shortExposure)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<LongShortChart type="short" chartData={shortData} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pb-8">
|
||||
<h2 className="mb-4">{t('perp-positions')}</h2>
|
||||
|
|
|
@ -0,0 +1,578 @@
|
|||
import { useState, useEffect, useMemo } from 'react'
|
||||
import { useTheme } from 'next-themes'
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
import dayjs from 'dayjs'
|
||||
import {
|
||||
AreaChart,
|
||||
Area,
|
||||
XAxis,
|
||||
YAxis,
|
||||
Tooltip as ChartTooltip,
|
||||
} from 'recharts'
|
||||
import { InformationCircleIcon } from '@heroicons/react/outline'
|
||||
import useDimensions from 'react-cool-dimensions'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { ZERO_BN } from '@blockworks-foundation/mango-client'
|
||||
import ButtonGroup from '../ButtonGroup'
|
||||
import { formatUsdValue } from '../../utils'
|
||||
import { numberCompacter } from '../SwapTokenInfo'
|
||||
import Checkbox from '../Checkbox'
|
||||
import Tooltip from '../Tooltip'
|
||||
import useMangoStore, { PerpPosition } from 'stores/useMangoStore'
|
||||
import LongShortChart from './LongShortChart'
|
||||
import HealthHeart from 'components/HealthHeart'
|
||||
|
||||
type AccountOverviewStats = {
|
||||
hourlyPerformanceStats: any[]
|
||||
performanceRange: '24hr' | '7d' | '30d' | '3m'
|
||||
}
|
||||
|
||||
const defaultData = [
|
||||
{ account_equity: 0, pnl: 0, time: '2022-01-01T00:00:00.000Z' },
|
||||
{ account_equity: 0, pnl: 0, time: '2023-01-01T00:00:00.000Z' },
|
||||
]
|
||||
|
||||
const performanceRangePresets = [
|
||||
{ label: '24h', value: 1 },
|
||||
{ label: '7d', value: 7 },
|
||||
{ label: '30d', value: 30 },
|
||||
{ label: '3m', value: 90 },
|
||||
]
|
||||
const performanceRangePresetLabels = performanceRangePresets.map((x) => x.label)
|
||||
|
||||
const AccountOverviewStats = ({ hourlyPerformanceStats, accountValue }) => {
|
||||
const { theme } = useTheme()
|
||||
const { t } = useTranslation('common')
|
||||
const { observe, width, height } = useDimensions()
|
||||
const [chartToShow, setChartToShow] = useState<string>('Value')
|
||||
const [chartData, setChartData] = useState<any[]>([])
|
||||
const [mouseData, setMouseData] = useState<string | null>(null)
|
||||
const [performanceRange, setPerformanceRange] = useState('30d')
|
||||
const [showSpotPnl, setShowSpotPnl] = useState(true)
|
||||
const [showPerpPnl, setShowPerpPnl] = useState(true)
|
||||
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
|
||||
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||
const mangoCache = useMangoStore((s) => s.selectedMangoGroup.cache)
|
||||
const spotBalances = useMangoStore((s) => s.selectedMangoAccount.spotBalances)
|
||||
const perpPositions = useMangoStore(
|
||||
(s) => s.selectedMangoAccount.perpPositions
|
||||
)
|
||||
|
||||
const maintHealthRatio = useMemo(() => {
|
||||
return mangoAccount && mangoGroup && mangoCache
|
||||
? mangoAccount.getHealthRatio(mangoGroup, mangoCache, 'Maint')
|
||||
: 100
|
||||
}, [mangoAccount, mangoGroup, mangoCache])
|
||||
|
||||
const { longData, shortData, longExposure, shortExposure } = useMemo(() => {
|
||||
const longData: any = []
|
||||
const shortData: any = []
|
||||
|
||||
if (!spotBalances || !perpPositions) {
|
||||
longData.push({ symbol: 'spacer', value: 1 })
|
||||
shortData.push({ symbol: 'spacer', value: 1 })
|
||||
// return {}
|
||||
}
|
||||
|
||||
const DUST_THRESHOLD = 0.05
|
||||
const netUnsettledPositionsValue = perpPositions.reduce(
|
||||
(a, c) => a + (c?.unsettledPnl ?? 0),
|
||||
0
|
||||
)
|
||||
|
||||
for (const { net, symbol, value } of spotBalances) {
|
||||
let amount = Number(net)
|
||||
let totValue = Number(value)
|
||||
if (symbol === 'USDC') {
|
||||
amount += netUnsettledPositionsValue
|
||||
totValue += netUnsettledPositionsValue
|
||||
}
|
||||
if (totValue > DUST_THRESHOLD) {
|
||||
longData.push({
|
||||
asset: symbol,
|
||||
amount: amount,
|
||||
symbol: symbol,
|
||||
value: totValue,
|
||||
})
|
||||
}
|
||||
if (-totValue > DUST_THRESHOLD) {
|
||||
shortData.push({
|
||||
asset: symbol,
|
||||
amount: Math.abs(amount),
|
||||
symbol: symbol,
|
||||
value: Math.abs(totValue),
|
||||
})
|
||||
}
|
||||
}
|
||||
for (const {
|
||||
marketConfig,
|
||||
basePosition,
|
||||
notionalSize,
|
||||
perpAccount,
|
||||
} of perpPositions.filter((p) => !!p) as PerpPosition[]) {
|
||||
if (notionalSize < DUST_THRESHOLD) continue
|
||||
|
||||
if (perpAccount.basePosition.gt(ZERO_BN)) {
|
||||
longData.push({
|
||||
asset: marketConfig.name,
|
||||
amount: basePosition,
|
||||
symbol: marketConfig.baseSymbol,
|
||||
value: notionalSize,
|
||||
})
|
||||
} else {
|
||||
shortData.push({
|
||||
asset: marketConfig.name,
|
||||
amount: Math.abs(basePosition),
|
||||
symbol: marketConfig.baseSymbol,
|
||||
value: notionalSize,
|
||||
})
|
||||
}
|
||||
}
|
||||
const longExposure = longData.reduce((a, c) => a + c.value, 0)
|
||||
const shortExposure = shortData.reduce((a, c) => a + c.value, 0)
|
||||
if (shortExposure === 0) {
|
||||
shortData.push({ symbol: 'spacer', value: 1 })
|
||||
}
|
||||
if (longExposure === 0) {
|
||||
longData.push({ symbol: 'spacer', value: 1 })
|
||||
}
|
||||
const dif = longExposure - shortExposure
|
||||
if (dif > 0) {
|
||||
shortData.push({ symbol: 'spacer', value: dif })
|
||||
}
|
||||
|
||||
return { longData, shortData, longExposure, shortExposure }
|
||||
}, [spotBalances, perpPositions])
|
||||
|
||||
useEffect(() => {
|
||||
if (hourlyPerformanceStats.length > 0) {
|
||||
if (performanceRange === '3m') {
|
||||
setChartData(hourlyPerformanceStats.slice().reverse())
|
||||
}
|
||||
if (performanceRange === '30d') {
|
||||
const start = new Date(
|
||||
// @ts-ignore
|
||||
dayjs().utc().hour(0).minute(0).subtract(29, 'day')
|
||||
).getTime()
|
||||
const chartData = cloneDeep(hourlyPerformanceStats).filter(
|
||||
(d) => new Date(d.time).getTime() > start
|
||||
)
|
||||
const pnlStart = chartData[chartData.length - 1].pnl
|
||||
const perpPnlStart = chartData[chartData.length - 1].perp_pnl
|
||||
for (let i = 0; i < chartData.length; i++) {
|
||||
if (i === chartData.length - 1) {
|
||||
chartData[i].pnl = 0
|
||||
chartData[i].perp_pnl = 0
|
||||
} else {
|
||||
chartData[i].pnl = chartData[i].pnl - pnlStart
|
||||
chartData[i].perp_pnl = chartData[i].perp_pnl - perpPnlStart
|
||||
}
|
||||
}
|
||||
setChartData(chartData.reverse())
|
||||
}
|
||||
if (performanceRange === '7d') {
|
||||
const start = new Date(
|
||||
// @ts-ignore
|
||||
dayjs().utc().hour(0).minute(0).subtract(7, 'day')
|
||||
).getTime()
|
||||
const chartData = cloneDeep(hourlyPerformanceStats).filter(
|
||||
(d) => new Date(d.time).getTime() > start
|
||||
)
|
||||
const pnlStart = chartData[chartData.length - 1].pnl
|
||||
const perpPnlStart = chartData[chartData.length - 1].perp_pnl
|
||||
for (let i = 0; i < chartData.length; i++) {
|
||||
if (i === chartData.length - 1) {
|
||||
chartData[i].pnl = 0
|
||||
chartData[i].perp_pnl = 0
|
||||
} else {
|
||||
chartData[i].pnl = chartData[i].pnl - pnlStart
|
||||
chartData[i].perp_pnl = chartData[i].perp_pnl - perpPnlStart
|
||||
}
|
||||
}
|
||||
setChartData(chartData.reverse())
|
||||
}
|
||||
if (performanceRange === '24h') {
|
||||
const start = new Date(
|
||||
// @ts-ignore
|
||||
dayjs().utc().hour(0).minute(0).subtract(1, 'day')
|
||||
).getTime()
|
||||
const chartData = cloneDeep(hourlyPerformanceStats).filter(
|
||||
(d) => new Date(d.time).getTime() > start
|
||||
)
|
||||
const pnlStart = chartData[chartData.length - 1].pnl
|
||||
const perpPnlStart = chartData[chartData.length - 1].perp_pnl
|
||||
for (let i = 0; i < chartData.length; i++) {
|
||||
if (i === chartData.length - 1) {
|
||||
chartData[i].pnl = 0
|
||||
chartData[i].perp_pnl = 0
|
||||
} else {
|
||||
chartData[i].pnl = chartData[i].pnl - pnlStart
|
||||
chartData[i].perp_pnl = chartData[i].perp_pnl - perpPnlStart
|
||||
}
|
||||
}
|
||||
setChartData(chartData.reverse())
|
||||
}
|
||||
} else {
|
||||
setChartData([])
|
||||
}
|
||||
}, [hourlyPerformanceStats, performanceRange])
|
||||
|
||||
useEffect(() => {
|
||||
if (chartData.length > 0) {
|
||||
for (const stat of chartData) {
|
||||
stat.spot_pnl = stat.pnl - stat.perp_pnl
|
||||
}
|
||||
}
|
||||
}, [chartData])
|
||||
|
||||
const handleMouseMove = (coords) => {
|
||||
if (coords.activePayload) {
|
||||
setMouseData(coords.activePayload[0].payload)
|
||||
}
|
||||
}
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
setMouseData(null)
|
||||
}
|
||||
|
||||
const renderPnlChartTitle = () => {
|
||||
if (showPerpPnl && showSpotPnl) {
|
||||
return t('pnl')
|
||||
}
|
||||
if (!showSpotPnl) {
|
||||
return `${t('perp')} ${t('pnl')}`
|
||||
}
|
||||
if (!showPerpPnl) {
|
||||
return `${t('spot')} ${t('pnl')}`
|
||||
}
|
||||
}
|
||||
|
||||
const formatDateAxis = (date) => {
|
||||
if (['3m', '30d'].includes(performanceRange)) {
|
||||
return dayjs(date).format('D MMM')
|
||||
} else if (performanceRange === '7d') {
|
||||
return dayjs(date).format('ddd, h:mma')
|
||||
} else {
|
||||
return dayjs(date).format('h:mma')
|
||||
}
|
||||
}
|
||||
|
||||
const pnlChartDataKey = () => {
|
||||
if (!showPerpPnl && showSpotPnl) {
|
||||
return 'spot_pnl'
|
||||
} else if (!showSpotPnl && showPerpPnl) {
|
||||
return 'perp_pnl'
|
||||
} else {
|
||||
return 'pnl'
|
||||
}
|
||||
}
|
||||
|
||||
const pnlChartColor =
|
||||
chartToShow === 'PnL' &&
|
||||
chartData.length > 0 &&
|
||||
chartData[chartData.length - 1][pnlChartDataKey()] > 0
|
||||
? theme === 'Mango'
|
||||
? '#AFD803'
|
||||
: '#5EBF4D'
|
||||
: theme === 'Mango'
|
||||
? '#F84638'
|
||||
: '#CC2929'
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-12 lg:gap-6">
|
||||
<div className="order-2 col-span-12 lg:order-1 lg:col-span-4">
|
||||
<div className="p-4 sm:p-5 lg:border-t lg:border-th-bkg-4">
|
||||
<div className="flex items-center pb-1.5">
|
||||
<div className="text-sm text-th-fgd-3">
|
||||
{chartToShow === 'Value'
|
||||
? t('account-value')
|
||||
: renderPnlChartTitle()}{' '}
|
||||
</div>
|
||||
<Tooltip content={t('delayed-info')}>
|
||||
<InformationCircleIcon className="ml-1.5 h-5 w-5 flex-shrink-0 cursor-help text-th-fgd-3" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
{mouseData ? (
|
||||
<>
|
||||
<div className="pb-1 text-2xl font-bold text-th-fgd-1 sm:text-4xl">
|
||||
{formatUsdValue(
|
||||
mouseData[
|
||||
chartToShow === 'PnL' ? pnlChartDataKey() : 'account_equity'
|
||||
]
|
||||
)}
|
||||
</div>
|
||||
<div className="text-xs font-normal text-th-fgd-4">
|
||||
{dayjs(mouseData['time']).format('ddd MMM D YYYY, h:mma')}
|
||||
</div>
|
||||
</>
|
||||
) : chartData.length === 0 ? (
|
||||
<>
|
||||
<div className="pb-1 text-3xl font-bold text-th-fgd-1 sm:text-4xl">
|
||||
{chartToShow === 'PnL' ? '--' : formatUsdValue(accountValue)}
|
||||
</div>
|
||||
<div className="text-xs font-normal text-th-fgd-4">
|
||||
{dayjs().format('ddd MMM D YYYY, h:mma')}
|
||||
</div>
|
||||
</>
|
||||
) : chartData.length > 0 ? (
|
||||
<>
|
||||
<div className="pb-1 text-3xl font-bold text-th-fgd-1 sm:text-4xl">
|
||||
{chartToShow === 'PnL'
|
||||
? formatUsdValue(
|
||||
chartData[chartData.length - 1][pnlChartDataKey()]
|
||||
)
|
||||
: formatUsdValue(accountValue)}
|
||||
</div>
|
||||
<div className="text-xs font-normal text-th-fgd-4">
|
||||
{chartToShow === 'PnL'
|
||||
? dayjs(chartData[chartData.length - 1]['time']).format(
|
||||
'ddd MMM D YYYY, h:mma'
|
||||
)
|
||||
: dayjs().format('ddd MMM D YYYY, h:mma')}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="mt-1 h-8 w-48 animate-pulse rounded bg-th-bkg-3" />
|
||||
<div className="mt-1 h-4 w-24 animate-pulse rounded bg-th-bkg-3" />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex divide-x divide-th-bkg-4 border-y border-th-bkg-4 p-4 sm:p-5 lg:flex-col lg:divide-x-0 lg:divide-y lg:p-0 xl:flex-row xl:divide-y-0 xl:divide-x xl:p-5">
|
||||
<div className="flex w-1/2 items-center space-x-2 lg:w-full lg:p-5 xl:w-1/2 xl:p-0">
|
||||
<HealthHeart size={40} health={Number(maintHealthRatio)} />
|
||||
<div>
|
||||
<Tooltip
|
||||
content={
|
||||
<div>
|
||||
{t('tooltip-account-liquidated')}{' '}
|
||||
<a
|
||||
href="https://docs.mango.markets/mango/health-overview"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t('learn-more')}
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="flex items-center space-x-1.5 pb-0.5">
|
||||
<div className="text-th-fgd-3">{t('health')}</div>
|
||||
<InformationCircleIcon className="h-5 w-5 flex-shrink-0 cursor-help text-th-fgd-3" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div className={`text-2xl font-bold text-th-fgd-1`}>
|
||||
{maintHealthRatio < 100 ? maintHealthRatio.toFixed(2) : '>100'}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-1/2 px-4 pl-4 sm:px-5 lg:w-full lg:p-5 xl:w-1/2 xl:p-0 xl:pl-4">
|
||||
<div className="pb-0.5 text-th-fgd-3">{t('leverage')}</div>
|
||||
{mangoGroup && mangoCache ? (
|
||||
<div className={`text-2xl font-bold text-th-fgd-1`}>
|
||||
{mangoAccount?.getLeverage(mangoGroup, mangoCache).toFixed(2)}x
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex divide-x divide-th-bkg-4 border-b border-th-bkg-4 p-4 sm:p-5 lg:flex-col lg:divide-x-0 lg:divide-y lg:p-0 xl:flex-row xl:divide-y-0 xl:divide-x xl:p-5">
|
||||
<div className="flex w-1/2 flex-col md:flex-row md:items-center md:space-x-3 lg:w-full lg:p-5 xl:w-1/2 xl:flex-col xl:items-start xl:space-x-0 xl:p-0">
|
||||
<LongShortChart chartData={longData} />
|
||||
<div className="mt-3 md:mt-0 xl:mt-3">
|
||||
<Tooltip content={t('total-long-tooltip')}>
|
||||
<div className="flex items-center space-x-1.5 pb-0.5">
|
||||
<div className="text-th-fgd-3">{t('long-exposure')}</div>
|
||||
<InformationCircleIcon className="h-5 w-5 flex-shrink-0 cursor-help text-th-fgd-3" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
{mangoGroup && mangoCache ? (
|
||||
<div className="text-2xl font-bold text-th-fgd-1">
|
||||
{formatUsdValue(+longExposure)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex w-1/2 flex-col pl-4 sm:px-5 md:flex-row md:items-center md:space-x-3 lg:w-full lg:p-5 xl:w-1/2 xl:flex-col xl:items-start xl:space-x-0 xl:p-0 xl:pl-4">
|
||||
<LongShortChart chartData={shortData} />
|
||||
<div className="mt-3 md:mt-0 xl:mt-3">
|
||||
<Tooltip content={t('total-short-tooltip')}>
|
||||
<div className="flex items-center space-x-1.5 pb-0.5">
|
||||
<div className="text-th-fgd-3">{t('short-exposure')}</div>
|
||||
<InformationCircleIcon className="h-5 w-5 flex-shrink-0 cursor-help text-th-fgd-3" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
{mangoGroup && mangoCache ? (
|
||||
<div className="text-2xl font-bold text-th-fgd-1">
|
||||
{formatUsdValue(+shortExposure)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="order-1 col-span-12 border-y border-th-bkg-4 p-4 lg:order-2 lg:col-span-8 xl:p-6">
|
||||
<div className="mb-4 flex justify-between space-x-2 sm:mb-6">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:space-x-3">
|
||||
<div className="mb-3 w-28 sm:mb-0">
|
||||
<ButtonGroup
|
||||
activeValue={chartToShow}
|
||||
className="h-8"
|
||||
onChange={(c) => setChartToShow(c)}
|
||||
values={['Value', 'PnL']}
|
||||
/>
|
||||
</div>
|
||||
{chartToShow === 'PnL' && chartData.length ? (
|
||||
<div className="flex space-x-3">
|
||||
<Checkbox
|
||||
checked={showSpotPnl}
|
||||
disabled={!showPerpPnl}
|
||||
onChange={(e) => setShowSpotPnl(e.target.checked)}
|
||||
>
|
||||
{t('include-spot')}
|
||||
</Checkbox>
|
||||
<Checkbox
|
||||
checked={showPerpPnl}
|
||||
disabled={!showSpotPnl}
|
||||
onChange={(e) => setShowPerpPnl(e.target.checked)}
|
||||
>
|
||||
{t('include-perp')}
|
||||
</Checkbox>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="w-40">
|
||||
<ButtonGroup
|
||||
activeValue={performanceRange}
|
||||
className="h-8"
|
||||
onChange={(p) => setPerformanceRange(p)}
|
||||
values={performanceRangePresetLabels}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{chartData.length > 0 ? (
|
||||
<div className="h-48 md:h-64 lg:h-[410px] xl:h-[270px]" ref={observe}>
|
||||
<AreaChart
|
||||
width={width}
|
||||
height={height}
|
||||
data={chartData?.length ? chartData : defaultData}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
<ChartTooltip
|
||||
cursor={{
|
||||
strokeOpacity: 0,
|
||||
}}
|
||||
content={<></>}
|
||||
/>
|
||||
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="defaultGradientArea"
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="0"
|
||||
y2="1"
|
||||
>
|
||||
<stop offset="0%" stopColor="#ffba24" stopOpacity={0.9} />
|
||||
<stop offset="80%" stopColor="#ffba24" stopOpacity={0} />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="greenGradientArea"
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="0"
|
||||
y2="1"
|
||||
>
|
||||
<stop
|
||||
offset="0%"
|
||||
stopColor={theme === 'Mango' ? '#AFD803' : '#5EBF4D'}
|
||||
stopOpacity={0.9}
|
||||
/>
|
||||
<stop
|
||||
offset="80%"
|
||||
stopColor={theme === 'Mango' ? '#AFD803' : '#5EBF4D'}
|
||||
stopOpacity={0}
|
||||
/>
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="redGradientArea"
|
||||
x1="0"
|
||||
y1="1"
|
||||
x2="0"
|
||||
y2="0"
|
||||
>
|
||||
<stop
|
||||
offset="0%"
|
||||
stopColor={theme === 'Mango' ? '#F84638' : '#CC2929'}
|
||||
stopOpacity={0.9}
|
||||
/>
|
||||
<stop
|
||||
offset="80%"
|
||||
stopColor={theme === 'Mango' ? '#F84638' : '#CC2929'}
|
||||
stopOpacity={0}
|
||||
/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<Area
|
||||
isAnimationActive={true}
|
||||
type="monotone"
|
||||
dataKey={
|
||||
chartToShow === 'PnL' ? pnlChartDataKey() : 'account_equity'
|
||||
}
|
||||
stroke={chartToShow === 'PnL' ? pnlChartColor : '#ffba24'}
|
||||
fill={
|
||||
chartToShow === 'PnL'
|
||||
? chartData[chartData.length - 1][pnlChartDataKey()] > 0
|
||||
? 'url(#greenGradientArea)'
|
||||
: 'url(#redGradientArea)'
|
||||
: 'url(#defaultGradientArea)'
|
||||
}
|
||||
fillOpacity={0.3}
|
||||
/>
|
||||
|
||||
<YAxis
|
||||
dataKey={
|
||||
chartToShow === 'PnL' ? pnlChartDataKey() : 'account_equity'
|
||||
}
|
||||
type="number"
|
||||
domain={['dataMin', 'dataMax']}
|
||||
axisLine={false}
|
||||
dx={-10}
|
||||
tick={{
|
||||
fill:
|
||||
theme === 'Light'
|
||||
? 'rgba(0,0,0,0.4)'
|
||||
: 'rgba(255,255,255,0.35)',
|
||||
fontSize: 10,
|
||||
}}
|
||||
tickLine={false}
|
||||
tickFormatter={(v) => numberCompacter.format(v)}
|
||||
/>
|
||||
<XAxis
|
||||
dataKey="time"
|
||||
axisLine={false}
|
||||
dy={10}
|
||||
minTickGap={20}
|
||||
tick={{
|
||||
fill:
|
||||
theme === 'Light'
|
||||
? 'rgba(0,0,0,0.4)'
|
||||
: 'rgba(255,255,255,0.35)',
|
||||
fontSize: 10,
|
||||
}}
|
||||
tickLine={false}
|
||||
tickFormatter={(v) => formatDateAxis(v)}
|
||||
/>
|
||||
</AreaChart>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex h-48 w-full items-center justify-center rounded-md bg-th-bkg-3 md:h-64 lg:h-[410px] xl:h-[270px]">
|
||||
<p className="mb-0">{t('no-chart')}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountOverviewStats
|
|
@ -2,7 +2,6 @@ import { PieChart, Pie, Cell, Tooltip } from 'recharts'
|
|||
import { formatUsdValue, tokenPrecision } from 'utils'
|
||||
import * as MonoIcons from '../icons'
|
||||
import { QuestionMarkCircleIcon } from '@heroicons/react/outline'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
export const CHART_COLORS = {
|
||||
All: '#ff7c43',
|
||||
|
@ -25,15 +24,7 @@ export const CHART_COLORS = {
|
|||
USDT: '#50AF95',
|
||||
}
|
||||
|
||||
const LongShortChart = ({
|
||||
type,
|
||||
chartData,
|
||||
}: {
|
||||
type: string
|
||||
chartData: any[]
|
||||
}) => {
|
||||
const { t } = useTranslation('common')
|
||||
|
||||
const LongShortChart = ({ chartData }: { chartData: any[] }) => {
|
||||
const CustomToolTip = () => {
|
||||
const renderIcon = (symbol) => {
|
||||
const iconName = `${symbol.slice(0, 1)}${symbol
|
||||
|
@ -47,7 +38,10 @@ const LongShortChart = ({
|
|||
)
|
||||
}
|
||||
|
||||
return chartData.length ? (
|
||||
const hideTooltip =
|
||||
chartData.length === 1 && chartData[0].symbol === 'spacer'
|
||||
|
||||
return chartData.length && !hideTooltip ? (
|
||||
<div className="space-y-1.5 rounded-md bg-th-bkg-2 p-3 pb-2 md:bg-th-bkg-1">
|
||||
{chartData
|
||||
.filter((d) => d.symbol !== 'spacer')
|
||||
|
@ -89,36 +83,35 @@ const LongShortChart = ({
|
|||
}
|
||||
|
||||
return chartData.length ? (
|
||||
<div className="relative h-20 w-20">
|
||||
<PieChart width={80} height={80}>
|
||||
<Pie
|
||||
cursor="pointer"
|
||||
data={chartData}
|
||||
dataKey="value"
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
outerRadius={40}
|
||||
innerRadius={28}
|
||||
minAngle={2}
|
||||
startAngle={90}
|
||||
endAngle={450}
|
||||
>
|
||||
{chartData
|
||||
.sort((a, b) => a.symbol.localeCompare(b.symbol))
|
||||
.map((entry, index) => (
|
||||
<Cell
|
||||
key={`cell-${index}`}
|
||||
fill={CHART_COLORS[entry.symbol]}
|
||||
stroke="rgba(0,0,0,0.1)"
|
||||
/>
|
||||
))}
|
||||
</Pie>
|
||||
<Tooltip content={<CustomToolTip />} position={{ x: -220, y: 0 }} />
|
||||
</PieChart>
|
||||
<div className="absolute top-1/2 left-1/2 -translate-y-1/2 -translate-x-1/2 transform text-xs font-bold uppercase text-th-fgd-3">
|
||||
{type === 'long' ? t('long') : t('short')}
|
||||
</div>
|
||||
</div>
|
||||
<PieChart width={48} height={48}>
|
||||
<Pie
|
||||
cursor="pointer"
|
||||
data={chartData}
|
||||
dataKey="value"
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
outerRadius={24}
|
||||
innerRadius={16}
|
||||
minAngle={2}
|
||||
startAngle={90}
|
||||
endAngle={450}
|
||||
>
|
||||
{chartData
|
||||
.sort((a, b) => a.symbol.localeCompare(b.symbol))
|
||||
.map((entry, index) => (
|
||||
<Cell
|
||||
key={`cell-${index}`}
|
||||
fill={CHART_COLORS[entry.symbol]}
|
||||
stroke="rgba(0,0,0,0.1)"
|
||||
/>
|
||||
))}
|
||||
</Pie>
|
||||
<Tooltip
|
||||
content={<CustomToolTip />}
|
||||
position={{ x: 64, y: 0 }}
|
||||
wrapperStyle={{ zIndex: 10 }}
|
||||
/>
|
||||
</PieChart>
|
||||
) : null
|
||||
}
|
||||
|
||||
|
|
|
@ -1,389 +0,0 @@
|
|||
import { useState, useEffect } from 'react'
|
||||
import { useTheme } from 'next-themes'
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
import dayjs from 'dayjs'
|
||||
import {
|
||||
AreaChart,
|
||||
Area,
|
||||
XAxis,
|
||||
YAxis,
|
||||
Tooltip as ChartTooltip,
|
||||
} from 'recharts'
|
||||
import { InformationCircleIcon } from '@heroicons/react/outline'
|
||||
import useDimensions from 'react-cool-dimensions'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
import ButtonGroup from '../ButtonGroup'
|
||||
import { formatUsdValue } from '../../utils'
|
||||
import { numberCompacter } from '../SwapTokenInfo'
|
||||
import Checkbox from '../Checkbox'
|
||||
import Tooltip from '../Tooltip'
|
||||
|
||||
type PerformanceChart = {
|
||||
hourlyPerformanceStats: any[]
|
||||
performanceRange: '24hr' | '7d' | '30d' | '3m'
|
||||
}
|
||||
|
||||
const defaultData = [
|
||||
{ account_equity: 0, pnl: 0, time: '2022-01-01T00:00:00.000Z' },
|
||||
{ account_equity: 0, pnl: 0, time: '2023-01-01T00:00:00.000Z' },
|
||||
]
|
||||
|
||||
const performanceRangePresets = [
|
||||
{ label: '24h', value: 1 },
|
||||
{ label: '7d', value: 7 },
|
||||
{ label: '30d', value: 30 },
|
||||
{ label: '3m', value: 90 },
|
||||
]
|
||||
const performanceRangePresetLabels = performanceRangePresets.map((x) => x.label)
|
||||
|
||||
const PerformanceChart = ({
|
||||
hourlyPerformanceStats,
|
||||
accountValue,
|
||||
chartToShow,
|
||||
}) => {
|
||||
const { theme } = useTheme()
|
||||
const { t } = useTranslation('common')
|
||||
const { observe, width, height } = useDimensions()
|
||||
|
||||
const [chartData, setChartData] = useState<any[]>([])
|
||||
const [mouseData, setMouseData] = useState<string | null>(null)
|
||||
const [performanceRange, setPerformanceRange] = useState('30d')
|
||||
const [showSpotPnl, setShowSpotPnl] = useState(true)
|
||||
const [showPerpPnl, setShowPerpPnl] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
if (hourlyPerformanceStats.length > 0) {
|
||||
if (performanceRange === '3m') {
|
||||
setChartData(hourlyPerformanceStats.slice().reverse())
|
||||
}
|
||||
if (performanceRange === '30d') {
|
||||
const start = new Date(
|
||||
// @ts-ignore
|
||||
dayjs().utc().hour(0).minute(0).subtract(29, 'day')
|
||||
).getTime()
|
||||
const chartData = cloneDeep(hourlyPerformanceStats).filter(
|
||||
(d) => new Date(d.time).getTime() > start
|
||||
)
|
||||
const pnlStart = chartData[chartData.length - 1].pnl
|
||||
const perpPnlStart = chartData[chartData.length - 1].perp_pnl
|
||||
for (let i = 0; i < chartData.length; i++) {
|
||||
if (i === chartData.length - 1) {
|
||||
chartData[i].pnl = 0
|
||||
chartData[i].perp_pnl = 0
|
||||
} else {
|
||||
chartData[i].pnl = chartData[i].pnl - pnlStart
|
||||
chartData[i].perp_pnl = chartData[i].perp_pnl - perpPnlStart
|
||||
}
|
||||
}
|
||||
setChartData(chartData.reverse())
|
||||
}
|
||||
if (performanceRange === '7d') {
|
||||
const start = new Date(
|
||||
// @ts-ignore
|
||||
dayjs().utc().hour(0).minute(0).subtract(7, 'day')
|
||||
).getTime()
|
||||
const chartData = cloneDeep(hourlyPerformanceStats).filter(
|
||||
(d) => new Date(d.time).getTime() > start
|
||||
)
|
||||
const pnlStart = chartData[chartData.length - 1].pnl
|
||||
const perpPnlStart = chartData[chartData.length - 1].perp_pnl
|
||||
for (let i = 0; i < chartData.length; i++) {
|
||||
if (i === chartData.length - 1) {
|
||||
chartData[i].pnl = 0
|
||||
chartData[i].perp_pnl = 0
|
||||
} else {
|
||||
chartData[i].pnl = chartData[i].pnl - pnlStart
|
||||
chartData[i].perp_pnl = chartData[i].perp_pnl - perpPnlStart
|
||||
}
|
||||
}
|
||||
setChartData(chartData.reverse())
|
||||
}
|
||||
if (performanceRange === '24h') {
|
||||
const start = new Date(
|
||||
// @ts-ignore
|
||||
dayjs().utc().hour(0).minute(0).subtract(1, 'day')
|
||||
).getTime()
|
||||
const chartData = cloneDeep(hourlyPerformanceStats).filter(
|
||||
(d) => new Date(d.time).getTime() > start
|
||||
)
|
||||
const pnlStart = chartData[chartData.length - 1].pnl
|
||||
const perpPnlStart = chartData[chartData.length - 1].perp_pnl
|
||||
for (let i = 0; i < chartData.length; i++) {
|
||||
if (i === chartData.length - 1) {
|
||||
chartData[i].pnl = 0
|
||||
chartData[i].perp_pnl = 0
|
||||
} else {
|
||||
chartData[i].pnl = chartData[i].pnl - pnlStart
|
||||
chartData[i].perp_pnl = chartData[i].perp_pnl - perpPnlStart
|
||||
}
|
||||
}
|
||||
setChartData(chartData.reverse())
|
||||
}
|
||||
} else {
|
||||
setChartData([])
|
||||
}
|
||||
}, [hourlyPerformanceStats, performanceRange])
|
||||
|
||||
useEffect(() => {
|
||||
if (chartData.length > 0) {
|
||||
for (const stat of chartData) {
|
||||
stat.spot_pnl = stat.pnl - stat.perp_pnl
|
||||
}
|
||||
}
|
||||
}, [chartData])
|
||||
|
||||
const handleMouseMove = (coords) => {
|
||||
if (coords.activePayload) {
|
||||
setMouseData(coords.activePayload[0].payload)
|
||||
}
|
||||
}
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
setMouseData(null)
|
||||
}
|
||||
|
||||
const renderPnlChartTitle = () => {
|
||||
if (showPerpPnl && showSpotPnl) {
|
||||
return t('pnl')
|
||||
}
|
||||
if (!showSpotPnl) {
|
||||
return `${t('perp')} ${t('pnl')}`
|
||||
}
|
||||
if (!showPerpPnl) {
|
||||
return `${t('spot')} ${t('pnl')}`
|
||||
}
|
||||
}
|
||||
|
||||
const formatDateAxis = (date) => {
|
||||
if (['3m', '30d'].includes(performanceRange)) {
|
||||
return dayjs(date).format('D MMM')
|
||||
} else if (performanceRange === '7d') {
|
||||
return dayjs(date).format('ddd, h:mma')
|
||||
} else {
|
||||
return dayjs(date).format('h:mma')
|
||||
}
|
||||
}
|
||||
|
||||
const pnlChartDataKey = () => {
|
||||
if (!showPerpPnl && showSpotPnl) {
|
||||
return 'spot_pnl'
|
||||
} else if (!showSpotPnl && showPerpPnl) {
|
||||
return 'perp_pnl'
|
||||
} else {
|
||||
return 'pnl'
|
||||
}
|
||||
}
|
||||
|
||||
const pnlChartColor =
|
||||
chartToShow === 'PnL' &&
|
||||
chartData.length > 0 &&
|
||||
chartData[chartData.length - 1][pnlChartDataKey()] > 0
|
||||
? theme === 'Mango'
|
||||
? '#AFD803'
|
||||
: '#5EBF4D'
|
||||
: theme === 'Mango'
|
||||
? '#F84638'
|
||||
: '#CC2929'
|
||||
|
||||
return (
|
||||
<div className="h-64" ref={observe}>
|
||||
<div className="flex flex-col pb-4 sm:flex-row sm:justify-between sm:pb-9">
|
||||
<div>
|
||||
<div className="flex items-center pb-0.5">
|
||||
<div className="text-sm text-th-fgd-3">
|
||||
{chartToShow === 'Value'
|
||||
? t('account-value')
|
||||
: renderPnlChartTitle()}{' '}
|
||||
<span className="text-th-fgd-4">
|
||||
{`(${t('timeframe-desc', {
|
||||
timeframe: performanceRange,
|
||||
})})`}
|
||||
</span>
|
||||
</div>
|
||||
<Tooltip content={t('delayed-info')}>
|
||||
<InformationCircleIcon className="ml-1.5 h-5 w-5 cursor-help text-th-fgd-3" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
{mouseData ? (
|
||||
<>
|
||||
<div className="pb-1 text-xl font-bold text-th-fgd-1">
|
||||
{formatUsdValue(
|
||||
mouseData[
|
||||
chartToShow === 'PnL' ? pnlChartDataKey() : 'account_equity'
|
||||
]
|
||||
)}
|
||||
</div>
|
||||
<div className="text-xs font-normal text-th-fgd-4">
|
||||
{dayjs(mouseData['time']).format('ddd MMM D YYYY, h:mma')}
|
||||
</div>
|
||||
</>
|
||||
) : chartData.length === 0 ? (
|
||||
<>
|
||||
<div className="pb-1 text-xl font-bold text-th-fgd-1">--</div>
|
||||
<div className="text-xs font-normal text-th-fgd-4">
|
||||
{dayjs().format('ddd MMM D YYYY, h:mma')}
|
||||
</div>
|
||||
</>
|
||||
) : chartData.length > 0 ? (
|
||||
<>
|
||||
<div className="pb-1 text-xl font-bold text-th-fgd-1">
|
||||
{chartToShow === 'PnL'
|
||||
? formatUsdValue(
|
||||
chartData[chartData.length - 1][pnlChartDataKey()]
|
||||
)
|
||||
: formatUsdValue(accountValue)}
|
||||
</div>
|
||||
<div className="text-xs font-normal text-th-fgd-4">
|
||||
{chartToShow === 'PnL'
|
||||
? dayjs(chartData[chartData.length - 1]['time']).format(
|
||||
'ddd MMM D YYYY, h:mma'
|
||||
)
|
||||
: dayjs().format('ddd MMM D YYYY, h:mma')}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="mt-1 h-8 w-48 animate-pulse rounded bg-th-bkg-3" />
|
||||
<div className="mt-1 h-4 w-24 animate-pulse rounded bg-th-bkg-3" />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col sm:items-end">
|
||||
<div className="mt-4 w-full sm:mt-0 sm:w-40">
|
||||
<ButtonGroup
|
||||
activeValue={performanceRange}
|
||||
className="h-8 font-bold"
|
||||
onChange={(p) => setPerformanceRange(p)}
|
||||
values={performanceRangePresetLabels}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{chartToShow === 'PnL' ? (
|
||||
<div className="flex space-x-3 pt-4">
|
||||
<Checkbox
|
||||
checked={showSpotPnl}
|
||||
disabled={!showPerpPnl}
|
||||
onChange={(e) => setShowSpotPnl(e.target.checked)}
|
||||
>
|
||||
{t('include-spot')}
|
||||
</Checkbox>
|
||||
<Checkbox
|
||||
checked={showPerpPnl}
|
||||
disabled={!showSpotPnl}
|
||||
onChange={(e) => setShowPerpPnl(e.target.checked)}
|
||||
>
|
||||
{t('include-perp')}
|
||||
</Checkbox>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
{chartData.length > 0 ? (
|
||||
<AreaChart
|
||||
width={width}
|
||||
height={height}
|
||||
data={chartData?.length ? chartData : defaultData}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
<ChartTooltip
|
||||
cursor={{
|
||||
strokeOpacity: 0,
|
||||
}}
|
||||
content={<></>}
|
||||
/>
|
||||
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="defaultGradientArea"
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="0"
|
||||
y2="1"
|
||||
>
|
||||
<stop offset="0%" stopColor="#ffba24" stopOpacity={0.9} />
|
||||
<stop offset="80%" stopColor="#ffba24" stopOpacity={0} />
|
||||
</linearGradient>
|
||||
<linearGradient id="greenGradientArea" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop
|
||||
offset="0%"
|
||||
stopColor={theme === 'Mango' ? '#AFD803' : '#5EBF4D'}
|
||||
stopOpacity={0.9}
|
||||
/>
|
||||
<stop
|
||||
offset="80%"
|
||||
stopColor={theme === 'Mango' ? '#AFD803' : '#5EBF4D'}
|
||||
stopOpacity={0}
|
||||
/>
|
||||
</linearGradient>
|
||||
<linearGradient id="redGradientArea" x1="0" y1="1" x2="0" y2="0">
|
||||
<stop
|
||||
offset="0%"
|
||||
stopColor={theme === 'Mango' ? '#F84638' : '#CC2929'}
|
||||
stopOpacity={0.9}
|
||||
/>
|
||||
<stop
|
||||
offset="80%"
|
||||
stopColor={theme === 'Mango' ? '#F84638' : '#CC2929'}
|
||||
stopOpacity={0}
|
||||
/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<Area
|
||||
isAnimationActive={true}
|
||||
type="monotone"
|
||||
dataKey={
|
||||
chartToShow === 'PnL' ? pnlChartDataKey() : 'account_equity'
|
||||
}
|
||||
stroke={chartToShow === 'PnL' ? pnlChartColor : '#ffba24'}
|
||||
fill={
|
||||
chartToShow === 'PnL'
|
||||
? chartData[chartData.length - 1][pnlChartDataKey()] > 0
|
||||
? 'url(#greenGradientArea)'
|
||||
: 'url(#redGradientArea)'
|
||||
: 'url(#defaultGradientArea)'
|
||||
}
|
||||
fillOpacity={0.3}
|
||||
/>
|
||||
|
||||
<YAxis
|
||||
dataKey={
|
||||
chartToShow === 'PnL' ? pnlChartDataKey() : 'account_equity'
|
||||
}
|
||||
type="number"
|
||||
domain={['dataMin', 'dataMax']}
|
||||
axisLine={false}
|
||||
dx={-10}
|
||||
tick={{
|
||||
fill:
|
||||
theme === 'Light'
|
||||
? 'rgba(0,0,0,0.4)'
|
||||
: 'rgba(255,255,255,0.35)',
|
||||
fontSize: 10,
|
||||
}}
|
||||
tickLine={false}
|
||||
tickFormatter={(v) => numberCompacter.format(v)}
|
||||
/>
|
||||
<XAxis
|
||||
dataKey="time"
|
||||
axisLine={false}
|
||||
dy={10}
|
||||
minTickGap={20}
|
||||
tick={{
|
||||
fill:
|
||||
theme === 'Light'
|
||||
? 'rgba(0,0,0,0.4)'
|
||||
: 'rgba(255,255,255,0.35)',
|
||||
fontSize: 10,
|
||||
}}
|
||||
tickLine={false}
|
||||
tickFormatter={(v) => formatDateAxis(v)}
|
||||
/>
|
||||
</AreaChart>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PerformanceChart
|
|
@ -3,6 +3,7 @@ import useMangoStore from '../stores/useMangoStore'
|
|||
import {
|
||||
getMultipleAccounts,
|
||||
nativeToUi,
|
||||
zeroKey,
|
||||
} from '@blockworks-foundation/mango-client'
|
||||
import {
|
||||
MSRM_DECIMALS,
|
||||
|
@ -66,7 +67,9 @@ const useSrmAccount = () => {
|
|||
)
|
||||
|
||||
setSrmAccount(srmAccountInfo.accountInfo)
|
||||
setMsrmAccount(msrmAccountInfo.accountInfo)
|
||||
if (!msrmPk.equals(zeroKey)) {
|
||||
setMsrmAccount(msrmAccountInfo.accountInfo)
|
||||
}
|
||||
}
|
||||
|
||||
fetchAccounts()
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"devnet": "NEXT_PUBLIC_CLUSTER=devnet NEXT_PUBLIC_ENDPOINT=https://mango.devnet.rpcpool.com/ NEXT_PUBLIC_GROUP=devnet.2 next dev",
|
||||
"devnet": "NEXT_PUBLIC_CLUSTER=devnet NEXT_PUBLIC_ENDPOINT=https://mango.devnet.rpcpool.com/ NEXT_PUBLIC_GROUP=devnet.4 next dev",
|
||||
"testnet": "NEXT_PUBLIC_CLUSTER=testnet NEXT_PUBLIC_ENDPOINT=https://api.testnet.solana.com NEXT_PUBLIC_GROUP=testnet.0 next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"prepare": "husky install",
|
||||
|
@ -17,7 +18,7 @@
|
|||
"analyze": "ANALYZE=true yarn build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blockworks-foundation/mango-client": "^3.5.5",
|
||||
"@blockworks-foundation/mango-client": "^3.5.7",
|
||||
"@headlessui/react": "^0.0.0-insiders.2dbc38c",
|
||||
"@heroicons/react": "^1.0.0",
|
||||
"@jup-ag/react-hook": "^1.0.0-beta.22",
|
||||
|
|
|
@ -5,12 +5,13 @@
|
|||
"close-account": "Close Account",
|
||||
"close-all-borrows": "Close all borrows",
|
||||
"close-open-orders": "Close all open orders",
|
||||
"close-perp-positions": "Close and settle all Perp positons",
|
||||
"close-perp-positions": "Close and settle all futures positons",
|
||||
"closing-account-will": "Closing your Mango Account will:",
|
||||
"delete-your-account": "Delete your Mango account",
|
||||
"error-deleting-account": "Error deleting account",
|
||||
"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}}"
|
||||
}
|
|
@ -213,7 +213,8 @@
|
|||
"liquidations": "Liquidations",
|
||||
"liquidity": "Liquidity",
|
||||
"liquidity-mining": "Liquidity Mining",
|
||||
"long": "long",
|
||||
"long": "Long",
|
||||
"long-exposure": "Long Exposure",
|
||||
"low": "Low",
|
||||
"maint-health": "Maint Health",
|
||||
"make-trade": "Make a trade",
|
||||
|
@ -262,14 +263,15 @@
|
|||
"no-account-found": "No Account Found",
|
||||
"no-address": "No {{tokenSymbol}} wallet address found",
|
||||
"no-balances": "No balances",
|
||||
"no-borrows": "No borrows found.",
|
||||
"no-borrows": "No borrows found",
|
||||
"no-chart": "No chart available",
|
||||
"no-funding": "No funding earned or paid",
|
||||
"no-history": "No trade history",
|
||||
"no-interest": "No interest earned or paid",
|
||||
"no-margin": "No margin accounts found",
|
||||
"no-markets": "No markets found",
|
||||
"no-orders": "No open orders",
|
||||
"no-perp": "No perp positions",
|
||||
"no-perp": "No futures positions",
|
||||
"no-trades-found": "No trades found...",
|
||||
"no-unsettled": "There are no unsettled funds",
|
||||
"no-wallet": "No wallet address",
|
||||
|
@ -301,9 +303,9 @@
|
|||
"perp": "Perp",
|
||||
"perp-desc": "Perpetual swaps settled in USDC",
|
||||
"perp-fees": "Mango Perp Fees",
|
||||
"perp-positions": "Perp Positions",
|
||||
"perp-positions-tip-desc": "Perp positions accrue Unsettled PnL as price moves. Redeeming adds or removes that amount from your USDC balance.",
|
||||
"perp-positions-tip-title": "Perp Position Details",
|
||||
"perp-positions": "Futures Positions",
|
||||
"perp-positions-tip-desc": "Futures positions accrue Unsettled PnL as price moves. Redeeming adds or removes that amount from your USDC balance.",
|
||||
"perp-positions-tip-title": "Futures Position Details",
|
||||
"perpetual-futures": "Perpetual Futures",
|
||||
"perps": "Perps",
|
||||
"pnl": "PnL",
|
||||
|
@ -364,7 +366,8 @@
|
|||
"settle-all": "Settle All",
|
||||
"settle-error": "Error settling funds",
|
||||
"settle-success": "Successfully settled funds",
|
||||
"short": "short",
|
||||
"short": "Short",
|
||||
"short-exposure": "Short Exposure",
|
||||
"show-all": "Show all in Nav",
|
||||
"show-less": "Show less",
|
||||
"show-more": "Show More",
|
||||
|
@ -432,10 +435,8 @@
|
|||
"total-funding": "Total Funding",
|
||||
"total-funding-stats": "Total Funding Earned/Paid",
|
||||
"total-liabilities": "Total Liabilities Value",
|
||||
"total-long": "Long Exposure",
|
||||
"total-long-tooltip": "Deposits, long futures positions and positive pnl unsettled positions",
|
||||
"total-pnl": "Total PnL",
|
||||
"total-short": "Short Exposure",
|
||||
"total-short-tooltip": "Borrows, short futures positions and negative pnl unsettled positions",
|
||||
"total-srm": "Total SRM in Mango",
|
||||
"totals": "Totals",
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
"error-deleting-account": "Error deleting 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}}"
|
||||
}
|
|
@ -214,6 +214,7 @@
|
|||
"liquidity": "Liquidez",
|
||||
"liquidity-mining": "Minería de liquidez",
|
||||
"long": "Larga",
|
||||
"long-exposure": "Long Exposure",
|
||||
"low": "Bajo",
|
||||
"maint-health": "Salud de mantenimiento",
|
||||
"make-trade": "Hacer un trato",
|
||||
|
@ -262,7 +263,8 @@
|
|||
"no-account-found": "Cuenta no encontrada",
|
||||
"no-address": "No ${tokenSymbol} dirección de billetera encontrada",
|
||||
"no-balances": "Sin saldos",
|
||||
"no-borrows": "No se encontraron préstamos.",
|
||||
"no-borrows": "No se encontraron préstamos",
|
||||
"no-chart": "No chart available",
|
||||
"no-funding": "Sin fondos ganados / pagados",
|
||||
"no-history": "Sin historial comercial",
|
||||
"no-interest": "Sin intereses ganados / pagados",
|
||||
|
@ -365,6 +367,7 @@
|
|||
"settle-error": "Error al liquidar fondos",
|
||||
"settle-success": "Fondos liquidados con éxito",
|
||||
"short": "Vender",
|
||||
"short-exposure": "Short Exposure",
|
||||
"show-all": "Mostrar todo en Nav",
|
||||
"show-less": "Mostrar menos",
|
||||
"show-more": "Mostrar más",
|
||||
|
@ -432,10 +435,8 @@
|
|||
"total-funding": "Financiamiento total",
|
||||
"total-funding-stats": "Financiamiento total ganado / pagado",
|
||||
"total-liabilities": "Valor del pasivo total",
|
||||
"total-long": "Long Exposure",
|
||||
"total-long-tooltip": "Deposits, long futures positions and positive pnl unsettled positions",
|
||||
"total-pnl": "Total PnL",
|
||||
"total-short": "Short Exposure",
|
||||
"total-short-tooltip": "Borrows, short futures positions and negative pnl unsettled positions",
|
||||
"total-srm": "SRM total en mango",
|
||||
"totals": "Totales",
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
"error-deleting-account": "删除帐户出错",
|
||||
"goodbye": "再见 👋",
|
||||
"recover-x-sol": "收回{{amount}}SOL(帐户租金)",
|
||||
"settle-balances": "Settle all balances",
|
||||
"transaction-confirmed": "交易成功",
|
||||
"withdraw-assets-worth": "将总价值{{value}}提出到您的钱包"
|
||||
}
|
|
@ -214,6 +214,7 @@
|
|||
"liquidity": "流动性",
|
||||
"liquidity-mining": "流动性挖矿",
|
||||
"long": "做多",
|
||||
"long-exposure": "Long Exposure",
|
||||
"low": "低",
|
||||
"maint-health": "维持健康度",
|
||||
"make-trade": "下订单",
|
||||
|
@ -263,6 +264,7 @@
|
|||
"no-address": "没有{{tokenSymbol}}钱包地址",
|
||||
"no-balances": "您没有余额",
|
||||
"no-borrows": "您没有借贷。",
|
||||
"no-chart": "No chart available",
|
||||
"no-funding": "您未收/付过资金费",
|
||||
"no-history": "您没有交易纪录",
|
||||
"no-interest": "您未收/付过利息",
|
||||
|
@ -365,6 +367,7 @@
|
|||
"settle-error": "结清出错",
|
||||
"settle-success": "已结清好",
|
||||
"short": "做空",
|
||||
"short-exposure": "Short Exposure",
|
||||
"show-all": "在导航栏中显示全部",
|
||||
"show-less": "显示较少",
|
||||
"show-more": "显示更多",
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
"error-deleting-account": "刪除帳戶出錯",
|
||||
"goodbye": "再見 👋",
|
||||
"recover-x-sol": "收回{{amount}}SOL(帳戶租金)",
|
||||
"settle-balances": "Settle all balances",
|
||||
"transaction-confirmed": "交易成功",
|
||||
"withdraw-assets-worth": "將總價值{{value}}提出到您的錢包"
|
||||
}
|
|
@ -214,6 +214,7 @@
|
|||
"liquidity": "流動性",
|
||||
"liquidity-mining": "流動性挖礦",
|
||||
"long": "做多",
|
||||
"long-exposure": "Long Exposure",
|
||||
"low": "低",
|
||||
"maint-health": "維持健康度",
|
||||
"make-trade": "下訂單",
|
||||
|
@ -263,6 +264,7 @@
|
|||
"no-address": "沒有{{tokenSymbol}}錢包地址",
|
||||
"no-balances": "您沒有餘額",
|
||||
"no-borrows": "您沒有借貸。",
|
||||
"no-chart": "No chart available",
|
||||
"no-funding": "您未收/付過資金費",
|
||||
"no-history": "您沒有交易紀錄",
|
||||
"no-interest": "您未收/付過利息",
|
||||
|
@ -365,6 +367,7 @@
|
|||
"settle-error": "結清出錯",
|
||||
"settle-success": "已結清好",
|
||||
"short": "做空",
|
||||
"short-exposure": "Short Exposure",
|
||||
"show-all": "在導航欄中顯示全部",
|
||||
"show-less": "顯示較少",
|
||||
"show-more": "顯示更多",
|
||||
|
|
|
@ -63,9 +63,15 @@ export const ENDPOINTS: EndpointInfo[] = [
|
|||
websocket: 'https://api.devnet.solana.com',
|
||||
custom: false,
|
||||
},
|
||||
{
|
||||
name: 'testnet',
|
||||
url: 'https://api.testnet.solana.com',
|
||||
websocket: 'https://api.testnet.solana.com',
|
||||
custom: false,
|
||||
},
|
||||
]
|
||||
|
||||
type ClusterType = 'mainnet' | 'devnet'
|
||||
type ClusterType = 'mainnet' | 'devnet' | 'testnet'
|
||||
const DEFAULT_MANGO_GROUP_NAME = process.env.NEXT_PUBLIC_GROUP || 'mainnet.1'
|
||||
export const CLUSTER = DEFAULT_MANGO_GROUP_NAME.split('.')[0] as ClusterType
|
||||
const ENDPOINT = ENDPOINTS.find((e) => e.name === CLUSTER) as EndpointInfo
|
||||
|
|
|
@ -528,3 +528,55 @@ body::-webkit-scrollbar-corner {
|
|||
.react-horizontal-scrolling-menu--item {
|
||||
@apply w-full;
|
||||
}
|
||||
|
||||
/*
|
||||
* react-circular-progressbar styles
|
||||
* All of the styles in this file are configurable!
|
||||
*/
|
||||
|
||||
.CircularProgressbar {
|
||||
/*
|
||||
* This fixes an issue where the CircularProgressbar svg has
|
||||
* 0 width inside a "display: flex" container, and thus not visible.
|
||||
*/
|
||||
width: 100%;
|
||||
/*
|
||||
* This fixes a centering issue with CircularProgressbarWithChildren:
|
||||
* https://github.com/kevinsqi/react-circular-progressbar/issues/94
|
||||
*/
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.CircularProgressbar .CircularProgressbar-path {
|
||||
@apply stroke-th-primary;
|
||||
stroke-linecap: round;
|
||||
transition: stroke-dashoffset 0.5s ease 0s;
|
||||
}
|
||||
|
||||
.CircularProgressbar .CircularProgressbar-trail {
|
||||
@apply stroke-th-bkg-4;
|
||||
/* Used when trail is not full diameter, i.e. when props.circleRatio is set */
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
.CircularProgressbar .CircularProgressbar-text {
|
||||
@apply fill-th-fgd-3 text-sm;
|
||||
dominant-baseline: middle;
|
||||
text-anchor: middle;
|
||||
}
|
||||
|
||||
.CircularProgressbar .CircularProgressbar-background {
|
||||
@apply fill-th-bkg-4;
|
||||
}
|
||||
|
||||
.CircularProgressbar.CircularProgressbar-green .CircularProgressbar-path {
|
||||
@apply stroke-th-green;
|
||||
}
|
||||
|
||||
.CircularProgressbar.CircularProgressbar-orange .CircularProgressbar-path {
|
||||
@apply stroke-th-orange;
|
||||
}
|
||||
|
||||
.CircularProgressbar.CircularProgressbar-red .CircularProgressbar-path {
|
||||
@apply stroke-th-red;
|
||||
}
|
||||
|
|
|
@ -1008,10 +1008,10 @@
|
|||
"@babel/helper-validator-identifier" "^7.16.7"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@blockworks-foundation/mango-client@^3.5.5":
|
||||
version "3.5.5"
|
||||
resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-client/-/mango-client-3.5.5.tgz#de71141571e97acae5165b8d94d1149b67f00e4f"
|
||||
integrity sha512-FSGSo8rqWthfa1ClLmbFrZr6J7iEpw+/rrI04x3a4pPZwpQfiIRaBlwc5SCrD7+q+DR1MD+9vFDRxBpU9hKwfw==
|
||||
"@blockworks-foundation/mango-client@^3.5.7":
|
||||
version "3.5.7"
|
||||
resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-client/-/mango-client-3.5.7.tgz#14a2ac00f2bec9198293179d507358f8f6f916d9"
|
||||
integrity sha512-Yc9UEjEJBaoaV6ZvbAhAZAUYQr/fpjGrub1uUuQYmtiknZwfLb1kZo01+Rfbs/YpMfyZoKTAGHg2ianAMITT6A==
|
||||
dependencies:
|
||||
"@project-serum/anchor" "^0.21.0"
|
||||
"@project-serum/serum" "0.13.55"
|
||||
|
|
Loading…
Reference in New Issue