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 ? (
{t('token')} |
{t('balance')} |
{t('account:collateral-value')}
|
{t('trade:in-orders')} |
{t('trade:unsettled')}
|
{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 (
|
|
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 }) => (
<>
{t('account:collateral-value')}
{' '}
x
>
)}
)
})}
)
) : mangoAccountAddress || connected ? (
) : (
)
}
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))
)
}
>
) : (
)
) : (
)}
)
}