diff --git a/components/forms/Select.tsx b/components/forms/Select.tsx
index 8ff43661..37f165c4 100644
--- a/components/forms/Select.tsx
+++ b/components/forms/Select.tsx
@@ -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
diff --git a/components/shared/SoonBadge.tsx b/components/shared/SoonBadge.tsx
index 51bdb127..29fd318e 100644
--- a/components/shared/SoonBadge.tsx
+++ b/components/shared/SoonBadge.tsx
@@ -3,7 +3,7 @@ import { useTranslation } from 'next-i18next'
const SoonBadge = () => {
const { t } = useTranslation('common')
return (
-
+
{t('soon')}™
)
diff --git a/components/stats/PerpMarketsOverviewTable.tsx b/components/stats/PerpMarketsOverviewTable.tsx
index 7267784c..3474f803 100644
--- a/components/stats/PerpMarketsOverviewTable.tsx
+++ b/components/stats/PerpMarketsOverviewTable.tsx
@@ -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 = () => {
- {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 (
- goToPerpMarketDetails(market, router)}
- >
-
-
-
-
- {market.name}
-
- {isComingSoon ? : null}
-
- |
-
-
-
- {market.uiPrice ? (
-
- ) : (
- '–'
- )}
-
-
- |
-
-
-
-
- |
-
- {!loadingMarketData ? (
- priceHistory && priceHistory.length ? (
-
-
-
- ) : symbol === 'USDC' || symbol === 'USDT' ? null : (
- {t('unavailable')}
- )
- ) : (
-
- )}
- |
-
-
-
- {volume ? `$${numberCompacter.format(volume)}` : '$0'}
-
-
- |
-
-
- {fundingRate !== '–' ? (
-
- {fundingRateApr ? (
-
- The 1hr rate as an APR is{' '}
-
- {fundingRateApr}
-
-
- ) : null}
-
- Funding is paid continuously. The 1hr rate
- displayed is a rolling average of the past 60
- mins.
-
-
- When positive, longs will pay shorts and when
- negative shorts pay longs.
-
- >
- }
- >
- {fundingRate}
-
- ) : (
- –
- )}
-
- |
-
-
- {openInterest ? (
- <>
-
- goToPerpMarketDetails(market, router)}
+ >
+
+
+
+
+ {market.name}
+
+ {isComingSoon ? : null}
+
+ |
+
+
+
+ {market.uiPrice ? (
+
+ ) : (
+ '–'
+ )}
+
+
+ |
+
+
+
+
+ |
+
+ {!loadingMarketData ? (
+ priceHistory && priceHistory.length ? (
+
+
+
+ ) : symbol === 'USDC' || symbol === 'USDT' ? null : (
+
+ {t('unavailable')}
-
- $
- {numberCompacter.format(
- openInterest * market.uiPrice,
- )}
-
- >
+ )
) : (
- <>
- –
- –
- >
+
)}
-
- |
-
-
-
-
- |
-
- )
- })}
+ |
+
+
+
+ {volume ? `$${numberCompacter.format(volume)}` : '$0'}
+
+
+ |
+
+
+ {fundingRate !== '–' ? (
+
+ {fundingRateApr ? (
+
+ The 1hr rate as an APR is{' '}
+
+ {fundingRateApr}
+
+
+ ) : null}
+
+ Funding is paid continuously. The 1hr rate
+ displayed is a rolling average of the past 60
+ mins.
+
+
+ When positive, longs will pay shorts and when
+ negative shorts pay longs.
+
+ >
+ }
+ >
+ {fundingRate}
+
+ ) : (
+ –
+ )}
+
+ |
+
+
+ {openInterest ? (
+ <>
+
+
+
+
+ $
+ {numberCompacter.format(
+ openInterest * market.uiPrice,
+ )}
+
+ >
+ ) : (
+ <>
+ –
+ –
+ >
+ )}
+
+ |
+
+
+
+
+ |
+
+ )
+ },
+ )}
) : (
- {perpMarkets.map((market) => {
- return (
-
- )
- })}
+ {sortPerpMarkets(perpMarketsWithData, 'quote_volume_24h').map(
+ (market) => {
+ return (
+
+ )
+ },
+ )}
)}
@@ -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]
diff --git a/components/stats/SpotMarketsTable.tsx b/components/stats/SpotMarketsTable.tsx
index 44b569fc..dc1aeac7 100644
--- a/components/stats/SpotMarketsTable.tsx
+++ b/components/stats/SpotMarketsTable.tsx
@@ -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 = () => {
- {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 = () => {
)
- })}
+ },
+ )}
) : (
- {serumMarkets
- .slice()
- .sort((a, b) => a.name.localeCompare(b.name))
- .map((market) => {
+ {sortSpotMarkets(serumMarketsWithData, 'quote_volume_24h').map(
+ (market) => {
return (
)
- })}
+ },
+ )}
)}
@@ -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
diff --git a/components/trade/MarketSelectDropdown.tsx b/components/trade/MarketSelectDropdown.tsx
index 14793094..c5fb0058 100644
--- a/components/trade/MarketSelectDropdown.tsx
+++ b/components/trade/MarketSelectDropdown.tsx
@@ -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
('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 (
@@ -107,7 +113,7 @@ const MarketSelectDropdown = () => {
} mt-0.5 ml-2 h-6 w-6 flex-shrink-0 text-th-fgd-2`}
/>
-
+
{
fillWidth
/>
-
- {spotOrPerp === 'perp' && perpMarkets?.length
- ? perpMarkets.map((m) => {
+
+ {spotOrPerp === 'perp' && perpMarketsToShow.length ? (
+ <>
+
+
{t('market')}
+
{t('price')}
+
+ {t('rolling-change')}
+
+
+ {t('daily-volume')}
+
+
+ {perpMarketsToShow.map((m) => {
const isComingSoon = m.oracleLastUpdatedSlot == 0
+
+ const volumeData = m?.marketData?.quote_volume_24h
+
+ const volume = volumeData ? volumeData : 0
+
return (
-
+
{!isComingSoon ? (
<>
{
}}
shallow={true}
>
-
-
-
{m.name}
+
+
+
+ {m.name}
+
-
-
+
+
{formatCurrencyValue(
m.uiPrice,
getDecimalCount(m.tickSize),
)}
+
+
+
+ {loadingMarketData ? (
+
+
+
+ ) : (
+
+ {volume ? (
+
+ ${numberCompacter.format(volume)}
+
+ ) : (
+
+ $0
+
+ )}
+
+ )}
+
-
+
+
+
>
) : (
-
- {m.name}
+
+ {m.name}
)}
)
- })
- : null}
- {spotOrPerp === 'spot' && serumMarkets?.length ? (
+ })}
+ >
+ ) : null}
+ {spotOrPerp === 'spot' && serumMarketsToShow.length ? (
<>
-
- {spotBaseTokens.map((tab) => (
-
- ))}
-
- {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 (
-
+
+ {spotBaseTokens.map((tab) => (
+
+ ))}
+
+ {/* need to sort out change before enabling more sorting options */}
+ {/*
+
+
*/}
+
+
+
{t('market')}
+
{t('price')}
+
+ {t('rolling-change')}
+
+
+ {t('daily-volume')}
+
+
+ {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 (
+
+
{
+ close()
+ }}
+ shallow={true}
+ >
+
+
+
+ {m.name}
+
+
+
+ {price && market?.tickSize ? (
+
+ {quoteBank?.name === 'USDC' ? '$' : ''}
+ {getDecimalCount(market.tickSize) <= 6
+ ? formatNumericValue(
+ price,
+ getDecimalCount(market.tickSize),
+ )
+ : price.toExponential(3)}
+ {quoteBank?.name !== 'USDC' ? (
+
+ {' '}
+ {quoteBank?.name}
+
+ ) : null}
+
+ ) : null}
+
+
+
+
+
+ {loadingMarketData ? (
+
+
+
+ ) : (
+
+ {quoteBank?.name === 'USDC' ? '$' : ''}
+ {volume ? numberCompacter.format(volume) : 0}
+ {quoteBank?.name !== 'USDC' ? (
+
+ {' '}
+ {quoteBank?.name}
+
+ ) : null}
+
+ )}
+
+
+
- )
- })}
+
+ )
+ })}
>
) : null}
diff --git a/hooks/useListedMarketsWithMarketData.ts b/hooks/useListedMarketsWithMarketData.ts
new file mode 100644
index 00000000..712d5f1f
--- /dev/null
+++ b/hooks/useListedMarketsWithMarketData.ts
@@ -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 }
+}
diff --git a/hooks/useMarketsData.ts b/hooks/useMarketsData.ts
index 734fe797..889fd8f4 100644
--- a/hooks/useMarketsData.ts
+++ b/hooks/useMarketsData.ts
@@ -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,
})
}
diff --git a/utils/markets.ts b/utils/markets.ts
new file mode 100644
index 00000000..ec003a3c
--- /dev/null
+++ b/utils/markets.ts
@@ -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
+ },
+ )
+}