import { HealthType, toUiDecimalsForQuote, } from '@blockworks-foundation/mango-v4' import { useTranslation } from 'next-i18next' import { useEffect, useMemo, useState } from 'react' import AccountActions from './AccountActions' import mangoStore from '@store/mangoStore' import { formatCurrencyValue } from '../../utils/numbers' import FlipNumbers from 'react-flip-numbers' import dynamic from 'next/dynamic' const SimpleAreaChart = dynamic( () => import('@components/shared/SimpleAreaChart'), { ssr: false } ) import { COLORS } from '../../styles/colors' import { useTheme } from 'next-themes' import { IconButton } from '../shared/Button' import { ArrowsPointingOutIcon, CalendarIcon, ChartBarIcon, } from '@heroicons/react/20/solid' import { Transition } from '@headlessui/react' import AccountTabs from './AccountTabs' import SheenLoader from '../shared/SheenLoader' import AccountChart from './AccountChart' import useMangoAccount from '../../hooks/useMangoAccount' import Change from '../shared/Change' import Tooltip from '@components/shared/Tooltip' import { ANIMATION_SETTINGS_KEY, MANGO_DATA_API_URL, // IS_ONBOARDED_KEY } from 'utils/constants' import { useWallet } from '@solana/wallet-adapter-react' import useLocalStorageState from 'hooks/useLocalStorageState' // import AccountOnboardingTour from '@components/tours/AccountOnboardingTour' import dayjs from 'dayjs' import { INITIAL_ANIMATION_SETTINGS } from '@components/settings/AnimationSettings' import { useViewport } from 'hooks/useViewport' import { breakpoints } from 'utils/theme' import useMangoGroup from 'hooks/useMangoGroup' import PnlHistoryModal from '@components/modals/PnlHistoryModal' import FormatNumericValue from '@components/shared/FormatNumericValue' import HealthBar from './HealthBar' const AssetsLiabilities = dynamic(() => import('./AssetsLiabilities'), { ssr: false, }) const TABS = ['account-value', 'account:assets-liabilities'] import { PerformanceDataItem, TotalAccountFundingItem } from 'types' import { useQuery } from '@tanstack/react-query' const FundingDetails = dynamic(() => import('./FundingDetails'), { ssr: false, }) const fetchFundingTotals = async (mangoAccountPk: string) => { try { const data = await fetch( `${MANGO_DATA_API_URL}/stats/funding-account-total?mango-account=${mangoAccountPk}` ) const res = await data.json() if (res) { const entries: [string, Omit][] = Object.entries(res) const stats: TotalAccountFundingItem[] = entries .map(([key, value]) => { return { ...value, market: key } }) .filter((x) => x) return stats } } catch (e) { console.log('Failed to fetch account funding', e) } } const AccountPage = () => { const { t } = useTranslation(['common', 'account']) const { connected } = useWallet() const { group } = useMangoGroup() const { mangoAccount, mangoAccountAddress } = useMangoAccount() const actions = mangoStore.getState().actions const performanceLoading = mangoStore( (s) => s.mangoAccount.performance.loading ) const performanceData = mangoStore((s) => s.mangoAccount.performance.data) const totalInterestData = mangoStore( (s) => s.mangoAccount.interestTotals.data ) const [chartToShow, setChartToShow] = useState< | 'account-value' | 'cumulative-interest-value' | 'pnl' | 'hourly-funding' | '' >('') const [showExpandChart, setShowExpandChart] = useState(false) const [showPnlHistory, setShowPnlHistory] = useState(false) const { theme } = useTheme() const { width } = useViewport() const isMobile = width ? width < breakpoints.md : false // const tourSettings = mangoStore((s) => s.settings.tours) // const [isOnBoarded] = useLocalStorageState(IS_ONBOARDED_KEY) const [animationSettings] = useLocalStorageState( ANIMATION_SETTINGS_KEY, INITIAL_ANIMATION_SETTINGS ) const [activeTab, setActiveTab] = useLocalStorageState( 'accountHeroKey-0.1', 'account-value' ) useEffect(() => { if (mangoAccountAddress || (!mangoAccountAddress && connected)) { actions.fetchAccountPerformance(mangoAccountAddress, 31) actions.fetchAccountInterestTotals(mangoAccountAddress) } }, [actions, mangoAccountAddress, connected]) const { data: fundingData, isLoading: loadingFunding, isFetching: fetchingFunding, } = useQuery( ['funding', mangoAccountAddress], () => fetchFundingTotals(mangoAccountAddress), { cacheTime: 1000 * 60 * 10, staleTime: 1000 * 60, retry: 3, refetchOnWindowFocus: false, enabled: !!mangoAccountAddress, } ) const oneDayPerformanceData: PerformanceDataItem[] | [] = useMemo(() => { if (!performanceData || !performanceData.length) return [] const nowDate = new Date() return performanceData.filter((d) => { const dataTime = new Date(d.time).getTime() return dataTime >= nowDate.getTime() - 86400000 }) }, [performanceData]) const onHoverMenu = (open: boolean, action: string) => { if ( (!open && action === 'onMouseEnter') || (open && action === 'onMouseLeave') ) { setShowExpandChart(!open) } } const handleShowAccountValueChart = () => { setChartToShow('account-value') setShowExpandChart(false) } const handleHideChart = () => { setChartToShow('') } const handleCloseDailyPnlModal = () => { const set = mangoStore.getState().set set((s) => { s.mangoAccount.performance.data = oneDayPerformanceData }) setShowPnlHistory(false) } const accountValue = useMemo(() => { if (!group || !mangoAccount) return 0.0 return toUiDecimalsForQuote(mangoAccount.getEquity(group).toNumber()) }, [group, mangoAccount]) const leverage = useMemo(() => { if (!group || !mangoAccount) return 0 const assetsValue = toUiDecimalsForQuote( mangoAccount.getAssetsValue(group).toNumber() ) if (isNaN(assetsValue / accountValue)) { return 0 } else { return Math.abs(1 - assetsValue / accountValue) } }, [mangoAccount, group, accountValue]) const [accountPnl, accountValueChange, oneDayPnlChange] = useMemo(() => { if ( accountValue && oneDayPerformanceData.length && performanceData.length ) { const accountPnl = performanceData[performanceData.length - 1].pnl const accountValueChange = accountValue - oneDayPerformanceData[0].account_equity const startDayPnl = oneDayPerformanceData[0].pnl const endDayPnl = oneDayPerformanceData[oneDayPerformanceData.length - 1].pnl const oneDayPnlChange = endDayPnl - startDayPnl return [accountPnl, accountValueChange, oneDayPnlChange] } return [0, 0, 0] }, [accountValue, oneDayPerformanceData, performanceData]) const interestTotalValue = useMemo(() => { if (totalInterestData.length) { return totalInterestData.reduce( (a, c) => a + c.borrow_interest_usd * -1 + c.deposit_interest_usd, 0 ) } return 0.0 }, [totalInterestData]) const fundingTotalValue = useMemo(() => { if (fundingData?.length && mangoAccountAddress) { return fundingData.reduce( (a, c) => a + c.long_funding + c.short_funding, 0 ) } return 0.0 }, [fundingData, mangoAccountAddress]) const oneDayInterestChange = useMemo(() => { if (oneDayPerformanceData.length) { const first = oneDayPerformanceData[0] const latest = oneDayPerformanceData[oneDayPerformanceData.length - 1] const startDayInterest = first.borrow_interest_cumulative_usd + first.deposit_interest_cumulative_usd const endDayInterest = latest.borrow_interest_cumulative_usd + latest.deposit_interest_cumulative_usd return endDayInterest - startDayInterest } return 0.0 }, [oneDayPerformanceData]) const maintHealth = useMemo(() => { return group && mangoAccount ? mangoAccount.getHealthRatioUi(group, HealthType.maint) : 0 }, [mangoAccount, group]) const handleChartToShow = ( chartName: 'pnl' | 'cumulative-interest-value' | 'hourly-funding' ) => { if ( (chartName === 'cumulative-interest-value' && interestTotalValue > 1) || interestTotalValue < -1 ) { setChartToShow(chartName) } if (chartName === 'pnl' && performanceData.length > 4) { setChartToShow(chartName) } if (chartName === 'hourly-funding') { setChartToShow(chartName) } } const latestAccountData = useMemo(() => { if (!accountValue || !performanceData.length) return [] const latestDataItem = performanceData[performanceData.length - 1] return [ { account_equity: accountValue, time: dayjs(Date.now()).toISOString(), borrow_interest_cumulative_usd: latestDataItem.borrow_interest_cumulative_usd, deposit_interest_cumulative_usd: latestDataItem.deposit_interest_cumulative_usd, pnl: latestDataItem.pnl, spot_value: latestDataItem.spot_value, transfer_balance: latestDataItem.transfer_balance, }, ] }, [accountValue, performanceData]) return !chartToShow ? ( <>
{TABS.map((tab) => ( ))}
{activeTab === 'account-value' ? (
{animationSettings['number-scroll'] ? ( group && mangoAccount ? ( ) : ( ) ) : ( )}

{t('rolling-change')}

{!performanceLoading ? ( oneDayPerformanceData.length ? (
onHoverMenu(showExpandChart, 'onMouseEnter') } onMouseLeave={() => onHoverMenu(showExpandChart, 'onMouseLeave') } > = 0 ? COLORS.UP[theme] : COLORS.DOWN[theme] } data={oneDayPerformanceData.concat(latestAccountData)} name="accountValue" xKey="time" yKey="account_equity" /> handleShowAccountValueChart()} >
) : null ) : mangoAccountAddress ? (
) : null}
) : null} {activeTab === 'account:assets-liabilities' ? ( ) : null}

Health describes how close your account is to liquidation. The lower your account health is the more likely you are to get liquidated when prices fluctuate.

Your account health is {maintHealth}%

Scenario:{' '} If the prices of all your liabilities increase by{' '} {maintHealth}%, even for just a moment, some of your liabilities will be liquidated.

Scenario:{' '} 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.

These are examples. A combination of events can also lead to liquidation.

} >

{t('health')}

{maintHealth}%

{t('free-collateral')}

{t('total')}:

{t('leverage')}

x

{t('pnl')}

{mangoAccountAddress ? (
{performanceData.length > 4 ? ( handleChartToShow('pnl')} > ) : null} setShowPnlHistory(true)} >
) : null}

{t('rolling-change')}

{t('total-interest-earned')}

{Math.abs(interestTotalValue) > 1 && mangoAccountAddress ? ( handleChartToShow('cumulative-interest-value') } > ) : null}

{t('rolling-change')}

{t('account:total-funding-earned')}

{Math.abs(fundingTotalValue) > 1 && mangoAccountAddress ? ( handleChartToShow('hourly-funding')} > ) : null}
{(loadingFunding || fetchingFunding) && mangoAccountAddress ? (
) : (

)}
{/* {!tourSettings?.account_tour_seen && isOnBoarded && connected ? ( ) : null} */} {showPnlHistory ? ( ) : null} ) : (
{chartToShow === 'account-value' ? ( ) : chartToShow === 'pnl' ? ( ) : chartToShow === 'hourly-funding' ? ( ) : ( )}
) } export default AccountPage