import { useEffect, useCallback, useMemo, useState } from 'react' import { Table, Thead, Tbody, Tr, Th, Td } from 'react-super-responsive-table' import styled from '@emotion/styled' import { Menu } from '@headlessui/react' import Link from 'next/link' import { ChartBarIcon, CurrencyDollarIcon, ExclamationIcon, DotsHorizontalIcon, GiftIcon, HeartIcon, XIcon, } from '@heroicons/react/outline' import { ArrowSmDownIcon } from '@heroicons/react/solid' import { getTokenBySymbol, nativeToUi, ZERO_BN, I80F48, } from '@blockworks-foundation/mango-client' import useMangoStore, { mangoClient, MNGO_INDEX, } from '../../stores/useMangoStore' import { useBalances } from '../../hooks/useBalances' import { useSortableData } from '../../hooks/useSortableData' import useLocalStorageState from '../../hooks/useLocalStorageState' import { sleep, tokenPrecision, formatUsdValue } from '../../utils' import { notify } from '../../utils/notifications' import { Market } from '@project-serum/serum' import SideBadge from '../SideBadge' import Button, { LinkButton } from '../Button' import Switch from '../Switch' import PositionsTable from '../PerpPositionsTable' import DepositModal from '../DepositModal' import WithdrawModal from '../WithdrawModal' const StyledAccountValue = styled.div` font-size: 1.8rem; line-height: 1.2; ` export default function AccountOverview() { const [spotPortfolio, setSpotPortfolio] = useState([]) const [unsettled, setUnsettled] = useState([]) const [filteredSpotPortfolio, setFilteredSpotPortfolio] = useState([]) const [showZeroBalances, setShowZeroBalances] = useLocalStorageState( 'showZeroAccountBalances', false ) const [showDepositModal, setShowDepositModal] = useState(false) const [showWithdrawModal, setShowWithdrawModal] = useState(false) const [actionSymbol, setActionSymbol] = useState('') const balances = useBalances() const actions = useMangoStore((s) => s.actions) const groupConfig = useMangoStore((s) => s.selectedMangoGroup.config) const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current) const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current) const mangoCache = useMangoStore((s) => s.selectedMangoGroup.cache) const { items, requestSort, sortConfig } = useSortableData( filteredSpotPortfolio ) useEffect(() => { const spotPortfolio = [] const unsettled = [] balances.forEach((b) => { const token = getTokenBySymbol(groupConfig, b.symbol) const tokenIndex = mangoGroup.getTokenIndex(token.mintKey) if (+b.marginDeposits > 0 || b.orders > 0) { spotPortfolio.push({ market: b.symbol, balance: +b.marginDeposits + b.orders + b.unsettled, borrowRate: mangoGroup .getBorrowRate(tokenIndex) .mul(I80F48.fromNumber(100)), depositRate: mangoGroup .getDepositRate(tokenIndex) .mul(I80F48.fromNumber(100)), price: mangoGroup.getPrice(tokenIndex, mangoCache).toNumber(), symbol: b.symbol, value: (+b.marginDeposits + b.orders + b.unsettled) * mangoGroup.getPrice(tokenIndex, mangoCache).toNumber(), type: 'Deposit', }) } else if (+b.borrows > 0) { spotPortfolio.push({ market: b.symbol, balance: +b.borrows, borrowRate: mangoGroup .getBorrowRate(tokenIndex) .mul(I80F48.fromNumber(100)), depositRate: mangoGroup .getDepositRate(tokenIndex) .mul(I80F48.fromNumber(100)), price: mangoGroup.getPrice(tokenIndex, mangoCache).toNumber(), symbol: b.symbol, value: b.borrows.mul(mangoGroup.getPrice(tokenIndex, mangoCache)), type: 'Borrow', }) } else { spotPortfolio.push({ market: b.symbol, balance: 0, borrowRate: mangoGroup .getBorrowRate(tokenIndex) .mul(I80F48.fromNumber(100)), depositRate: mangoGroup .getDepositRate(tokenIndex) .mul(I80F48.fromNumber(100)), price: mangoGroup.getPrice(tokenIndex, mangoCache).toNumber(), symbol: b.symbol, value: 0, type: '–', }) } if (b.unsettled > 0) { unsettled.push({ market: b.symbol, balance: b.unsettled, symbol: b.symbol, value: b.unsettled * mangoGroup.getPrice(tokenIndex, mangoCache).toNumber(), }) } }) setSpotPortfolio(spotPortfolio.sort((a, b) => b.value - a.value)) setFilteredSpotPortfolio( !showZeroBalances ? spotPortfolio .filter((pos) => pos.balance > 0) .sort((a, b) => b.value - a.value) : spotPortfolio.sort((a, b) => b.value - a.value) ) setUnsettled(unsettled) }, [mangoAccount]) const maintHealthRatio = useMemo(() => { return mangoAccount ? mangoAccount.getHealthRatio(mangoGroup, mangoCache, 'Maint') : 100 }, [mangoAccount, mangoGroup, mangoCache]) const initHealthRatio = useMemo(() => { return mangoAccount ? mangoAccount.getHealthRatio(mangoGroup, mangoCache, 'Init') : 100 }, [mangoAccount, mangoGroup, mangoCache]) const mngoAccrued = useMemo(() => { return mangoAccount ? mangoAccount.perpAccounts.reduce((acc, perpAcct) => { return perpAcct.mngoAccrued.add(acc) }, ZERO_BN) : ZERO_BN }, [mangoAccount]) const handleShowZeroBalances = (checked) => { if (checked) { setFilteredSpotPortfolio(spotPortfolio) } else { setFilteredSpotPortfolio(spotPortfolio.filter((pos) => pos.balance > 0)) } setShowZeroBalances(checked) } async function handleSettleAll() { const mangoAccount = useMangoStore.getState().selectedMangoAccount.current const mangoGroup = useMangoStore.getState().selectedMangoGroup.current const markets = useMangoStore.getState().selectedMangoGroup.markets const wallet = useMangoStore.getState().wallet.current try { const spotMarkets = Object.values(markets).filter( (mkt) => mkt instanceof Market ) as Market[] // @ts-ignore await mangoClient.settleAll(mangoGroup, mangoAccount, spotMarkets, wallet) notify({ title: 'Successfully settled funds' }) await sleep(250) actions.fetchMangoAccounts() } catch (e) { console.warn('Error settling all:', e) if (e.message === 'No unsettled funds') { notify({ title: 'There are no unsettled funds', type: 'error', }) } else { notify({ title: 'Error settling funds', description: e.message, txid: e.txid, type: 'error', }) } } } const handleRedeemMngo = async () => { const wallet = useMangoStore.getState().wallet.current const mngoNodeBank = mangoGroup.rootBankAccounts[MNGO_INDEX].nodeBankAccounts[0] try { const txid = await mangoClient.redeemAllMngo( mangoGroup, mangoAccount, wallet, mangoGroup.tokens[MNGO_INDEX].rootBank, mngoNodeBank.publicKey, mngoNodeBank.vault ) actions.fetchMangoAccounts() notify({ title: 'Successfully redeemed MNGO', description: '', txid, }) } catch (e) { notify({ title: 'Error redeeming MNGO', description: e.message, txid: e.txid, type: 'error', }) } } const handleOpenDepositModal = useCallback((symbol) => { setActionSymbol(symbol) setShowDepositModal(true) }, []) const handleOpenWithdrawModal = useCallback((symbol) => { setActionSymbol(symbol) setShowWithdrawModal(true) }, []) return mangoAccount ? ( <>
Account Value
{formatUsdValue( +mangoAccount.computeValue(mangoGroup, mangoCache) )}
PNL
{formatUsdValue( +mangoAccount.computeValue(mangoGroup, mangoCache) )}
Health Ratio
{maintHealthRatio.toFixed(2)}%
30 ? 'bg-th-green' : initHealthRatio > 0 ? 'bg-th-orange' : 'bg-th-red' }`} >
MNGO Rewards
{mangoGroup ? nativeToUi( mngoAccrued.toNumber(), mangoGroup.tokens[MNGO_INDEX].decimals ) : 0}
Claim Reward
{unsettled.length > 0 ? (
Unsettled Balances
{unsettled.map((a) => (
{a.symbol}
{a.balance.toFixed(tokenPrecision[a.symbol])}
))}
) : null}
Perp Positions
Assets & Liabilities
Total Assets Value
{formatUsdValue( +mangoAccount.getAssetsVal(mangoGroup, mangoCache) )}
Total Liabilities Value
{formatUsdValue( +mangoAccount.getLiabsVal(mangoGroup, mangoCache) )}
Show zero balances
{filteredSpotPortfolio.length > 0 ? ( {items.map((pos, i) => ( ))}
requestSort('market')} > Asset requestSort('type')} > Type requestSort('balance')} > Balance requestSort('price')} > Price requestSort('value')} > Value requestSort('depositRate')} > Deposit Rate requestSort('borrowRate')} > Borrow Rate
{pos.market}
{pos.type === 'Long' || pos.type === 'Short' ? ( ) : ( pos.type )} {pos.balance > 0 ? pos.balance.toFixed(tokenPrecision[pos.symbol]) : 0} {formatUsdValue(pos.price)} {formatUsdValue(pos.value)} {pos.depositRate.toFixed(2)}% {pos.borrowRate.toFixed(2)}% {({ open }) => (
{open ? ( ) : ( )}
{pos.symbol}
{pos.symbol !== 'USDC' ? (
Trade
) : null}
)}
) : (
No assets
)} {showDepositModal && ( setShowDepositModal(false)} tokenSymbol={actionSymbol} /> )} {showWithdrawModal && ( setShowWithdrawModal(false)} tokenSymbol={actionSymbol} /> )} ) : null }