2022-11-15 21:09:55 -08:00
|
|
|
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'
|
2022-09-28 13:02:12 -07:00
|
|
|
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'
|
2022-09-28 13:02:12 -07:00
|
|
|
import { useTranslation } from 'next-i18next'
|
2022-10-28 14:46:38 -07:00
|
|
|
import Image from 'next/legacy/image'
|
2022-11-15 21:09:55 -08:00
|
|
|
import { useRouter } from 'next/router'
|
2022-12-07 13:33:35 -08:00
|
|
|
import { useCallback, useMemo } from 'react'
|
2022-11-20 21:01:44 -08:00
|
|
|
import {
|
|
|
|
floorToDecimal,
|
2023-01-23 21:17:08 -08:00
|
|
|
formatNumericValue,
|
2022-11-22 17:45:26 -08:00
|
|
|
getDecimalCount,
|
2022-11-20 21:01:44 -08:00
|
|
|
} from 'utils/numbers'
|
2022-10-03 19:26:50 -07:00
|
|
|
import { breakpoints } from 'utils/theme'
|
2022-12-14 13:24:08 -08:00
|
|
|
import { calculateLimitPriceForMarketOrder } from 'utils/tradeForm'
|
2022-11-15 21:09:55 -08:00
|
|
|
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-19 17:45:08 -08:00
|
|
|
import ConnectEmptyState from './ConnectEmptyState'
|
|
|
|
import { useWallet } from '@solana/wallet-adapter-react'
|
2023-01-20 15:21:57 -08:00
|
|
|
import Decimal from 'decimal.js'
|
2023-01-23 21:17:08 -08:00
|
|
|
import FormatNumericValue from './FormatNumericValue'
|
2023-01-28 17:13:36 -08:00
|
|
|
import BankAmountWithValue from './BankAmountWithValue'
|
2022-09-28 13:02:12 -07:00
|
|
|
|
2022-11-17 14:16:45 -08:00
|
|
|
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()
|
2022-09-28 13:02:12 -07:00
|
|
|
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
|
2022-09-28 13:02:12 -07:00
|
|
|
|
|
|
|
const banks = useMemo(() => {
|
2023-01-14 21:01:30 -08:00
|
|
|
if (!group || !mangoAccount) return []
|
2022-11-20 21:01:44 -08:00
|
|
|
|
|
|
|
const rawBanks = Array.from(group?.banksMapByName, ([key, value]) => ({
|
|
|
|
key,
|
|
|
|
value,
|
2023-01-14 21:01:30 -08:00
|
|
|
balance: mangoAccount.getTokenBalanceUi(value[0]),
|
2022-11-20 21:01:44 -08:00
|
|
|
}))
|
|
|
|
const sortedBanks = mangoAccount
|
|
|
|
? rawBanks
|
|
|
|
.sort(
|
2022-09-28 13:02:12 -07:00
|
|
|
(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)
|
2022-11-20 21:01:44 -08:00
|
|
|
)
|
2023-01-02 12:43:31 -08:00
|
|
|
.filter((c) => {
|
|
|
|
return (
|
2022-09-28 13:02:12 -07:00
|
|
|
Math.abs(
|
2023-01-14 21:01:30 -08:00
|
|
|
floorToDecimal(c.balance, c.value[0].mintDecimals).toNumber()
|
2023-01-02 12:43:31 -08:00
|
|
|
) > 0 ||
|
|
|
|
spotBalances[c.value[0].mint.toString()]?.unsettled > 0 ||
|
|
|
|
spotBalances[c.value[0].mint.toString()]?.inOrders > 0
|
|
|
|
)
|
|
|
|
})
|
2022-11-20 21:01:44 -08:00
|
|
|
: rawBanks
|
2022-09-28 13:02:12 -07:00
|
|
|
|
2022-11-20 21:01:44 -08:00
|
|
|
return sortedBanks
|
2023-01-14 21:01:30 -08:00
|
|
|
}, [group, mangoAccount, spotBalances])
|
2022-09-28 13:02:12 -07:00
|
|
|
|
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">
|
2023-01-23 21:17:08 -08:00
|
|
|
<FormatNumericValue
|
|
|
|
value={
|
|
|
|
mangoAccount
|
|
|
|
? mangoAccount.getTokenBalanceUi(bank) * bank.uiPrice
|
|
|
|
: 0
|
|
|
|
}
|
|
|
|
isUsd
|
|
|
|
/>
|
2022-11-04 06:14:50 -07:00
|
|
|
</p>
|
2022-11-20 02:44:14 -08:00
|
|
|
</Td>
|
2023-01-08 19:38:09 -08:00
|
|
|
<Td className="text-right">
|
2023-01-28 17:13:36 -08:00
|
|
|
<BankAmountWithValue amount={inOrders} bank={bank} stacked />
|
2022-11-20 02:44:14 -08:00
|
|
|
</Td>
|
2023-01-08 19:38:09 -08:00
|
|
|
<Td className="text-right">
|
2023-01-28 17:13:36 -08:00
|
|
|
<BankAmountWithValue amount={unsettled} bank={bank} 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
|
|
|
) : (
|
|
|
|
<>
|
2022-09-28 13:02:12 -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(
|
2022-09-28 13:02:12 -07:00
|
|
|
(t) => t.address === bank.mint.toString()
|
2022-11-20 12:32:38 -08:00
|
|
|
)?.logoURI
|
2022-09-28 13:02:12 -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-10-06 20:44:18 -07:00
|
|
|
|
2022-09-28 13:02:12 -07:00
|
|
|
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" />
|
|
|
|
)}
|
2022-09-28 13:02:12 -07:00
|
|
|
</div>
|
2022-11-04 06:14:50 -07:00
|
|
|
<span>{bank.name}</span>
|
|
|
|
</div>
|
|
|
|
<div className="text-right">
|
2022-11-20 21:01:44 -08:00
|
|
|
<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">
|
2023-01-23 21:17:08 -08:00
|
|
|
<FormatNumericValue
|
|
|
|
value={
|
|
|
|
mangoAccount
|
|
|
|
? mangoAccount.getTokenBalanceUi(bank) * bank.uiPrice
|
|
|
|
: 0
|
|
|
|
}
|
|
|
|
isUsd
|
|
|
|
/>
|
2022-10-03 19:26:50 -07:00
|
|
|
</span>
|
2022-11-20 21:01:44 -08:00
|
|
|
</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')}:{' '}
|
2023-01-23 21:17:08 -08:00
|
|
|
<span className="font-mono text-th-fgd-3">
|
|
|
|
<FormatNumericValue
|
|
|
|
value={inOrders}
|
|
|
|
decimals={bank.mintDecimals}
|
|
|
|
/>
|
|
|
|
</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">
|
2023-01-23 21:17:08 -08:00
|
|
|
<FormatNumericValue
|
|
|
|
value={unsettled}
|
|
|
|
decimals={bank.mintDecimals}
|
|
|
|
/>
|
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>
|
2022-09-28 13:02:12 -07:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-11-17 14:16:45 -08:00
|
|
|
export default BalancesTable
|
2022-11-15 21:09:55 -08:00
|
|
|
|
|
|
|
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()
|
2022-11-15 21:09:55 -08:00
|
|
|
const { asPath } = useRouter()
|
|
|
|
|
2022-12-07 13:33:35 -08:00
|
|
|
const handleTradeFormBalanceClick = useCallback(
|
|
|
|
(balance: number, type: 'base' | 'quote') => {
|
|
|
|
const set = mangoStore.getState().set
|
|
|
|
const group = mangoStore.getState().group
|
|
|
|
const tradeForm = mangoStore.getState().tradeForm
|
2022-11-15 21:09:55 -08:00
|
|
|
|
2022-12-07 13:33:35 -08:00
|
|
|
if (!group || !selectedMarket) return
|
2022-11-22 17:45:26 -08:00
|
|
|
|
2022-12-07 13:33:35 -08:00
|
|
|
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'
|
2022-12-14 13:24:08 -08:00
|
|
|
price = calculateLimitPriceForMarketOrder(orderbook, balance, side)
|
2022-12-07 13:33:35 -08:00
|
|
|
} else {
|
2022-12-14 12:54:25 -08:00
|
|
|
price = Number(tradeForm.price)
|
2022-12-07 13:33:35 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
let minOrderDecimals: number
|
2023-01-24 19:11:42 -08:00
|
|
|
let tickDecimals: number
|
2022-12-07 13:33:35 -08:00
|
|
|
if (selectedMarket instanceof Serum3Market) {
|
|
|
|
const market = group.getSerum3ExternalMarket(
|
|
|
|
selectedMarket.serumMarketExternal
|
|
|
|
)
|
|
|
|
minOrderDecimals = getDecimalCount(market.minOrderSize)
|
2023-01-24 19:11:42 -08:00
|
|
|
tickDecimals = getDecimalCount(market.tickSize)
|
2022-12-07 13:33:35 -08:00
|
|
|
} else {
|
|
|
|
minOrderDecimals = getDecimalCount(selectedMarket.minOrderSize)
|
2023-01-24 19:11:42 -08:00
|
|
|
tickDecimals = getDecimalCount(selectedMarket.tickSize)
|
2022-12-07 13:33:35 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (type === 'quote') {
|
2023-01-24 19:11:42 -08:00
|
|
|
const floorBalance = floorToDecimal(balance, tickDecimals).toNumber()
|
|
|
|
const baseSize = floorToDecimal(
|
|
|
|
floorBalance / price,
|
|
|
|
minOrderDecimals
|
|
|
|
).toNumber()
|
|
|
|
const quoteSize = floorToDecimal(baseSize * price, tickDecimals)
|
2022-12-07 13:33:35 -08:00
|
|
|
set((s) => {
|
|
|
|
s.tradeForm.baseSize = baseSize.toString()
|
|
|
|
s.tradeForm.quoteSize = quoteSize.toString()
|
|
|
|
})
|
|
|
|
} else {
|
2023-01-24 19:11:42 -08:00
|
|
|
const baseSize = floorToDecimal(balance, minOrderDecimals).toNumber()
|
|
|
|
const quoteSize = floorToDecimal(baseSize * price, tickDecimals)
|
2022-12-07 13:33:35 -08:00
|
|
|
set((s) => {
|
|
|
|
s.tradeForm.baseSize = baseSize.toString()
|
|
|
|
s.tradeForm.quoteSize = quoteSize.toString()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
},
|
|
|
|
[selectedMarket]
|
|
|
|
)
|
2022-11-15 21:09:55 -08:00
|
|
|
|
2022-12-18 11:44:02 -08:00
|
|
|
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]
|
|
|
|
)
|
2022-11-15 21:09:55 -08:00
|
|
|
|
|
|
|
const balance = useMemo(() => {
|
|
|
|
return mangoAccount ? mangoAccount.getTokenBalanceUi(bank) : 0
|
2022-12-07 13:33:35 -08:00
|
|
|
}, [bank, mangoAccount])
|
2022-11-15 21:09:55 -08:00
|
|
|
|
|
|
|
const isBaseOrQuote = useMemo(() => {
|
2022-12-16 09:27:12 -08:00
|
|
|
if (selectedMarket instanceof Serum3Market) {
|
2022-11-15 21:09:55 -08:00
|
|
|
if (bank.tokenIndex === selectedMarket.baseTokenIndex) {
|
|
|
|
return 'base'
|
|
|
|
} else if (bank.tokenIndex === selectedMarket.quoteTokenIndex) {
|
|
|
|
return 'quote'
|
|
|
|
} else return ''
|
|
|
|
}
|
|
|
|
}, [bank, selectedMarket])
|
|
|
|
|
2023-01-20 15:21:57 -08:00
|
|
|
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>
|
2022-12-07 13:33:35 -08:00
|
|
|
|
2022-11-15 21:09:55 -08:00
|
|
|
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
|
|
|
>
|
2023-01-23 21:17:08 -08:00
|
|
|
<FormatNumericValue value={balance} decimals={bank.mintDecimals} />
|
2022-12-16 09:27:12 -08:00
|
|
|
</LinkButton>
|
|
|
|
) : asPath.includes('/swap') ? (
|
|
|
|
<LinkButton
|
|
|
|
className="font-normal underline-offset-4"
|
2023-01-05 21:26:54 -08:00
|
|
|
onClick={() =>
|
|
|
|
handleSwapFormBalanceClick(
|
2023-01-23 21:17:08 -08:00
|
|
|
Number(formatNumericValue(balance, bank.mintDecimals))
|
2023-01-05 21:26:54 -08:00
|
|
|
)
|
|
|
|
}
|
2022-12-16 09:27:12 -08:00
|
|
|
>
|
2023-01-23 21:17:08 -08:00
|
|
|
<FormatNumericValue value={balance} decimals={bank.mintDecimals} />
|
2022-12-16 09:27:12 -08:00
|
|
|
</LinkButton>
|
2022-11-15 21:09:55 -08:00
|
|
|
) : (
|
2023-01-23 21:17:08 -08:00
|
|
|
<FormatNumericValue value={balance} decimals={bank.mintDecimals} />
|
2022-11-15 21:09:55 -08:00
|
|
|
)}
|
|
|
|
</p>
|
|
|
|
)
|
|
|
|
}
|