token table overview updates

This commit is contained in:
saml33 2024-05-23 13:51:07 +10:00
parent bedc864717
commit b1ef1563fa
8 changed files with 221 additions and 108 deletions

View File

@ -1,15 +1,15 @@
import Change from '@components/shared/Change'
import FormatNumericValue from '@components/shared/FormatNumericValue'
import TokenLogo from '@components/shared/TokenLogo'
import useListedMarketsWithMarketData, {
SerumMarketWithMarketData,
} from 'hooks/useListedMarketsWithMarketData'
import useMangoGroup from 'hooks/useMangoGroup'
import { useRouter } from 'next/router'
import { useMemo } from 'react'
import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { goToTokenPage } from '@components/stats/tokens/TokenOverviewTable'
import {
ArrowDownTrayIcon,
BoltIcon,
ChevronRightIcon,
FaceFrownIcon,
@ -25,6 +25,13 @@ import mangoStore from '@store/mangoStore'
import { goToPerpMarketDetails } from '@components/stats/perps/PerpMarketDetailsTable'
import MarketLogos from '@components/trade/MarketLogos'
import { TOKEN_REDUCE_ONLY_OPTIONS } from 'utils/constants'
import TableTokenName from '@components/shared/TableTokenName'
import { IconButton } from '@components/shared/Button'
import DepositWithdrawModal from '@components/modals/DepositWithdrawModal'
import useMangoAccount from 'hooks/useMangoAccount'
import { useWallet } from '@solana/wallet-adapter-react'
import CreateAccountModal from '@components/modals/CreateAccountModal'
import Tooltip from '@components/shared/Tooltip'
dayjs.extend(relativeTime)
export type BankWithMarketData = {
@ -39,6 +46,8 @@ const RecentGainersLosers = () => {
const { t } = useTranslation(['common', 'explore', 'trade'])
const router = useRouter()
const { group } = useMangoGroup()
const { mangoAccountAddress } = useMangoAccount()
const { connected } = useWallet()
const { banks } = useBanks()
const {
serumMarketsWithData,
@ -46,6 +55,8 @@ const RecentGainersLosers = () => {
isLoading: loadingSerumMarkets,
} = useListedMarketsWithMarketData()
const groupLoaded = mangoStore((s) => s.groupLoaded)
const [showDepositModal, setShowDepositModal] = useState('')
const [showCreateAccountModal, setShowCreateAccountModal] = useState(false)
const banksWithMarketData = useMemo(() => {
if (!banks.length || !group || !serumMarketsWithData.length) return []
@ -134,6 +145,14 @@ const RecentGainersLosers = () => {
return [gainers, losers]
}, [banksWithMarketData, perpMarketsWithData])
const handleDepositModal = (token: string) => {
if (mangoAccountAddress) {
setShowDepositModal(token)
} else {
setShowCreateAccountModal(true)
}
}
return (
<>
<div className="grid grid-cols-12 gap-4 px-4 md:px-6">
@ -152,37 +171,36 @@ const RecentGainersLosers = () => {
{groupLoaded ? (
<div className="border-t border-th-bkg-3">
{newlyListed.map((token) => {
const mintInfo = newlyListedMintInfo.find(
(info) => info.tokenIndex === token.tokenIndex,
)
let timeSinceListing = ''
if (mintInfo) {
timeSinceListing = dayjs().to(
mintInfo.registrationTime.toNumber() * 1000,
)
}
return (
<div
className="default-transition flex h-16 cursor-pointer items-center justify-between border-b border-th-bkg-3 px-4 md:hover:bg-th-bkg-2"
key={token.tokenIndex}
onClick={() =>
goToTokenPage(token.name.split(' ')[0], router)
}
>
<div className="flex items-center">
<TokenLogo bank={token} showRewardsLogo />
<p className="ml-3 font-body text-th-fgd-2">
{token.name}
</p>
</div>
<div className="flex items-center">
<div className="mr-3">
<span className="text-th-fgd-3">
{timeSinceListing}
</span>
<div className="relative" key={token.tokenIndex}>
<div
className="default-transition flex h-16 cursor-pointer items-center justify-between border-b border-th-bkg-3 px-4 md:hover:bg-th-bkg-2"
onClick={() =>
goToTokenPage(token.name.split(' ')[0], router)
}
>
<div className="flex items-center">
<TableTokenName
bank={token}
symbol={token.name}
showLeverage
hideReduceDesc
/>
</div>
<ChevronRightIcon className="h-5 w-5 text-th-fgd-3" />
</div>
{connected ? (
<div className="absolute right-12 top-4">
<Tooltip content={`${t('deposit')} ${token.name}`}>
<IconButton
onClick={() => handleDepositModal(token.name)}
size="small"
>
<ArrowDownTrayIcon className="h-4 w-4" />
</IconButton>
</Tooltip>
</div>
) : null}
</div>
)
})}
@ -219,18 +237,23 @@ const RecentGainersLosers = () => {
}
onClick={onClick}
>
<div className="flex items-center">
{bank ? (
<div className="mr-3">
<TokenLogo bank={bank} showRewardsLogo />
</div>
) : (
{bank ? (
<div className="mr-3">
<TableTokenName
bank={bank}
symbol={bank.name}
showLeverage
hideReduceDesc
/>
</div>
) : (
<div className="flex items-center">
<MarketLogos market={gainer?.market} size="large" />
)}
<p className="font-body text-th-fgd-2">
{bank?.name || gainer?.market?.name}
</p>
</div>
<p className="font-body text-th-fgd-2">
{gainer?.market?.name}
</p>
</div>
)}
<div className="flex items-center">
<div className="mr-3 flex flex-col items-end">
<span className="font-mono">
@ -282,16 +305,23 @@ const RecentGainersLosers = () => {
}
onClick={onClick}
>
<div className="flex items-center">
{bank ? (
<TokenLogo bank={bank} showRewardsLogo />
) : (
<MarketLogos market={loser?.market} />
)}
<p className="ml-3 font-body text-th-fgd-2">
{bank?.name || loser?.market?.name}
</p>
</div>
{bank ? (
<div className="mr-3">
<TableTokenName
bank={bank}
symbol={bank.name}
showLeverage
hideReduceDesc
/>
</div>
) : (
<div className="flex items-center">
<MarketLogos market={loser?.market} size="large" />
<p className="font-body text-th-fgd-2">
{loser?.market?.name}
</p>
</div>
)}
<div className="flex items-center">
<div className="mr-3 flex flex-col items-end">
<span className="font-mono">
@ -316,6 +346,20 @@ const RecentGainersLosers = () => {
)}
</div>
</div>
{showDepositModal ? (
<DepositWithdrawModal
action="deposit"
isOpen={!!showDepositModal}
onClose={() => setShowDepositModal('')}
token={showDepositModal}
/>
) : null}
{showCreateAccountModal ? (
<CreateAccountModal
isOpen={showCreateAccountModal}
onClose={() => setShowCreateAccountModal(false)}
/>
) : null}
</>
)
}

View File

@ -20,7 +20,8 @@ import useBanks from 'hooks/useBanks'
import SheenLoader from '@components/shared/SheenLoader'
import { useViewport } from 'hooks/useViewport'
import useLocalStorageState from 'hooks/useLocalStorageState'
import { TOKEN_WATCHLIST_KEY } from 'utils/constants'
import { TOKEN_REDUCE_ONLY_OPTIONS, TOKEN_WATCHLIST_KEY } from 'utils/constants'
import { DEFAULT_WATCHLIST } from './WatchlistButton'
export type BankWithMarketData = {
bank: Bank
@ -61,14 +62,31 @@ const sortTokens = (
return tokens.sort((a: BankWithMarketData, b: BankWithMarketData) => {
const aInWatchlist = watchlist.includes(a.bank.tokenIndex)
const bInWatchlist = watchlist.includes(b.bank.tokenIndex)
const aIsReduce = a.bank.reduceOnly === TOKEN_REDUCE_ONLY_OPTIONS.ENABLED
const bIsReduce = b.bank.reduceOnly === TOKEN_REDUCE_ONLY_OPTIONS.ENABLED
const aIsNoBorrow =
a.bank.reduceOnly === TOKEN_REDUCE_ONLY_OPTIONS.NO_BORROWS
const bIsNoBorrow =
b.bank.reduceOnly === TOKEN_REDUCE_ONLY_OPTIONS.NO_BORROWS
// Prioritize tokens in the watchlist
// prioritize tokens in the watchlist
if (aInWatchlist && !bInWatchlist) {
return -1 // a should come before b
} else if (!aInWatchlist && bInWatchlist) {
return 1 // b should come before a
}
if (!aIsReduce && bIsReduce) {
return -1 // a should come before b
} else if (aIsReduce && !bIsReduce) {
return 1 // b should come before a
}
if (!aIsNoBorrow && bIsNoBorrow) {
return -1 // a should come before b
} else if (aIsNoBorrow && !bIsNoBorrow) {
return 1 // b should come before a
}
let aValue: number | undefined
let bValue: number | undefined
if (sortByKey === 'change_24h') {
@ -94,7 +112,10 @@ const sortTokens = (
}
const Spot = () => {
const [watchlist] = useLocalStorageState(TOKEN_WATCHLIST_KEY, [])
const [watchlist] = useLocalStorageState(
TOKEN_WATCHLIST_KEY,
DEFAULT_WATCHLIST,
)
const { t } = useTranslation(['common', 'explore', 'trade'])
const { group } = useMangoGroup()
const { banks } = useBanks()

View File

@ -4,11 +4,9 @@ import Change from '@components/shared/Change'
import FormatNumericValue from '@components/shared/FormatNumericValue'
import TokenLogo from '@components/shared/TokenLogo'
import { goToTokenPage } from '@components/stats/tokens/TokenOverviewTable'
import Decimal from 'decimal.js'
import useMangoGroup from 'hooks/useMangoGroup'
import { useRouter } from 'next/router'
import { useTranslation } from 'react-i18next'
import { numberCompacter } from 'utils/numbers'
import { floorToDecimal, numberCompacter } from 'utils/numbers'
import { BankWithMarketData } from './Spot'
import Tooltip from '@components/shared/Tooltip'
import SimpleAreaChart from '@components/shared/SimpleAreaChart'
@ -18,10 +16,10 @@ import TokenReduceOnlyDesc from '@components/shared/TokenReduceOnlyDesc'
import CollateralWeightDisplay from '@components/shared/CollateralWeightDisplay'
import WatchlistButton from './WatchlistButton'
import TableRatesDisplay from '@components/shared/TableRatesDisplay'
import { LeverageMaxDisplay } from './SpotTable'
const SpotCards = ({ tokens }: { tokens: BankWithMarketData[] }) => {
const { t } = useTranslation(['common', 'explore', 'trade'])
const { group } = useMangoGroup()
const { theme } = useThemeWrapper()
const router = useRouter()
return (
@ -29,14 +27,6 @@ const SpotCards = ({ tokens }: { tokens: BankWithMarketData[] }) => {
{tokens.map((token) => {
const { bank } = token
const availableVaultBalance = group
? group.getTokenVaultBalanceByMintUi(bank.mint) -
bank.uiDeposits() * bank.minVaultToDepositsRatio
: 0
const available = Decimal.max(
0,
availableVaultBalance.toFixed(bank.mintDecimals),
).mul(bank.uiPrice)
const depositRate = bank.getDepositRateUi()
const borrowRate = bank.getBorrowRateUi()
const chartData = token?.market?.priceHistory?.length
@ -49,6 +39,10 @@ const SpotCards = ({ tokens }: { tokens: BankWithMarketData[] }) => {
const change = token.market?.rollingChange || 0
const weight = bank.scaledInitAssetWeight(bank.price)
const leverageFactor = 1 / (1 - weight.toNumber())
const leverageMax = floorToDecimal(leverageFactor, 1).toNumber()
return (
<div
className="col-span-12 rounded-lg border border-th-bkg-3 p-6 md:col-span-6 xl:col-span-4 2xl:col-span-3"
@ -114,15 +108,8 @@ const SpotCards = ({ tokens }: { tokens: BankWithMarketData[] }) => {
</p>
</div>
<div>
<Tooltip
content={t('tooltip-available', { token: bank.name })}
placement="top-start"
>
<p className="tooltip-underline mb-1">{t('available')}</p>
</Tooltip>
<span className="font-mono text-th-fgd-2">
<FormatNumericValue value={available} isUsd />
</span>
<p className="mb-1">{t('trade:max-leverage')}</p>
<LeverageMaxDisplay leverageMax={leverageMax} />
</div>
<div>
<Tooltip

View File

@ -10,7 +10,7 @@ import {
TrHead,
} from '@components/shared/TableElements'
import FormatNumericValue from '@components/shared/FormatNumericValue'
import { numberCompacter } from 'utils/numbers'
import { floorToDecimal, numberCompacter } from 'utils/numbers'
import SimpleAreaChart from '@components/shared/SimpleAreaChart'
import { Disclosure, Transition } from '@headlessui/react'
import { ChevronDownIcon, ChevronRightIcon } from '@heroicons/react/20/solid'
@ -26,7 +26,6 @@ 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'
@ -39,6 +38,9 @@ import useLocalStorageState from 'hooks/useLocalStorageState'
import { TOKEN_WATCHLIST_KEY } from 'utils/constants'
import TableRatesDisplay from '@components/shared/TableRatesDisplay'
export const GRADIENT_TEXT =
'bg-gradient-to-bl from-yellow-500 to-red-500 bg-clip-text text-transparent'
type TableData = {
assetWeight: string
available: Decimal
@ -55,6 +57,7 @@ type TableData = {
}[]
volume: number
isUp: boolean
leverageMax: number
}
const SpotTable = ({ tokens }: { tokens: BankWithMarketData[] }) => {
@ -91,6 +94,7 @@ const SpotTable = ({ tokens }: { tokens: BankWithMarketData[] }) => {
let depositRate = 0
let borrowRate = 0
let assetWeight = '0'
let leverageMax = 0
if (baseBank) {
availableVaultBalance = group
@ -107,6 +111,10 @@ const SpotTable = ({ tokens }: { tokens: BankWithMarketData[] }) => {
assetWeight = baseBank
.scaledInitAssetWeight(baseBank.price)
.toFixed(2)
const weight = baseBank.scaledInitAssetWeight(baseBank.price)
const leverageFactor = 1 / (1 - weight.toNumber())
leverageMax = floorToDecimal(leverageFactor, 1).toNumber()
}
const isUp =
@ -126,6 +134,7 @@ const SpotTable = ({ tokens }: { tokens: BankWithMarketData[] }) => {
tokenName,
volume,
isUp,
leverageMax,
}
formatted.push(data)
}
@ -188,15 +197,12 @@ const SpotTable = ({ tokens }: { tokens: BankWithMarketData[] }) => {
</Th>
<Th>
<div className="flex justify-end">
<Tooltip content={t('tooltip-available', { token: '' })}>
<SortableColumnHeader
sortKey="availableValue"
sort={() => requestSort('availableValue')}
sortConfig={sortConfig}
title={t('available')}
titleClass="tooltip-underline"
/>
</Tooltip>
<SortableColumnHeader
sortKey="leverageMax"
sort={() => requestSort('leverageMax')}
sortConfig={sortConfig}
title={t('trade:max-leverage')}
/>
</div>
</Th>
<Th>
@ -246,7 +252,6 @@ const SpotTable = ({ tokens }: { tokens: BankWithMarketData[] }) => {
})
.map((data) => {
const {
available,
baseBank,
borrowRate,
change,
@ -257,6 +262,7 @@ const SpotTable = ({ tokens }: { tokens: BankWithMarketData[] }) => {
tokenName,
volume,
isUp,
leverageMax,
} = data
if (!baseBank) return null
@ -272,7 +278,7 @@ const SpotTable = ({ tokens }: { tokens: BankWithMarketData[] }) => {
</td>
</tr>
<TrBody
className="default-transition border-t-0 md:hover:cursor-pointer md:hover:bg-th-bkg-2"
className="default-transition h-16 border-t-0 md:hover:cursor-pointer md:hover:bg-th-bkg-2"
onClick={() =>
goToTokenPage(tokenName.split(' ')[0], router)
}
@ -352,12 +358,7 @@ const SpotTable = ({ tokens }: { tokens: BankWithMarketData[] }) => {
</Td>
<Td>
<div className="flex flex-col text-right">
<BankAmountWithValue
amount={available}
bank={baseBank}
fixDecimals={false}
stacked
/>
<LeverageMaxDisplay leverageMax={leverageMax} />
</div>
</Td>
<Td className="font-mono">
@ -417,7 +418,6 @@ const MobileSpotItem = ({ data }: { data: TableData }) => {
const router = useRouter()
const {
available,
baseBank,
borrowRate,
change,
@ -428,6 +428,7 @@ const MobileSpotItem = ({ data }: { data: TableData }) => {
tokenName,
volume,
isUp,
leverageMax,
} = data
return (
@ -521,12 +522,10 @@ const MobileSpotItem = ({ data }: { data: TableData }) => {
</p>
</div>
<div className="col-span-1">
<p className="text-xs text-th-fgd-3">{t('available')}</p>
<BankAmountWithValue
amount={available}
bank={baseBank}
fixDecimals={false}
/>
<p className="text-xs text-th-fgd-3">
{t('trade:max-leverage')}
</p>
<LeverageMaxDisplay leverageMax={leverageMax} />
</div>
<div className="col-span-1">
<p className="text-xs text-th-fgd-3">
@ -566,3 +565,24 @@ const MobileSpotItem = ({ data }: { data: TableData }) => {
</Disclosure>
)
}
export const LeverageMaxDisplay = ({
leverageMax,
}: {
leverageMax: number | undefined
}) => {
return (
<span className="font-mono">
{leverageMax && leverageMax !== Infinity ? (
<span className={`${leverageMax >= 5 ? GRADIENT_TEXT : ''}`}>
{leverageMax < 2 && leverageMax > 1
? leverageMax.toFixed(1)
: leverageMax.toFixed()}
x
</span>
) : (
''
)}
</span>
)
}

View File

@ -3,6 +3,8 @@ import { TOKEN_WATCHLIST_KEY } from 'utils/constants'
import PinFill from '@components/icons/PinFill'
import PinOutline from '@components/icons/PinOutline'
export const DEFAULT_WATCHLIST = [4, 0]
const WatchlistButton = ({
tokenIndex,
className,
@ -12,7 +14,7 @@ const WatchlistButton = ({
}) => {
const [watchlist, setWatchlist] = useLocalStorageState(
TOKEN_WATCHLIST_KEY,
[],
DEFAULT_WATCHLIST,
)
const toggleWatchlist = (tokenIndex: number) => {

View File

@ -2,7 +2,7 @@ import { Bank } from '@blockworks-foundation/mango-v4'
import TokenLogo from './TokenLogo'
import TokenReduceOnlyDesc from './TokenReduceOnlyDesc'
import { useVaultLimits } from '@components/swap/useVaultLimits'
import { ExclamationTriangleIcon } from '@heroicons/react/20/solid'
import { Battery100Icon } from '@heroicons/react/20/solid'
import Tooltip from './Tooltip'
import { useTranslation } from 'react-i18next'
import { floorToDecimal } from 'utils/numbers'
@ -12,10 +12,12 @@ const TableTokenName = ({
bank,
symbol,
showLeverage,
hideReduceDesc,
}: {
bank: Bank
symbol: string
showLeverage?: boolean
hideReduceDesc?: boolean
}) => {
const { t } = useTranslation(['common', 'trade'])
const { vaultFull } = useVaultLimits(bank)
@ -31,8 +33,8 @@ const TableTokenName = ({
<div>
<div className="flex items-center">
<p className="font-body leading-none text-th-fgd-2">{symbol}</p>
{showLeverage && leverageMax > 1 && leverageMax < Infinity ? (
<div className="ml-1">
{showLeverage && leverageMax < Infinity ? (
<div className="ml-1.5">
<Tooltip content={t('trade:max-leverage')}>
<LeverageBadge leverage={leverageMax} />
</Tooltip>
@ -40,11 +42,11 @@ const TableTokenName = ({
) : null}
{vaultFull ? (
<Tooltip content={t('warning-deposits-full', { token: bank.name })}>
<ExclamationTriangleIcon className="ml-1 h-4 w-4 text-th-warning" />
<Battery100Icon className="ml-1.5 h-5 w-5 text-th-warning" />
</Tooltip>
) : null}
</div>
<TokenReduceOnlyDesc bank={bank} />
{hideReduceDesc ? null : <TokenReduceOnlyDesc bank={bank} />}
</div>
</div>
)

View File

@ -25,6 +25,8 @@ import { useSortableData } from 'hooks/useSortableData'
import TableTokenName from '@components/shared/TableTokenName'
import CollateralWeightDisplay from '@components/shared/CollateralWeightDisplay'
import OracleProvider from '@components/shared/OracleProvider'
import { floorToDecimal } from 'utils/numbers'
import { LeverageMaxDisplay } from '@components/explore/SpotTable'
const TokenDetailsTable = () => {
const { t } = useTranslation([
@ -56,6 +58,9 @@ const TokenDetailsTable = () => {
const [oracleProvider, oracleLinkPath] = getOracleProvider(bank)
const symbol = bank.name
const collateralFeeRate = bank.collateralFeePerDay * 100
const weight = bank.scaledInitAssetWeight(bank.price)
const leverageFactor = 1 / (1 - weight.toNumber())
const leverageMax = floorToDecimal(leverageFactor, 1).toNumber()
const data = {
bank,
@ -69,6 +74,7 @@ const TokenDetailsTable = () => {
oracleProvider,
symbol,
collateralFeeRate,
leverageMax,
}
formatted.push(data)
}
@ -111,6 +117,16 @@ const TokenDetailsTable = () => {
</Tooltip>
</div>
</Th>
<Th>
<div className="flex justify-end">
<SortableColumnHeader
sortKey="leverageMax"
sort={() => requestSort('leverageMax')}
sortConfig={sortConfig}
title={t('trade:max-leverage')}
/>
</div>
</Th>
<Th>
<div className="flex justify-end">
<Tooltip content={t('tooltip-borrow-fee')}>
@ -204,6 +220,7 @@ const TokenDetailsTable = () => {
loanOriginationFee,
symbol,
collateralFeeRate,
leverageMax,
} = data
return (
@ -226,6 +243,11 @@ const TokenDetailsTable = () => {
<p>{initLiabWeight.toFixed(2)}x</p>
</div>
</Td>
<Td>
<div className="flex flex-col text-right">
<LeverageMaxDisplay leverageMax={leverageMax} />
</div>
</Td>
<Td>
<p className="text-right">
{loanOriginationFee.toFixed(2)}%
@ -268,6 +290,7 @@ const TokenDetailsTable = () => {
liquidationFee,
loanOriginationFee,
collateralFeeRate,
leverageMax,
} = data
return (
<Disclosure key={bank.name}>
@ -313,6 +336,12 @@ const TokenDetailsTable = () => {
<span>{initLiabWeight.toFixed(2)}x</span>
</div>
</div>
<div className="col-span-1">
<p className="text-xs text-th-fgd-3">
{t('trade:max-leverage')}
</p>
<LeverageMaxDisplay leverageMax={leverageMax} />
</div>
<div className="col-span-1">
<Tooltip
content={t('tooltip-borrow-fee')}

View File

@ -41,6 +41,7 @@ import { TOKEN_REDUCE_ONLY_OPTIONS } from 'utils/constants'
import { isBankVisibleForUser } from 'utils/bank'
import Decimal from 'decimal.js'
import useMangoAccount from 'hooks/useMangoAccount'
import { GRADIENT_TEXT } from '@components/explore/SpotTable'
type Currencies = {
[key: string]: string
@ -526,8 +527,15 @@ export default MarketSelectDropdown
export const LeverageBadge = ({ leverage }: { leverage: number }) => {
return (
<div className="rounded bg-th-bkg-3 px-1 py-0.5 font-mono text-xxs leading-none text-th-fgd-2">
<span>{leverage < 2 ? leverage.toFixed(1) : leverage.toFixed()}x</span>
<div
className={`rounded bg-th-bkg-3 px-1 py-0.5 font-mono text-xs leading-none`}
>
<span className={leverage >= 5 ? GRADIENT_TEXT : 'text-th-fgd-3'}>
{leverage > 1 && leverage < 2
? leverage.toFixed(1)
: leverage.toFixed()}
x
</span>
</div>
)
}