import { useTranslation } from 'next-i18next' import { Fragment, useCallback } from 'react' import useMangoGroup from 'hooks/useMangoGroup' import { SortableColumnHeader, Table, Td, Th, TrBody, TrHead, } from '@components/shared/TableElements' import FormatNumericValue from '@components/shared/FormatNumericValue' import { numberCompacter } from 'utils/numbers' import SimpleAreaChart from '@components/shared/SimpleAreaChart' import { Disclosure, Transition } from '@headlessui/react' import { ChevronDownIcon, ChevronRightIcon } from '@heroicons/react/20/solid' import useThemeWrapper from 'hooks/useThemeWrapper' import { useSortableData } from 'hooks/useSortableData' import Change from '@components/shared/Change' import { Bank } from '@blockworks-foundation/mango-v4' import { useViewport } from 'hooks/useViewport' import { breakpoints } from 'utils/theme' import ContentBox from '@components/shared/ContentBox' import { COLORS } from 'styles/colors' import TokenLogo from '@components/shared/TokenLogo' import { goToTokenPage } from '@components/stats/tokens/TokenOverviewTable' import { useRouter } from 'next/router' import Decimal from 'decimal.js' import BankAmountWithValue from '@components/shared/BankAmountWithValue' import { BankWithMarketData } from './Spot' import { SerumMarketWithMarketData } from 'hooks/useListedMarketsWithMarketData' import Tooltip from '@components/shared/Tooltip' import TableTokenName from '@components/shared/TableTokenName' import { LinkButton } from '@components/shared/Button' import { formatTokenSymbol } from 'utils/tokens' import CollateralWeightDisplay from '@components/shared/CollateralWeightDisplay' import WatchlistButton from './WatchlistButton' import useLocalStorageState from 'hooks/useLocalStorageState' import { TOKEN_WATCHLIST_KEY } from 'utils/constants' import TableRatesDisplay from '@components/shared/TableRatesDisplay' type TableData = { assetWeight: string available: Decimal baseBank: Bank borrowRate: number change: number depositRate: number tokenName: string market: SerumMarketWithMarketData | undefined price: number priceHistory: { price: number time: number }[] volume: number isUp: boolean } const SpotTable = ({ tokens }: { tokens: BankWithMarketData[] }) => { const { t } = useTranslation('common') const { group } = useMangoGroup() const { theme } = useThemeWrapper() const { width } = useViewport() const router = useRouter() const [watchlist] = useLocalStorageState(TOKEN_WATCHLIST_KEY, []) const showTableView = width ? width > breakpoints.md : false const formattedTableData = useCallback( (tokens: BankWithMarketData[]) => { const formatted = [] for (const token of tokens) { const baseBank = token.bank const price = baseBank.uiPrice const priceHistory = token?.market?.priceHistory?.length ? token.market.priceHistory ?.sort((a, b) => a.time - b.time) .concat([{ price: price, time: Date.now() }]) : [] const volume = token.market?.marketData?.quote_volume_24h || 0 const change = token.market?.rollingChange || 0 const tokenName = baseBank.name let availableVaultBalance = 0 let available = new Decimal(0) let availableValue = 0 let depositRate = 0 let borrowRate = 0 let assetWeight = '0' if (baseBank) { availableVaultBalance = group ? group.getTokenVaultBalanceByMintUi(baseBank.mint) - baseBank.uiDeposits() * baseBank.minVaultToDepositsRatio : 0 available = Decimal.max( 0, availableVaultBalance.toFixed(baseBank.mintDecimals), ) availableValue = available.mul(price).toNumber() depositRate = baseBank.getDepositRateUi() borrowRate = baseBank.getBorrowRateUi() assetWeight = baseBank .scaledInitAssetWeight(baseBank.price) .toFixed(2) } const isUp = price && priceHistory.length ? price >= priceHistory[0].price : false const data = { available, availableValue, assetWeight, borrowRate, baseBank, change, depositRate, market: token.market, price, priceHistory, tokenName, volume, isUp, } formatted.push(data) } return formatted }, [group], ) const { items: tableData, requestSort, sortConfig, } = useSortableData(formattedTableData(tokens)) return ( {showTableView ? (
{tableData .sort((a: TableData, b: TableData) => { const aInWatchlist = watchlist.includes(a.baseBank.tokenIndex) const bInWatchlist = watchlist.includes(b.baseBank.tokenIndex) if (aInWatchlist && !bInWatchlist) { return -1 } else if (!aInWatchlist && bInWatchlist) { return 1 } else { return 0 } }) .map((data) => { const { available, baseBank, borrowRate, change, depositRate, market, price, priceHistory, tokenName, volume, isUp, } = data if (!baseBank) return null return ( goToTokenPage(tokenName.split(' ')[0], router) } > ) })}
requestSort('tokenName')} sortConfig={sortConfig} title={t('token')} />
requestSort('price')} sortConfig={sortConfig} title={t('price')} />
requestSort('change')} sortConfig={sortConfig} title={t('rolling-change')} />
requestSort('volume')} sortConfig={sortConfig} title={t('trade:24h-volume')} />
requestSort('availableValue')} sortConfig={sortConfig} title={t('available')} titleClass="tooltip-underline" />
requestSort('assetWeight')} sortConfig={sortConfig} title={t('explore:collateral-weight')} titleClass="tooltip-underline" />
requestSort('depositRate')} sortConfig={sortConfig} title={t('rates')} titleClass="tooltip-underline" />

{price ? ( <> ) : ( '–' )}

{market && price ? ( ) : ( )}
{priceHistory.length ? (
) : (

{t('unavailable')}

)}

{!market || !market?.marketData ? ( '–' ) : volume ? ( {numberCompacter.format(volume)}{' '} USDC ) : ( 0{' '} USDC )}

) : (
{tableData .sort((a: TableData, b: TableData) => { const aInWatchlist = watchlist.includes(a.baseBank.tokenIndex) const bInWatchlist = watchlist.includes(b.baseBank.tokenIndex) if (aInWatchlist && !bInWatchlist) { return -1 } else if (!aInWatchlist && bInWatchlist) { return 1 } else { return 0 } }) .map((data) => { return })}
)}
) } export default SpotTable const MobileSpotItem = ({ data }: { data: TableData }) => { const { t } = useTranslation('common') const { theme } = useThemeWrapper() const router = useRouter() const { available, baseBank, borrowRate, change, depositRate, market, price, priceHistory, tokenName, volume, isUp, } = data return ( {({ open }) => ( <>

{tokenName}

{priceHistory.length ? (
) : (

{t('unavailable')}

)}

{price ? ( ) : ( '-' )}

{market ? : null}

{t('trade:24h-volume')}

{!market ? ( '–' ) : volume ? ( {numberCompacter.format(volume)}{' '} USDC ) : ( 0 USDC )}

{t('available')}

{t('explore:collateral-weight')}

{t('rates')}

goToTokenPage(baseBank.name.split(' ')[0], router) } > {t('token:token-stats', { token: formatTokenSymbol(baseBank.name), })}
)}
) }