sort markets by volume
This commit is contained in:
parent
5b38739e1b
commit
3c492c893e
|
@ -1,10 +1,11 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { Listbox } from '@headlessui/react'
|
||||
import { ChevronDownIcon } from '@heroicons/react/20/solid'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
interface SelectProps {
|
||||
value: string | ReactNode
|
||||
onChange: (x: string) => void
|
||||
onChange: (x: any) => void
|
||||
children: ReactNode
|
||||
className?: string
|
||||
dropdownPanelClassName?: string
|
||||
|
|
|
@ -3,7 +3,7 @@ import { useTranslation } from 'next-i18next'
|
|||
const SoonBadge = () => {
|
||||
const { t } = useTranslation('common')
|
||||
return (
|
||||
<div className="flex items-center rounded-full border border-th-active px-1.5 py-0.5 text-xs uppercase text-th-active">
|
||||
<div className="flex items-center rounded-full border border-th-active px-1.5 py-0.5 text-xxs uppercase text-th-active leading-none">
|
||||
{t('soon')}™
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { PerpMarket } from '@blockworks-foundation/mango-v4'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useViewport } from '../../hooks/useViewport'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { COLORS } from '../../styles/colors'
|
||||
import { breakpoints } from '../../utils/theme'
|
||||
import ContentBox from '../shared/ContentBox'
|
||||
|
@ -15,16 +14,17 @@ import { ChevronDownIcon, ChevronRightIcon } from '@heroicons/react/20/solid'
|
|||
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
||||
import { getDecimalCount, numberCompacter } from 'utils/numbers'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import { MarketData } from 'types'
|
||||
import { NextRouter, useRouter } from 'next/router'
|
||||
import SimpleAreaChart from '@components/shared/SimpleAreaChart'
|
||||
import { Disclosure, Transition } from '@headlessui/react'
|
||||
import { LinkButton } from '@components/shared/Button'
|
||||
import SoonBadge from '@components/shared/SoonBadge'
|
||||
import useMarketsData from 'hooks/useMarketsData'
|
||||
import { useMemo } from 'react'
|
||||
import MarketChange from '@components/shared/MarketChange'
|
||||
import useThemeWrapper from 'hooks/useThemeWrapper'
|
||||
import useListedMarketsWithMarketData, {
|
||||
PerpMarketWithMarketData,
|
||||
} from 'hooks/useListedMarketsWithMarketData'
|
||||
import { sortPerpMarkets } from 'utils/markets'
|
||||
|
||||
export const goToPerpMarketDetails = (
|
||||
market: PerpMarket,
|
||||
|
@ -36,18 +36,13 @@ export const goToPerpMarketDetails = (
|
|||
|
||||
const PerpMarketsOverviewTable = () => {
|
||||
const { t } = useTranslation(['common', 'trade'])
|
||||
const perpMarkets = mangoStore((s) => s.perpMarkets)
|
||||
const { theme } = useThemeWrapper()
|
||||
const { width } = useViewport()
|
||||
const showTableView = width ? width > breakpoints.md : false
|
||||
const rate = usePerpFundingRate()
|
||||
const router = useRouter()
|
||||
const { data: marketsData, isLoading, isFetching } = useMarketsData()
|
||||
|
||||
const perpData: MarketData = useMemo(() => {
|
||||
if (!marketsData) return []
|
||||
return marketsData?.perpData || []
|
||||
}, [marketsData])
|
||||
const { perpMarketsWithData, isLoading, isFetching } =
|
||||
useListedMarketsWithMarketData()
|
||||
|
||||
const loadingMarketData = isLoading || isFetching
|
||||
|
||||
|
@ -68,191 +63,193 @@ const PerpMarketsOverviewTable = () => {
|
|||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{perpMarkets.map((market) => {
|
||||
const symbol = market.name.split('-')[0]
|
||||
{sortPerpMarkets(perpMarketsWithData, 'quote_volume_24h').map(
|
||||
(market) => {
|
||||
const symbol = market.name.split('-')[0]
|
||||
|
||||
const perpDataEntries = Object.entries(perpData).find(
|
||||
(e) => e[0].toLowerCase() === market.name.toLowerCase(),
|
||||
)
|
||||
const marketData = perpDataEntries
|
||||
? perpDataEntries[1][0]
|
||||
: undefined
|
||||
const priceHistory = market?.marketData?.price_history
|
||||
|
||||
const priceHistory = marketData?.price_history
|
||||
const volumeData = market?.marketData?.quote_volume_24h
|
||||
|
||||
const volume = marketData?.quote_volume_24h
|
||||
? marketData.quote_volume_24h
|
||||
: 0
|
||||
const volume = volumeData ? volumeData : 0
|
||||
|
||||
let fundingRate
|
||||
let fundingRateApr
|
||||
if (rate.isSuccess) {
|
||||
const marketRate = rate?.data?.find(
|
||||
(r) => r.market_index === market.perpMarketIndex,
|
||||
)
|
||||
if (marketRate) {
|
||||
fundingRate = formatFunding.format(
|
||||
marketRate.funding_rate_hourly,
|
||||
)
|
||||
fundingRateApr = formatFunding.format(
|
||||
marketRate.funding_rate_hourly * 8760,
|
||||
let fundingRate
|
||||
let fundingRateApr
|
||||
if (rate.isSuccess) {
|
||||
const marketRate = rate?.data?.find(
|
||||
(r) => r.market_index === market.perpMarketIndex,
|
||||
)
|
||||
if (marketRate) {
|
||||
fundingRate = formatFunding.format(
|
||||
marketRate.funding_rate_hourly,
|
||||
)
|
||||
fundingRateApr = formatFunding.format(
|
||||
marketRate.funding_rate_hourly * 8760,
|
||||
)
|
||||
} else {
|
||||
fundingRate = '–'
|
||||
fundingRateApr = '–'
|
||||
}
|
||||
} else {
|
||||
fundingRate = '–'
|
||||
fundingRateApr = '–'
|
||||
}
|
||||
} else {
|
||||
fundingRate = '–'
|
||||
fundingRateApr = '–'
|
||||
}
|
||||
|
||||
const openInterest = market.baseLotsToUi(market.openInterest)
|
||||
const isComingSoon = market.oracleLastUpdatedSlot == 0
|
||||
const isUp =
|
||||
priceHistory && priceHistory.length
|
||||
? market.uiPrice >= priceHistory[0].price
|
||||
: false
|
||||
const openInterest = market.baseLotsToUi(market.openInterest)
|
||||
const isComingSoon = market.oracleLastUpdatedSlot == 0
|
||||
const isUp =
|
||||
priceHistory && priceHistory.length
|
||||
? market.uiPrice >= priceHistory[0].price
|
||||
: false
|
||||
|
||||
return (
|
||||
<TrBody
|
||||
className="default-transition md:hover:cursor-pointer md:hover:bg-th-bkg-2"
|
||||
key={market.publicKey.toString()}
|
||||
onClick={() => goToPerpMarketDetails(market, router)}
|
||||
>
|
||||
<Td>
|
||||
<div className="flex items-center">
|
||||
<MarketLogos market={market} size="large" />
|
||||
<p className="mr-2 whitespace-nowrap font-body">
|
||||
{market.name}
|
||||
</p>
|
||||
{isComingSoon ? <SoonBadge /> : null}
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex flex-col text-right">
|
||||
<p>
|
||||
{market.uiPrice ? (
|
||||
<FormatNumericValue value={market.uiPrice} isUsd />
|
||||
) : (
|
||||
'–'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex flex-col items-end">
|
||||
<MarketChange market={market} />
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
{!loadingMarketData ? (
|
||||
priceHistory && priceHistory.length ? (
|
||||
<div className="h-10 w-24">
|
||||
<SimpleAreaChart
|
||||
color={isUp ? COLORS.UP[theme] : COLORS.DOWN[theme]}
|
||||
data={priceHistory.concat([
|
||||
{
|
||||
time: new Date().toString(),
|
||||
price: market.uiPrice,
|
||||
},
|
||||
])}
|
||||
name={symbol}
|
||||
xKey="time"
|
||||
yKey="price"
|
||||
/>
|
||||
</div>
|
||||
) : symbol === 'USDC' || symbol === 'USDT' ? null : (
|
||||
<p className="mb-0 text-th-fgd-4">{t('unavailable')}</p>
|
||||
)
|
||||
) : (
|
||||
<div className="h-10 w-[104px] animate-pulse rounded bg-th-bkg-3" />
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex flex-col text-right">
|
||||
<p>
|
||||
{volume ? `$${numberCompacter.format(volume)}` : '$0'}
|
||||
</p>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex items-center justify-end">
|
||||
{fundingRate !== '–' ? (
|
||||
<Tooltip
|
||||
content={
|
||||
<>
|
||||
{fundingRateApr ? (
|
||||
<div className="">
|
||||
The 1hr rate as an APR is{' '}
|
||||
<span className="font-mono text-th-fgd-2">
|
||||
{fundingRateApr}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="mt-2">
|
||||
Funding is paid continuously. The 1hr rate
|
||||
displayed is a rolling average of the past 60
|
||||
mins.
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
When positive, longs will pay shorts and when
|
||||
negative shorts pay longs.
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<p className="tooltip-underline">{fundingRate}</p>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<p>–</p>
|
||||
)}
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex flex-col text-right">
|
||||
{openInterest ? (
|
||||
<>
|
||||
<p>
|
||||
<FormatNumericValue
|
||||
value={openInterest}
|
||||
decimals={getDecimalCount(market.minOrderSize)}
|
||||
return (
|
||||
<TrBody
|
||||
className="default-transition md:hover:cursor-pointer md:hover:bg-th-bkg-2"
|
||||
key={market.publicKey.toString()}
|
||||
onClick={() => goToPerpMarketDetails(market, router)}
|
||||
>
|
||||
<Td>
|
||||
<div className="flex items-center">
|
||||
<MarketLogos market={market} size="large" />
|
||||
<p className="mr-2 whitespace-nowrap font-body">
|
||||
{market.name}
|
||||
</p>
|
||||
{isComingSoon ? <SoonBadge /> : null}
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex flex-col text-right">
|
||||
<p>
|
||||
{market.uiPrice ? (
|
||||
<FormatNumericValue value={market.uiPrice} isUsd />
|
||||
) : (
|
||||
'–'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex flex-col items-end">
|
||||
<MarketChange market={market} />
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
{!loadingMarketData ? (
|
||||
priceHistory && priceHistory.length ? (
|
||||
<div className="h-10 w-24">
|
||||
<SimpleAreaChart
|
||||
color={
|
||||
isUp ? COLORS.UP[theme] : COLORS.DOWN[theme]
|
||||
}
|
||||
data={priceHistory.concat([
|
||||
{
|
||||
time: new Date().toString(),
|
||||
price: market.uiPrice,
|
||||
},
|
||||
])}
|
||||
name={symbol}
|
||||
xKey="time"
|
||||
yKey="price"
|
||||
/>
|
||||
</div>
|
||||
) : symbol === 'USDC' || symbol === 'USDT' ? null : (
|
||||
<p className="mb-0 text-th-fgd-4">
|
||||
{t('unavailable')}
|
||||
</p>
|
||||
<p className="text-th-fgd-4">
|
||||
$
|
||||
{numberCompacter.format(
|
||||
openInterest * market.uiPrice,
|
||||
)}
|
||||
</p>
|
||||
</>
|
||||
)
|
||||
) : (
|
||||
<>
|
||||
<p>–</p>
|
||||
<p className="text-th-fgd-4">–</p>
|
||||
</>
|
||||
<div className="h-10 w-[104px] animate-pulse rounded bg-th-bkg-3" />
|
||||
)}
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex justify-end">
|
||||
<ChevronRightIcon className="h-5 w-5 text-th-fgd-3" />
|
||||
</div>
|
||||
</Td>
|
||||
</TrBody>
|
||||
)
|
||||
})}
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex flex-col text-right">
|
||||
<p>
|
||||
{volume ? `$${numberCompacter.format(volume)}` : '$0'}
|
||||
</p>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex items-center justify-end">
|
||||
{fundingRate !== '–' ? (
|
||||
<Tooltip
|
||||
content={
|
||||
<>
|
||||
{fundingRateApr ? (
|
||||
<div className="">
|
||||
The 1hr rate as an APR is{' '}
|
||||
<span className="font-mono text-th-fgd-2">
|
||||
{fundingRateApr}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="mt-2">
|
||||
Funding is paid continuously. The 1hr rate
|
||||
displayed is a rolling average of the past 60
|
||||
mins.
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
When positive, longs will pay shorts and when
|
||||
negative shorts pay longs.
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<p className="tooltip-underline">{fundingRate}</p>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<p>–</p>
|
||||
)}
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex flex-col text-right">
|
||||
{openInterest ? (
|
||||
<>
|
||||
<p>
|
||||
<FormatNumericValue
|
||||
value={openInterest}
|
||||
decimals={getDecimalCount(market.minOrderSize)}
|
||||
/>
|
||||
</p>
|
||||
<p className="text-th-fgd-4">
|
||||
$
|
||||
{numberCompacter.format(
|
||||
openInterest * market.uiPrice,
|
||||
)}
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p>–</p>
|
||||
<p className="text-th-fgd-4">–</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex justify-end">
|
||||
<ChevronRightIcon className="h-5 w-5 text-th-fgd-3" />
|
||||
</div>
|
||||
</Td>
|
||||
</TrBody>
|
||||
)
|
||||
},
|
||||
)}
|
||||
</tbody>
|
||||
</Table>
|
||||
) : (
|
||||
<div className="border-b border-th-bkg-3">
|
||||
{perpMarkets.map((market) => {
|
||||
return (
|
||||
<MobilePerpMarketItem
|
||||
key={market.publicKey.toString()}
|
||||
market={market}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
{sortPerpMarkets(perpMarketsWithData, 'quote_volume_24h').map(
|
||||
(market) => {
|
||||
return (
|
||||
<MobilePerpMarketItem
|
||||
key={market.publicKey.toString()}
|
||||
loadingMarketData={loadingMarketData}
|
||||
market={market}
|
||||
/>
|
||||
)
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</ContentBox>
|
||||
|
@ -261,28 +258,23 @@ const PerpMarketsOverviewTable = () => {
|
|||
|
||||
export default PerpMarketsOverviewTable
|
||||
|
||||
const MobilePerpMarketItem = ({ market }: { market: PerpMarket }) => {
|
||||
const MobilePerpMarketItem = ({
|
||||
market,
|
||||
loadingMarketData,
|
||||
}: {
|
||||
market: PerpMarketWithMarketData
|
||||
loadingMarketData: boolean
|
||||
}) => {
|
||||
const { t } = useTranslation('common')
|
||||
const { theme } = useThemeWrapper()
|
||||
const router = useRouter()
|
||||
const rate = usePerpFundingRate()
|
||||
const { data: marketsData, isLoading, isFetching } = useMarketsData()
|
||||
|
||||
const perpData: MarketData = useMemo(() => {
|
||||
if (!marketsData) return []
|
||||
return marketsData?.perpData || []
|
||||
}, [marketsData])
|
||||
const priceHistory = market?.marketData?.price_history
|
||||
|
||||
const perpDataEntries = Object.entries(perpData).find(
|
||||
(e) => e[0].toLowerCase() === market.name.toLowerCase(),
|
||||
)
|
||||
const marketData = perpDataEntries ? perpDataEntries[1][0] : undefined
|
||||
const volumeData = market?.marketData?.quote_volume_24h
|
||||
|
||||
const priceHistory = marketData?.price_history
|
||||
|
||||
const volume = marketData?.quote_volume_24h ? marketData.quote_volume_24h : 0
|
||||
|
||||
const loadingMarketData = isLoading || isFetching
|
||||
const volume = volumeData ? volumeData : 0
|
||||
|
||||
const symbol = market.name.split('-')[0]
|
||||
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { Serum3Market } from '@blockworks-foundation/mango-v4'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useMemo } from 'react'
|
||||
import { useViewport } from '../../hooks/useViewport'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { COLORS } from '../../styles/colors'
|
||||
import { breakpoints } from '../../utils/theme'
|
||||
import ContentBox from '../shared/ContentBox'
|
||||
|
@ -12,26 +10,23 @@ import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements'
|
|||
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
||||
import { floorToDecimal, getDecimalCount, numberCompacter } from 'utils/numbers'
|
||||
import SimpleAreaChart from '@components/shared/SimpleAreaChart'
|
||||
import { MarketData } from 'types'
|
||||
import { Disclosure, Transition } from '@headlessui/react'
|
||||
import { ChevronDownIcon } from '@heroicons/react/20/solid'
|
||||
import MarketChange from '@components/shared/MarketChange'
|
||||
import useMarketsData from 'hooks/useMarketsData'
|
||||
import useThemeWrapper from 'hooks/useThemeWrapper'
|
||||
import useListedMarketsWithMarketData, {
|
||||
SerumMarketWithMarketData,
|
||||
} from 'hooks/useListedMarketsWithMarketData'
|
||||
import { sortSpotMarkets } from 'utils/markets'
|
||||
|
||||
const SpotMarketsTable = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const { group } = useMangoGroup()
|
||||
const serumMarkets = mangoStore((s) => s.serumMarkets)
|
||||
const { theme } = useThemeWrapper()
|
||||
const { width } = useViewport()
|
||||
const showTableView = width ? width > breakpoints.md : false
|
||||
const { data: marketsData, isLoading, isFetching } = useMarketsData()
|
||||
|
||||
const spotData: MarketData = useMemo(() => {
|
||||
if (!marketsData) return []
|
||||
return marketsData?.spotData || []
|
||||
}, [marketsData])
|
||||
const { serumMarketsWithData, isLoading, isFetching } =
|
||||
useListedMarketsWithMarketData()
|
||||
|
||||
const loadingMarketData = isLoading || isFetching
|
||||
|
||||
|
@ -49,10 +44,8 @@ const SpotMarketsTable = () => {
|
|||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{serumMarkets
|
||||
.slice()
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.map((mkt) => {
|
||||
{sortSpotMarkets(serumMarketsWithData, 'quote_volume_24h').map(
|
||||
(mkt) => {
|
||||
const baseBank = group?.getFirstBankByTokenIndex(
|
||||
mkt.baseTokenIndex,
|
||||
)
|
||||
|
@ -70,18 +63,11 @@ const SpotMarketsTable = () => {
|
|||
).toNumber()
|
||||
}
|
||||
|
||||
const spotDataEntries = Object.entries(spotData).find(
|
||||
(e) => e[0].toLowerCase() === mkt.name.toLowerCase(),
|
||||
)
|
||||
const marketData = spotDataEntries
|
||||
? spotDataEntries[1][0]
|
||||
: undefined
|
||||
const priceHistory = mkt?.marketData?.price_history
|
||||
|
||||
const priceHistory = marketData?.price_history
|
||||
const volumeData = mkt?.marketData?.quote_volume_24h
|
||||
|
||||
const volume = marketData?.quote_volume_24h
|
||||
? marketData.quote_volume_24h
|
||||
: 0
|
||||
const volume = volumeData ? volumeData : 0
|
||||
|
||||
const isUp =
|
||||
price && priceHistory && priceHistory.length
|
||||
|
@ -169,22 +155,23 @@ const SpotMarketsTable = () => {
|
|||
</Td>
|
||||
</TrBody>
|
||||
)
|
||||
})}
|
||||
},
|
||||
)}
|
||||
</tbody>
|
||||
</Table>
|
||||
) : (
|
||||
<div className="border-b border-th-bkg-3">
|
||||
{serumMarkets
|
||||
.slice()
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.map((market) => {
|
||||
{sortSpotMarkets(serumMarketsWithData, 'quote_volume_24h').map(
|
||||
(market) => {
|
||||
return (
|
||||
<MobileSpotMarketItem
|
||||
key={market.publicKey.toString()}
|
||||
loadingMarketData={loadingMarketData}
|
||||
market={market}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</ContentBox>
|
||||
|
@ -193,21 +180,19 @@ const SpotMarketsTable = () => {
|
|||
|
||||
export default SpotMarketsTable
|
||||
|
||||
const MobileSpotMarketItem = ({ market }: { market: Serum3Market }) => {
|
||||
const MobileSpotMarketItem = ({
|
||||
market,
|
||||
loadingMarketData,
|
||||
}: {
|
||||
market: SerumMarketWithMarketData
|
||||
loadingMarketData: boolean
|
||||
}) => {
|
||||
const { t } = useTranslation('common')
|
||||
const { group } = useMangoGroup()
|
||||
const { theme } = useThemeWrapper()
|
||||
const baseBank = group?.getFirstBankByTokenIndex(market.baseTokenIndex)
|
||||
const quoteBank = group?.getFirstBankByTokenIndex(market.quoteTokenIndex)
|
||||
const serumMarket = group?.getSerum3ExternalMarket(market.serumMarketExternal)
|
||||
const { data: marketsData, isLoading, isFetching } = useMarketsData()
|
||||
|
||||
const spotData: MarketData = useMemo(() => {
|
||||
if (!marketsData) return []
|
||||
return marketsData?.spotData || []
|
||||
}, [marketsData])
|
||||
|
||||
const loadingMarketData = isLoading || isFetching
|
||||
|
||||
const price = useMemo(() => {
|
||||
if (!baseBank || !quoteBank || !serumMarket) return 0
|
||||
|
@ -217,14 +202,11 @@ const MobileSpotMarketItem = ({ market }: { market: Serum3Market }) => {
|
|||
).toNumber()
|
||||
}, [baseBank, quoteBank, serumMarket])
|
||||
|
||||
const spotDataEntries = Object.entries(spotData).find(
|
||||
(e) => e[0].toLowerCase() === market.name.toLowerCase(),
|
||||
)
|
||||
const marketData = spotDataEntries ? spotDataEntries[1][0] : undefined
|
||||
const priceHistory = market?.marketData?.price_history
|
||||
|
||||
const priceHistory = marketData?.price_history
|
||||
const volueData = market?.marketData?.quote_volume_24h
|
||||
|
||||
const volume = marketData?.quote_volume_24h ? marketData.quote_volume_24h : 0
|
||||
const volume = volueData ? volueData : 0
|
||||
|
||||
const isUp =
|
||||
price && priceHistory && priceHistory.length
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import FavoriteMarketButton from '@components/shared/FavoriteMarketButton'
|
||||
import { Popover } from '@headlessui/react'
|
||||
import { ChevronDownIcon } from '@heroicons/react/20/solid'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import useSelectedMarket from 'hooks/useSelectedMarket'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
@ -12,6 +11,7 @@ import {
|
|||
formatCurrencyValue,
|
||||
formatNumericValue,
|
||||
getDecimalCount,
|
||||
numberCompacter,
|
||||
} from 'utils/numbers'
|
||||
import MarketLogos from './MarketLogos'
|
||||
import SoonBadge from '@components/shared/SoonBadge'
|
||||
|
@ -19,15 +19,23 @@ import TabButtons from '@components/shared/TabButtons'
|
|||
import { PerpMarket } from '@blockworks-foundation/mango-v4'
|
||||
import Loading from '@components/shared/Loading'
|
||||
import MarketChange from '@components/shared/MarketChange'
|
||||
|
||||
const MARKET_LINK_WRAPPER_CLASSES =
|
||||
'flex items-center justify-between px-4 md:pl-6 md:pr-4'
|
||||
import SheenLoader from '@components/shared/SheenLoader'
|
||||
// import Select from '@components/forms/Select'
|
||||
import useListedMarketsWithMarketData from 'hooks/useListedMarketsWithMarketData'
|
||||
import { AllowedKeys, sortPerpMarkets, sortSpotMarkets } from 'utils/markets'
|
||||
|
||||
const MARKET_LINK_CLASSES =
|
||||
'mr-1 -ml-3 flex w-full items-center justify-between rounded-md py-2 px-3 focus:outline-none focus-visible:text-th-active md:hover:cursor-pointer md:hover:bg-th-bkg-3 md:hover:text-th-fgd-1'
|
||||
'grid grid-cols-3 md:grid-cols-4 flex items-center w-full py-2 px-4 rounded-r-md focus:outline-none focus-visible:text-th-active md:hover:cursor-pointer md:hover:bg-th-bkg-3 md:hover:text-th-fgd-1'
|
||||
|
||||
const MARKET_LINK_DISABLED_CLASSES =
|
||||
'mr-2 -ml-3 flex w-full items-center justify-between rounded-md py-2 px-3 md:hover:cursor-not-allowed'
|
||||
'flex w-full items-center justify-between py-2 px-4 md:hover:cursor-not-allowed'
|
||||
|
||||
// const SORT_KEYS = [
|
||||
// 'quote_volume_24h',
|
||||
// 'quote_volume_1h',
|
||||
// 'change_24h',
|
||||
// 'change_1h',
|
||||
// ]
|
||||
|
||||
const MarketSelectDropdown = () => {
|
||||
const { t } = useTranslation('common')
|
||||
|
@ -35,27 +43,21 @@ const MarketSelectDropdown = () => {
|
|||
const [spotOrPerp, setSpotOrPerp] = useState(
|
||||
selectedMarket instanceof PerpMarket ? 'perp' : 'spot',
|
||||
)
|
||||
const serumMarkets = mangoStore((s) => s.serumMarkets)
|
||||
const allPerpMarkets = mangoStore((s) => s.perpMarkets)
|
||||
const [sortByKey] = useState<AllowedKeys>('quote_volume_24h')
|
||||
const { group } = useMangoGroup()
|
||||
const [spotBaseFilter, setSpotBaseFilter] = useState('All')
|
||||
const { perpMarketsWithData, serumMarketsWithData, isLoading, isFetching } =
|
||||
useListedMarketsWithMarketData()
|
||||
|
||||
const perpMarkets = useMemo(() => {
|
||||
return allPerpMarkets
|
||||
.filter(
|
||||
(p) =>
|
||||
p.publicKey.toString() !==
|
||||
'9Y8paZ5wUpzLFfQuHz8j2RtPrKsDtHx9sbgFmWb5abCw',
|
||||
)
|
||||
.sort((a, b) =>
|
||||
a.oracleLastUpdatedSlot == 0 ? -1 : a.name.localeCompare(b.name),
|
||||
)
|
||||
}, [allPerpMarkets])
|
||||
const perpMarketsToShow = useMemo(() => {
|
||||
if (!perpMarketsWithData.length) return []
|
||||
return sortPerpMarkets(perpMarketsWithData, sortByKey)
|
||||
}, [perpMarketsWithData, sortByKey])
|
||||
|
||||
const spotBaseTokens: string[] = useMemo(() => {
|
||||
if (serumMarkets.length) {
|
||||
if (serumMarketsWithData.length) {
|
||||
const baseTokens: string[] = ['All']
|
||||
serumMarkets.map((m) => {
|
||||
serumMarketsWithData.map((m) => {
|
||||
const base = m.name.split('/')[1]
|
||||
if (!baseTokens.includes(base)) {
|
||||
baseTokens.push(base)
|
||||
|
@ -64,19 +66,23 @@ const MarketSelectDropdown = () => {
|
|||
return baseTokens.sort((a, b) => a.localeCompare(b))
|
||||
}
|
||||
return ['All']
|
||||
}, [serumMarkets])
|
||||
}, [serumMarketsWithData])
|
||||
|
||||
const serumMarketsToShow = useMemo(() => {
|
||||
if (!serumMarkets || !serumMarkets.length) return []
|
||||
if (!serumMarketsWithData.length) return []
|
||||
|
||||
if (spotBaseFilter !== 'All') {
|
||||
return serumMarkets.filter((m) => {
|
||||
const filteredMarkets = serumMarketsWithData.filter((m) => {
|
||||
const base = m.name.split('/')[1]
|
||||
return base === spotBaseFilter
|
||||
})
|
||||
return sortSpotMarkets(filteredMarkets, sortByKey)
|
||||
} else {
|
||||
return serumMarkets
|
||||
return sortSpotMarkets(serumMarketsWithData, sortByKey)
|
||||
}
|
||||
}, [serumMarkets, spotBaseFilter])
|
||||
}, [serumMarketsWithData, sortByKey, spotBaseFilter])
|
||||
|
||||
const loadingMarketData = isLoading || isFetching
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
|
@ -107,7 +113,7 @@ const MarketSelectDropdown = () => {
|
|||
} mt-0.5 ml-2 h-6 w-6 flex-shrink-0 text-th-fgd-2`}
|
||||
/>
|
||||
</Popover.Button>
|
||||
<Popover.Panel className="absolute -left-4 top-12 z-40 mr-4 w-screen rounded-none border-y border-r border-th-bkg-3 bg-th-bkg-2 md:-left-6 md:w-[420px] md:rounded-br-md">
|
||||
<Popover.Panel className="absolute top-12 z-40 w-screen border-y md:border-r border-th-bkg-3 bg-th-bkg-2 -left-4 md:w-[560px]">
|
||||
<div className="border-b border-th-bkg-3">
|
||||
<TabButtons
|
||||
activeValue={spotOrPerp}
|
||||
|
@ -119,15 +125,28 @@ const MarketSelectDropdown = () => {
|
|||
fillWidth
|
||||
/>
|
||||
</div>
|
||||
<div className="py-3">
|
||||
{spotOrPerp === 'perp' && perpMarkets?.length
|
||||
? perpMarkets.map((m) => {
|
||||
<div className="py-3 max-h-[calc(100vh-160px)] thin-scroll overflow-auto">
|
||||
{spotOrPerp === 'perp' && perpMarketsToShow.length ? (
|
||||
<>
|
||||
<div className="grid grid-cols-3 md:grid-cols-4 pl-4 pr-14 text-xxs border-b border-th-bkg-3 pb-1 mb-2">
|
||||
<p className="col-span-1">{t('market')}</p>
|
||||
<p className="col-span-1 text-right">{t('price')}</p>
|
||||
<p className="col-span-1 text-right">
|
||||
{t('rolling-change')}
|
||||
</p>
|
||||
<p className="col-span-1 text-right hidden md:block">
|
||||
{t('daily-volume')}
|
||||
</p>
|
||||
</div>
|
||||
{perpMarketsToShow.map((m) => {
|
||||
const isComingSoon = m.oracleLastUpdatedSlot == 0
|
||||
|
||||
const volumeData = m?.marketData?.quote_volume_24h
|
||||
|
||||
const volume = volumeData ? volumeData : 0
|
||||
|
||||
return (
|
||||
<div
|
||||
className={MARKET_LINK_WRAPPER_CLASSES}
|
||||
key={m.publicKey.toString()}
|
||||
>
|
||||
<div className="flex items-center w-full" key={m.name}>
|
||||
{!isComingSoon ? (
|
||||
<>
|
||||
<Link
|
||||
|
@ -141,116 +160,196 @@ const MarketSelectDropdown = () => {
|
|||
}}
|
||||
shallow={true}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<MarketLogos market={m} />
|
||||
<span className="text-th-fgd-2">{m.name}</span>
|
||||
<div className="col-span-1 flex items-center">
|
||||
<MarketLogos market={m} size="small" />
|
||||
<span className="text-th-fgd-2 text-xs">
|
||||
{m.name}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<span className="mr-3 font-mono text-xs text-th-fgd-2">
|
||||
<div className="col-span-1 flex justify-end">
|
||||
<span className="font-mono text-xs text-th-fgd-2">
|
||||
{formatCurrencyValue(
|
||||
m.uiPrice,
|
||||
getDecimalCount(m.tickSize),
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="col-span-1 flex justify-end">
|
||||
<MarketChange market={m} size="small" />
|
||||
</div>
|
||||
<div className="col-span-1 md:flex justify-end hidden">
|
||||
{loadingMarketData ? (
|
||||
<SheenLoader className="mt-0.5">
|
||||
<div className="h-3.5 w-12 bg-th-bkg-2" />
|
||||
</SheenLoader>
|
||||
) : (
|
||||
<span>
|
||||
{volume ? (
|
||||
<span className="font-mono text-xs text-th-fgd-2">
|
||||
${numberCompacter.format(volume)}
|
||||
</span>
|
||||
) : (
|
||||
<span className="font-mono text-xs text-th-fgd-2">
|
||||
$0
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
<FavoriteMarketButton market={m} />
|
||||
<div className="px-3">
|
||||
<FavoriteMarketButton market={m} />
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<span className={MARKET_LINK_DISABLED_CLASSES}>
|
||||
<div className="flex items-center">
|
||||
<MarketLogos market={m} />
|
||||
<span className="mr-2">{m.name}</span>
|
||||
<MarketLogos market={m} size="small" />
|
||||
<span className="mr-2 text-xs">{m.name}</span>
|
||||
<SoonBadge />
|
||||
</div>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
: null}
|
||||
{spotOrPerp === 'spot' && serumMarkets?.length ? (
|
||||
})}
|
||||
</>
|
||||
) : null}
|
||||
{spotOrPerp === 'spot' && serumMarketsToShow.length ? (
|
||||
<>
|
||||
<div className="mb-3 px-4 md:px-6">
|
||||
{spotBaseTokens.map((tab) => (
|
||||
<button
|
||||
className={`rounded-md py-1.5 px-2.5 text-sm font-medium focus-visible:bg-th-bkg-3 focus-visible:text-th-fgd-1 ${
|
||||
spotBaseFilter === tab
|
||||
? 'bg-th-bkg-3 text-th-active md:hover:text-th-active'
|
||||
: 'text-th-fgd-3 md:hover:text-th-fgd-2'
|
||||
}`}
|
||||
onClick={() => setSpotBaseFilter(tab)}
|
||||
key={tab}
|
||||
>
|
||||
{t(tab)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{serumMarketsToShow
|
||||
.map((x) => x)
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.map((m) => {
|
||||
const baseBank = group?.getFirstBankByTokenIndex(
|
||||
m.baseTokenIndex,
|
||||
)
|
||||
const quoteBank = group?.getFirstBankByTokenIndex(
|
||||
m.quoteTokenIndex,
|
||||
)
|
||||
const market = group?.getSerum3ExternalMarket(
|
||||
m.serumMarketExternal,
|
||||
)
|
||||
let price
|
||||
if (baseBank && market && quoteBank) {
|
||||
price = floorToDecimal(
|
||||
baseBank.uiPrice / quoteBank.uiPrice,
|
||||
getDecimalCount(market.tickSize),
|
||||
).toNumber()
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className={MARKET_LINK_WRAPPER_CLASSES}
|
||||
key={m.publicKey.toString()}
|
||||
<div className="flex items-center justify-between mb-3 px-4">
|
||||
<div>
|
||||
{spotBaseTokens.map((tab) => (
|
||||
<button
|
||||
className={`rounded-md py-1.5 px-2.5 text-sm font-medium focus-visible:bg-th-bkg-3 focus-visible:text-th-fgd-1 ${
|
||||
spotBaseFilter === tab
|
||||
? 'bg-th-bkg-3 text-th-active md:hover:text-th-active'
|
||||
: 'text-th-fgd-3 md:hover:text-th-fgd-2'
|
||||
}`}
|
||||
onClick={() => setSpotBaseFilter(tab)}
|
||||
key={tab}
|
||||
>
|
||||
<Link
|
||||
className={MARKET_LINK_CLASSES}
|
||||
href={{
|
||||
pathname: '/trade',
|
||||
query: { name: m.name },
|
||||
}}
|
||||
onClick={() => {
|
||||
close()
|
||||
}}
|
||||
shallow={true}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<MarketLogos market={m} />
|
||||
<span className="text-th-fgd-2">{m.name}</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
{price && market?.tickSize ? (
|
||||
<span className="mr-3 font-mono text-xs text-th-fgd-2">
|
||||
{quoteBank?.name === 'USDC' ? '$' : ''}
|
||||
{getDecimalCount(market.tickSize) <= 6
|
||||
? formatNumericValue(
|
||||
price,
|
||||
getDecimalCount(market.tickSize),
|
||||
)
|
||||
: price.toExponential(3)}{' '}
|
||||
{quoteBank?.name !== 'USDC' ? (
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{quoteBank?.name}
|
||||
</span>
|
||||
) : null}
|
||||
</span>
|
||||
) : null}
|
||||
<MarketChange market={m} size="small" />
|
||||
</div>
|
||||
</Link>
|
||||
{t(tab)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{/* need to sort out change before enabling more sorting options */}
|
||||
{/* <div>
|
||||
<Select
|
||||
value={sortByKey}
|
||||
onChange={(sortBy) => setSortByKey(sortBy)}
|
||||
className="w-full"
|
||||
>
|
||||
{SORT_KEYS.map((sortBy) => {
|
||||
return (
|
||||
<Select.Option key={sortBy} value={sortBy}>
|
||||
<div className="flex w-full items-center justify-between">
|
||||
{sortBy}
|
||||
</div>
|
||||
</Select.Option>
|
||||
)
|
||||
})}
|
||||
</Select>
|
||||
</div> */}
|
||||
</div>
|
||||
<div className="grid grid-cols-3 md:grid-cols-4 pl-4 pr-14 text-xxs border-b border-th-bkg-3 pb-1 mb-2">
|
||||
<p className="col-span-1">{t('market')}</p>
|
||||
<p className="col-span-1 text-right">{t('price')}</p>
|
||||
<p className="col-span-1 text-right">
|
||||
{t('rolling-change')}
|
||||
</p>
|
||||
<p className="col-span-1 text-right hidden md:block">
|
||||
{t('daily-volume')}
|
||||
</p>
|
||||
</div>
|
||||
{serumMarketsToShow.map((m) => {
|
||||
const baseBank = group?.getFirstBankByTokenIndex(
|
||||
m.baseTokenIndex,
|
||||
)
|
||||
const quoteBank = group?.getFirstBankByTokenIndex(
|
||||
m.quoteTokenIndex,
|
||||
)
|
||||
const market = group?.getSerum3ExternalMarket(
|
||||
m.serumMarketExternal,
|
||||
)
|
||||
let price
|
||||
if (baseBank && market && quoteBank) {
|
||||
price = floorToDecimal(
|
||||
baseBank.uiPrice / quoteBank.uiPrice,
|
||||
getDecimalCount(market.tickSize),
|
||||
).toNumber()
|
||||
}
|
||||
|
||||
const volumeData = m?.marketData?.quote_volume_24h
|
||||
|
||||
const volume = volumeData ? volumeData : 0
|
||||
|
||||
return (
|
||||
<div className="flex items-center w-full" key={m.name}>
|
||||
<Link
|
||||
className={MARKET_LINK_CLASSES}
|
||||
href={{
|
||||
pathname: '/trade',
|
||||
query: { name: m.name },
|
||||
}}
|
||||
onClick={() => {
|
||||
close()
|
||||
}}
|
||||
shallow={true}
|
||||
>
|
||||
<div className="col-span-1 flex items-center">
|
||||
<MarketLogos market={m} size="small" />
|
||||
<span className="text-th-fgd-2 text-xs">
|
||||
{m.name}
|
||||
</span>
|
||||
</div>
|
||||
<div className="col-span-1 flex justify-end">
|
||||
{price && market?.tickSize ? (
|
||||
<span className="font-mono text-xs text-th-fgd-2">
|
||||
{quoteBank?.name === 'USDC' ? '$' : ''}
|
||||
{getDecimalCount(market.tickSize) <= 6
|
||||
? formatNumericValue(
|
||||
price,
|
||||
getDecimalCount(market.tickSize),
|
||||
)
|
||||
: price.toExponential(3)}
|
||||
{quoteBank?.name !== 'USDC' ? (
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{' '}
|
||||
{quoteBank?.name}
|
||||
</span>
|
||||
) : null}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="col-span-1 flex justify-end">
|
||||
<MarketChange market={m} size="small" />
|
||||
</div>
|
||||
<div className="col-span-1 md:flex justify-end hidden">
|
||||
{loadingMarketData ? (
|
||||
<SheenLoader className="mt-0.5">
|
||||
<div className="h-3.5 w-12 bg-th-bkg-2" />
|
||||
</SheenLoader>
|
||||
) : (
|
||||
<span className="font-mono text-xs text-th-fgd-2">
|
||||
{quoteBank?.name === 'USDC' ? '$' : ''}
|
||||
{volume ? numberCompacter.format(volume) : 0}
|
||||
{quoteBank?.name !== 'USDC' ? (
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{' '}
|
||||
{quoteBank?.name}
|
||||
</span>
|
||||
) : null}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
<div className="px-3">
|
||||
<FavoriteMarketButton market={m} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
import { MarketData, MarketsDataItem } from 'types'
|
||||
import useMarketsData from './useMarketsData'
|
||||
import { useMemo } from 'react'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { PerpMarket, Serum3Market } from '@blockworks-foundation/mango-v4'
|
||||
|
||||
type ApiData = {
|
||||
marketData: MarketsDataItem | undefined
|
||||
}
|
||||
|
||||
export type SerumMarketWithMarketData = Serum3Market & ApiData
|
||||
|
||||
export type PerpMarketWithMarketData = PerpMarket & ApiData
|
||||
|
||||
export default function useListedMarketsWithMarketData() {
|
||||
const { data: marketsData, isLoading, isFetching } = useMarketsData()
|
||||
const serumMarkets = mangoStore((s) => s.serumMarkets)
|
||||
const perpMarkets = mangoStore((s) => s.perpMarkets)
|
||||
|
||||
const perpData: MarketData = useMemo(() => {
|
||||
if (!marketsData) return []
|
||||
return marketsData?.perpData || []
|
||||
}, [marketsData])
|
||||
|
||||
const spotData: MarketData = useMemo(() => {
|
||||
if (!marketsData) return []
|
||||
return marketsData?.spotData || []
|
||||
}, [marketsData])
|
||||
|
||||
const serumMarketsWithData = useMemo(() => {
|
||||
if (!serumMarkets || !serumMarkets.length) return []
|
||||
const allSpotMarkets: SerumMarketWithMarketData[] =
|
||||
serumMarkets as SerumMarketWithMarketData[]
|
||||
if (spotData) {
|
||||
for (const market of allSpotMarkets) {
|
||||
const spotEntries = Object.entries(spotData).find(
|
||||
(e) => e[0].toLowerCase() === market.name.toLowerCase(),
|
||||
)
|
||||
market.marketData = spotEntries ? spotEntries[1][0] : undefined
|
||||
}
|
||||
}
|
||||
return [...allSpotMarkets].sort((a, b) => a.name.localeCompare(b.name))
|
||||
}, [spotData, serumMarkets])
|
||||
|
||||
const perpMarketsWithData = useMemo(() => {
|
||||
if (!perpMarkets || !perpMarkets.length) return []
|
||||
const allPerpMarkets: PerpMarketWithMarketData[] =
|
||||
perpMarkets as PerpMarketWithMarketData[]
|
||||
if (perpData) {
|
||||
for (const market of allPerpMarkets) {
|
||||
const perpEntries = Object.entries(perpData).find(
|
||||
(e) => e[0].toLowerCase() === market.name.toLowerCase(),
|
||||
)
|
||||
market.marketData = perpEntries ? perpEntries[1][0] : undefined
|
||||
}
|
||||
}
|
||||
return allPerpMarkets
|
||||
.filter(
|
||||
(p) =>
|
||||
p.publicKey.toString() !==
|
||||
'9Y8paZ5wUpzLFfQuHz8j2RtPrKsDtHx9sbgFmWb5abCw',
|
||||
)
|
||||
.sort((a, b) =>
|
||||
a.oracleLastUpdatedSlot == 0 ? -1 : a.name.localeCompare(b.name),
|
||||
)
|
||||
}, [perpData, perpMarkets])
|
||||
|
||||
return { perpMarketsWithData, serumMarketsWithData, isLoading, isFetching }
|
||||
}
|
|
@ -17,12 +17,11 @@ const fetchMarketData = async () => {
|
|||
}
|
||||
}
|
||||
|
||||
export default function useMarketData() {
|
||||
export default function useMarketsData() {
|
||||
return useQuery(['market-data'], () => fetchMarketData(), {
|
||||
cacheTime: 1000 * 60 * 10,
|
||||
staleTime: 1000 * 60,
|
||||
retry: 3,
|
||||
refetchOnWindowFocus: false,
|
||||
// enabled: market && market instanceof Serum3Market,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
import {
|
||||
PerpMarketWithMarketData,
|
||||
SerumMarketWithMarketData,
|
||||
} from 'hooks/useListedMarketsWithMarketData'
|
||||
|
||||
export type AllowedKeys =
|
||||
| 'quote_volume_24h'
|
||||
| 'quote_volume_1h'
|
||||
| 'change_24h'
|
||||
| 'change_1h'
|
||||
|
||||
export const sortSpotMarkets = (
|
||||
spotMarkets: SerumMarketWithMarketData[],
|
||||
sortByKey: AllowedKeys,
|
||||
) => {
|
||||
return spotMarkets.sort(
|
||||
(a: SerumMarketWithMarketData, b: SerumMarketWithMarketData) => {
|
||||
const aValue: number | undefined = a?.marketData?.[sortByKey]
|
||||
const bValue: number | undefined = b?.marketData?.[sortByKey]
|
||||
|
||||
// Handle marketData[sortByKey] is undefined
|
||||
if (typeof aValue === 'undefined' && typeof bValue === 'undefined') {
|
||||
return 0 // Consider them equal
|
||||
}
|
||||
if (typeof aValue === 'undefined') {
|
||||
return 1 // b should come before a
|
||||
}
|
||||
if (typeof bValue === 'undefined') {
|
||||
return -1 // a should come before b
|
||||
}
|
||||
|
||||
return bValue - aValue
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
export const sortPerpMarkets = (
|
||||
perpMarkets: PerpMarketWithMarketData[],
|
||||
sortByKey: AllowedKeys,
|
||||
) => {
|
||||
return perpMarkets.sort(
|
||||
(a: PerpMarketWithMarketData, b: PerpMarketWithMarketData) => {
|
||||
const aValue: number | undefined = a?.marketData?.[sortByKey]
|
||||
const bValue: number | undefined = b?.marketData?.[sortByKey]
|
||||
|
||||
// Handle marketData[sortByKey] is undefined
|
||||
if (typeof aValue === 'undefined' && typeof bValue === 'undefined') {
|
||||
return 0 // Consider them equal
|
||||
}
|
||||
if (typeof aValue === 'undefined') {
|
||||
return 1 // b should come before a
|
||||
}
|
||||
if (typeof bValue === 'undefined') {
|
||||
return -1 // a should come before b
|
||||
}
|
||||
|
||||
return bValue - aValue
|
||||
},
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue