mango-v4-ui/components/shared/BalancesTable.tsx

355 lines
12 KiB
TypeScript
Raw Normal View History

import { Bank, Serum3Market } from '@blockworks-foundation/mango-v4'
2022-11-18 11:11:06 -08:00
import useJupiterMints from 'hooks/useJupiterMints'
2023-01-15 01:53:34 -08:00
import { NoSymbolIcon, QuestionMarkCircleIcon } from '@heroicons/react/20/solid'
import mangoStore from '@store/mangoStore'
2022-11-18 09:09:39 -08:00
import useMangoAccount from 'hooks/useMangoAccount'
2022-10-03 19:26:50 -07:00
import { useViewport } from 'hooks/useViewport'
import { useTranslation } from 'next-i18next'
2022-10-28 14:46:38 -07:00
import Image from 'next/legacy/image'
import { useRouter } from 'next/router'
import { useCallback, useMemo } from 'react'
import {
floorToDecimal,
formatDecimal,
formatFixedDecimals,
getDecimalCount,
trimDecimals,
} from 'utils/numbers'
2022-10-03 19:26:50 -07:00
import { breakpoints } from 'utils/theme'
import { calculateLimitPriceForMarketOrder } from 'utils/tradeForm'
import { LinkButton } from './Button'
2022-11-20 02:44:14 -08:00
import { Table, Td, Th, TrBody, TrHead } from './TableElements'
2022-11-20 12:20:27 -08:00
import useSelectedMarket from 'hooks/useSelectedMarket'
2022-11-20 12:32:38 -08:00
import useMangoGroup from 'hooks/useMangoGroup'
2023-01-08 19:38:09 -08:00
import AmountWithValue from './AmountWithValue'
2023-01-19 17:45:08 -08:00
import ConnectEmptyState from './ConnectEmptyState'
import { useWallet } from '@solana/wallet-adapter-react'
import Decimal from 'decimal.js'
const BalancesTable = () => {
2022-10-03 03:38:05 -07:00
const { t } = useTranslation(['common', 'trade'])
2023-01-19 19:10:15 -08:00
const { mangoAccount, mangoAccountAddress } = useMangoAccount()
const spotBalances = mangoStore((s) => s.mangoAccount.spotBalances)
2022-11-20 12:32:38 -08:00
const { group } = useMangoGroup()
2022-11-18 11:11:06 -08:00
const { mangoTokens } = useJupiterMints()
2022-10-03 19:26:50 -07:00
const { width } = useViewport()
2023-01-19 17:45:08 -08:00
const { connected } = useWallet()
2022-10-03 19:26:50 -07:00
const showTableView = width ? width > breakpoints.md : false
const banks = useMemo(() => {
2023-01-14 21:01:30 -08:00
if (!group || !mangoAccount) return []
const rawBanks = Array.from(group?.banksMapByName, ([key, value]) => ({
key,
value,
2023-01-14 21:01:30 -08:00
balance: mangoAccount.getTokenBalanceUi(value[0]),
}))
const sortedBanks = mangoAccount
? rawBanks
.sort(
(a, b) =>
2023-01-14 21:01:30 -08:00
Math.abs(b.balance * b.value[0].uiPrice) -
Math.abs(a.balance * a.value[0].uiPrice)
)
.filter((c) => {
return (
Math.abs(
2023-01-14 21:01:30 -08:00
floorToDecimal(c.balance, c.value[0].mintDecimals).toNumber()
) > 0 ||
spotBalances[c.value[0].mint.toString()]?.unsettled > 0 ||
spotBalances[c.value[0].mint.toString()]?.inOrders > 0
)
})
: rawBanks
return sortedBanks
2023-01-14 21:01:30 -08:00
}, [group, mangoAccount, spotBalances])
2022-11-04 06:14:50 -07:00
return banks?.length ? (
showTableView ? (
2022-11-20 02:44:14 -08:00
<Table>
2022-11-04 06:14:50 -07:00
<thead>
2022-11-20 02:44:14 -08:00
<TrHead>
<Th className="bg-th-bkg-1 text-left">{t('token')}</Th>
<Th className="bg-th-bkg-1 text-right">{t('balance')}</Th>
<Th className="bg-th-bkg-1 text-right">{t('trade:in-orders')}</Th>
<Th className="bg-th-bkg-1 text-right" id="trade-step-ten">
2022-11-04 06:14:50 -07:00
{t('trade:unsettled')}
2022-11-20 02:44:14 -08:00
</Th>
</TrHead>
2022-11-04 06:14:50 -07:00
</thead>
<tbody>
{banks.map(({ key, value }) => {
const bank = value[0]
let logoURI
2022-11-18 11:11:06 -08:00
if (mangoTokens.length) {
logoURI = mangoTokens.find(
2022-11-04 06:14:50 -07:00
(t) => t.address === bank.mint.toString()
2022-11-20 12:32:38 -08:00
)?.logoURI
2022-11-04 06:14:50 -07:00
}
2022-11-21 21:14:35 -08:00
const inOrders = spotBalances[bank.mint.toString()]?.inOrders || 0
const unsettled = spotBalances[bank.mint.toString()]?.unsettled || 0
2022-11-04 06:14:50 -07:00
return (
2022-11-20 02:44:14 -08:00
<TrBody key={key} className="text-sm">
<Td>
2022-11-04 06:14:50 -07:00
<div className="flex items-center">
<div className="mr-2.5 flex flex-shrink-0 items-center">
{logoURI ? (
<Image alt="" width="20" height="20" src={logoURI} />
) : (
<QuestionMarkCircleIcon className="h-7 w-7 text-th-fgd-3" />
)}
</div>
<span>{bank.name}</span>
</div>
2022-11-20 02:44:14 -08:00
</Td>
<Td className="text-right">
2022-11-17 14:28:02 -08:00
<Balance bank={bank} />
2022-11-04 06:14:50 -07:00
<p className="text-sm text-th-fgd-4">
{mangoAccount
? `${formatFixedDecimals(
2022-11-20 12:32:38 -08:00
mangoAccount.getTokenBalanceUi(bank) * bank.uiPrice,
2022-11-04 06:14:50 -07:00
true
)}`
: '$0.00'}
</p>
2022-11-20 02:44:14 -08:00
</Td>
2023-01-08 19:38:09 -08:00
<Td className="text-right">
<AmountWithValue
amount={
inOrders
? formatDecimal(Number(inOrders), bank.mintDecimals)
: '0'
}
value={formatFixedDecimals(
inOrders * bank.uiPrice,
true,
true
)}
stacked
/>
2022-11-20 02:44:14 -08:00
</Td>
2023-01-08 19:38:09 -08:00
<Td className="text-right">
<AmountWithValue
amount={
unsettled
? formatDecimal(Number(unsettled), bank.mintDecimals)
: '0'
}
value={formatFixedDecimals(
unsettled * bank.uiPrice,
true,
true
)}
stacked
/>
2022-11-20 02:44:14 -08:00
</Td>
</TrBody>
2022-11-04 06:14:50 -07:00
)
})}
</tbody>
2022-11-20 02:44:14 -08:00
</Table>
2022-11-04 06:14:50 -07:00
) : (
<>
{banks.map(({ key, value }) => {
const bank = value[0]
let logoURI
2022-11-18 11:11:06 -08:00
if (mangoTokens.length) {
logoURI = mangoTokens.find(
(t) => t.address === bank.mint.toString()
2022-11-20 12:32:38 -08:00
)?.logoURI
}
2022-11-21 21:14:35 -08:00
const inOrders = spotBalances[bank.mint.toString()]?.inOrders || 0
const unsettled = spotBalances[bank.mint.toString()]?.unsettled || 0
return (
2022-11-04 06:14:50 -07:00
<div
className="flex items-center justify-between border-b border-th-bkg-3 p-4"
key={key}
>
<div className="flex items-center">
<div className="mr-2.5 flex flex-shrink-0 items-center">
{logoURI ? (
<Image alt="" width="20" height="20" src={logoURI} />
) : (
<QuestionMarkCircleIcon className="h-7 w-7 text-th-fgd-3" />
)}
</div>
2022-11-04 06:14:50 -07:00
<span>{bank.name}</span>
</div>
<div className="text-right">
<div className="mb-0.5 flex justify-end space-x-1.5">
<Balance bank={bank} />
2022-11-04 06:14:50 -07:00
<span className="text-sm text-th-fgd-4">
{mangoAccount
? `${formatFixedDecimals(
2022-11-20 12:32:38 -08:00
mangoAccount.getTokenBalanceUi(bank) * bank.uiPrice,
2023-01-06 02:32:54 -08:00
false,
2022-11-04 06:14:50 -07:00
true
)}`
: '$0.00'}
2022-10-03 19:26:50 -07:00
</span>
</div>
2022-11-22 15:43:25 -08:00
<div className="flex space-x-2">
2022-11-04 06:14:50 -07:00
<p className="text-xs text-th-fgd-4">
{t('trade:in-orders')}:{' '}
2022-11-21 21:14:35 -08:00
<span className="font-mono text-th-fgd-3">{inOrders}</span>
2022-11-04 06:14:50 -07:00
</p>
<p className="text-xs text-th-fgd-4">
{t('trade:unsettled')}:{' '}
<span className="font-mono text-th-fgd-3">
2022-11-21 21:14:35 -08:00
{unsettled ? unsettled.toFixed(bank.mintDecimals) : 0}
2022-11-04 06:14:50 -07:00
</span>
</p>
</div>
2022-10-03 19:26:50 -07:00
</div>
</div>
2022-11-04 06:14:50 -07:00
)
})}
</>
)
2023-01-19 19:10:15 -08:00
) : mangoAccountAddress || connected ? (
2022-11-04 06:14:50 -07:00
<div className="flex flex-col items-center p-8">
2023-01-15 01:53:34 -08:00
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
2022-11-04 06:14:50 -07:00
<p>{t('trade:no-balances')}</p>
</div>
2023-01-19 17:45:08 -08:00
) : (
<div className="p-8">
<ConnectEmptyState text={t('connect-balances')} />
</div>
)
}
export default BalancesTable
const Balance = ({ bank }: { bank: Bank }) => {
2022-11-18 09:09:39 -08:00
const { mangoAccount } = useMangoAccount()
2022-11-20 12:20:27 -08:00
const { selectedMarket } = useSelectedMarket()
const { asPath } = useRouter()
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 tickSize: number
if (selectedMarket instanceof Serum3Market) {
const market = group.getSerum3ExternalMarket(
selectedMarket.serumMarketExternal
)
minOrderDecimals = getDecimalCount(market.minOrderSize)
tickSize = getDecimalCount(market.tickSize)
} else {
minOrderDecimals = getDecimalCount(selectedMarket.minOrderSize)
tickSize = getDecimalCount(selectedMarket.tickSize)
}
if (type === 'quote') {
const trimmedBalance = trimDecimals(balance, tickSize)
const baseSize = trimDecimals(trimmedBalance / price, minOrderDecimals)
const quoteSize = trimDecimals(baseSize * price, tickSize)
set((s) => {
s.tradeForm.baseSize = baseSize.toString()
s.tradeForm.quoteSize = quoteSize.toString()
})
} else {
const baseSize = trimDecimals(balance, minOrderDecimals)
const quoteSize = trimDecimals(baseSize * price, tickSize)
set((s) => {
s.tradeForm.baseSize = baseSize.toString()
s.tradeForm.quoteSize = quoteSize.toString()
})
}
},
[selectedMarket]
)
const handleSwapFormBalanceClick = useCallback(
(balance: number) => {
const set = mangoStore.getState().set
if (balance >= 0) {
set((s) => {
s.swap.inputBank = bank
s.swap.amountIn = balance.toString()
s.swap.amountOut = ''
s.swap.swapMode = 'ExactIn'
})
} else {
set((s) => {
s.swap.outputBank = bank
s.swap.amountIn = ''
s.swap.amountOut = Math.abs(balance).toString()
s.swap.swapMode = 'ExactOut'
})
}
},
[bank]
)
const balance = useMemo(() => {
return mangoAccount ? mangoAccount.getTokenBalanceUi(bank) : 0
}, [bank, mangoAccount])
const isBaseOrQuote = useMemo(() => {
2022-12-16 09:27:12 -08:00
if (selectedMarket instanceof Serum3Market) {
if (bank.tokenIndex === selectedMarket.baseTokenIndex) {
return 'base'
} else if (bank.tokenIndex === selectedMarket.quoteTokenIndex) {
return 'quote'
} else return ''
}
}, [bank, selectedMarket])
console.log(bank.name, ' balance', new Decimal(balance).toFixed())
2022-12-16 09:27:12 -08:00
if (!balance) return <p className="flex justify-end">0</p>
return (
<p className="flex justify-end">
2022-12-16 09:27:12 -08:00
{asPath.includes('/trade') && isBaseOrQuote ? (
<LinkButton
className="font-normal underline-offset-4"
2023-01-15 17:27:51 -08:00
onClick={() =>
handleTradeFormBalanceClick(Math.abs(balance), isBaseOrQuote)
}
2022-12-16 09:27:12 -08:00
>
{formatDecimal(balance, bank.mintDecimals)}
</LinkButton>
) : asPath.includes('/swap') ? (
<LinkButton
className="font-normal underline-offset-4"
2023-01-05 21:26:54 -08:00
onClick={() =>
handleSwapFormBalanceClick(
floorToDecimal(balance, bank.mintDecimals).toNumber()
)
}
2022-12-16 09:27:12 -08:00
>
{formatDecimal(balance, bank.mintDecimals)}
</LinkButton>
) : (
2022-12-16 09:27:12 -08:00
formatDecimal(balance, bank.mintDecimals)
)}
</p>
)
}