import { Serum3Market } from '@blockworks-foundation/mango-v4' import { ChevronDownIcon, NoSymbolIcon } from '@heroicons/react/20/solid' import mangoStore from '@store/mangoStore' import useMangoAccount from 'hooks/useMangoAccount' import { useViewport } from 'hooks/useViewport' import { useTranslation } from 'next-i18next' import { useRouter } from 'next/router' import { useCallback, useMemo } from 'react' import { floorToDecimal, getDecimalCount } from 'utils/numbers' import { breakpoints } from 'utils/theme' import { calculateLimitPriceForMarketOrder } from 'utils/tradeForm' import { LinkButton } from './Button' import { Table, Td, Th, TrBody, TrHead } from './TableElements' import useSelectedMarket from 'hooks/useSelectedMarket' import ConnectEmptyState from './ConnectEmptyState' import { useWallet } from '@solana/wallet-adapter-react' import FormatNumericValue from './FormatNumericValue' import BankAmountWithValue from './BankAmountWithValue' import useBanksWithBalances, { BankWithBalance, } from 'hooks/useBanksWithBalances' import useUnownedAccount from 'hooks/useUnownedAccount' import { Disclosure, Transition } from '@headlessui/react' import TokenLogo from './TokenLogo' import useHealthContributions from 'hooks/useHealthContributions' import Tooltip from './Tooltip' import { PublicKey } from '@solana/web3.js' import { USDC_MINT } from 'utils/constants' import { WRAPPED_SOL_MINT } from '@project-serum/serum/lib/token-instructions' const BalancesTable = () => { const { t } = useTranslation(['common', 'account', 'trade']) const { mangoAccount, mangoAccountAddress } = useMangoAccount() const spotBalances = mangoStore((s) => s.mangoAccount.spotBalances) const { width } = useViewport() const { connected } = useWallet() const showTableView = width ? width > breakpoints.md : false const banks = useBanksWithBalances('balance') const { initContributions } = useHealthContributions() const filteredBanks = useMemo(() => { if (banks.length) { return banks.filter((b) => { return ( Math.abs(floorToDecimal(b.balance, b.bank.mintDecimals).toNumber()) > 0 || spotBalances[b.bank.mint.toString()]?.unsettled > 0 || spotBalances[b.bank.mint.toString()]?.inOrders > 0 ) }) } return [] }, [banks]) return filteredBanks.length ? ( showTableView ? ( {filteredBanks.map((b) => { const bank = b.bank const symbol = bank.name === 'MSOL' ? 'mSOL' : bank.name const inOrders = spotBalances[bank.mint.toString()]?.inOrders || 0 const unsettled = spotBalances[bank.mint.toString()]?.unsettled || 0 const collateralValue = initContributions.find((val) => val.asset === bank.name) ?.contribution || 0 const assetWeight = bank .scaledInitAssetWeight(bank.price) .toFixed(2) const liabWeight = bank.scaledInitLiabWeight(bank.price).toFixed(2) return ( ) })}
{t('token')} {t('balance')}
{t('account:collateral-value')}
{t('trade:in-orders')} {t('trade:unsettled')}
{symbol}

x

) : (
{filteredBanks.map((b, i) => { const bank = b.bank const symbol = bank.name === 'MSOL' ? 'mSOL' : bank.name const inOrders = spotBalances[bank.mint.toString()]?.inOrders || 0 const unsettled = spotBalances[bank.mint.toString()]?.unsettled || 0 const collateralValue = initContributions.find((val) => val.asset === bank.name) ?.contribution || 0 const assetWeight = bank.scaledInitAssetWeight(bank.price).toFixed(2) const liabWeight = bank.scaledInitLiabWeight(bank.price).toFixed(2) return ( {({ open }) => ( <>

{symbol}

{t('account:collateral-value')}

{' '} x

{t('trade:in-orders')}

{t('trade:unsettled')}

)}
) })}
) ) : mangoAccountAddress || connected ? (

{t('trade:no-balances')}

) : (
) } export default BalancesTable const Balance = ({ bank }: { bank: BankWithBalance }) => { const { selectedMarket } = useSelectedMarket() const { asPath } = useRouter() const { isUnownedAccount } = useUnownedAccount() const { width } = useViewport() const isMobile = width ? width < breakpoints.md : false const tokenBank = bank.bank const handleTradeFormBalanceClick = useCallback( (balance: number, type: 'base' | 'quote') => { const set = mangoStore.getState().set const group = mangoStore.getState().group const tradeForm = mangoStore.getState().tradeForm if (!group || !selectedMarket) return let price: number if (tradeForm.tradeType === 'Market') { const orderbook = mangoStore.getState().selectedMarket.orderbook const side = (balance > 0 && type === 'quote') || (balance < 0 && type === 'base') ? 'buy' : 'sell' price = calculateLimitPriceForMarketOrder(orderbook, balance, side) } else { price = Number(tradeForm.price) } let minOrderDecimals: number let tickDecimals: number if (selectedMarket instanceof Serum3Market) { const market = group.getSerum3ExternalMarket( selectedMarket.serumMarketExternal ) minOrderDecimals = getDecimalCount(market.minOrderSize) tickDecimals = getDecimalCount(market.tickSize) } else { minOrderDecimals = getDecimalCount(selectedMarket.minOrderSize) tickDecimals = getDecimalCount(selectedMarket.tickSize) } if (type === 'quote') { const floorBalance = floorToDecimal(balance, tickDecimals).toNumber() const baseSize = floorToDecimal( floorBalance / price, minOrderDecimals ).toNumber() const quoteSize = floorToDecimal(baseSize * price, tickDecimals) set((s) => { s.tradeForm.baseSize = baseSize.toString() s.tradeForm.quoteSize = quoteSize.toString() }) } else { const baseSize = floorToDecimal(balance, minOrderDecimals).toNumber() const quoteSize = floorToDecimal(baseSize * price, tickDecimals) set((s) => { s.tradeForm.baseSize = baseSize.toString() s.tradeForm.quoteSize = quoteSize.toString() }) } }, [selectedMarket] ) const handleSwapFormBalanceClick = useCallback( (balance: number) => { const set = mangoStore.getState().set const group = mangoStore.getState().group const swap = mangoStore.getState().swap const usdcBank = group?.getFirstBankByMint(new PublicKey(USDC_MINT)) const solBank = group?.getFirstBankByMint(WRAPPED_SOL_MINT) if (balance >= 0) { set((s) => { s.swap.inputBank = tokenBank s.swap.amountIn = balance.toString() s.swap.amountOut = '' s.swap.swapMode = 'ExactIn' if (tokenBank.name === swap.outputBank?.name) { s.swap.outputBank = swap.outputBank.name === 'USDC' ? solBank : usdcBank } }) } else { set((s) => { s.swap.outputBank = tokenBank s.swap.amountIn = '' s.swap.amountOut = Math.abs(balance).toString() s.swap.swapMode = 'ExactOut' if (tokenBank.name === swap.inputBank?.name) { s.swap.inputBank = swap.inputBank.name === 'USDC' ? solBank : usdcBank } }) } }, [bank] ) const balance = bank.balance const isBaseOrQuote = useMemo(() => { if (selectedMarket instanceof Serum3Market) { if (tokenBank.tokenIndex === selectedMarket.baseTokenIndex) { return 'base' } else if (tokenBank.tokenIndex === selectedMarket.quoteTokenIndex) { return 'quote' } else return '' } }, [tokenBank, selectedMarket]) if (!balance) return

0

return (

{!isUnownedAccount && !isMobile ? ( asPath.includes('/trade') && isBaseOrQuote ? ( handleTradeFormBalanceClick(Math.abs(balance), isBaseOrQuote) } > ) : asPath.includes('/swap') ? ( handleSwapFormBalanceClick( Number(floorToDecimal(balance, tokenBank.mintDecimals)) ) } > ) : ( ) ) : ( )}

) }