import { PerpMarket, Serum3Market } from '@blockworks-foundation/mango-v4' import { IconButton, LinkButton } from '@components/shared/Button' import Change from '@components/shared/Change' import { getOneDayPerpStats } from '@components/stats/PerpMarketsOverviewTable' import { ChartBarIcon, InformationCircleIcon } from '@heroicons/react/20/solid' import mangoStore from '@store/mangoStore' import useSelectedMarket from 'hooks/useSelectedMarket' import { useTranslation } from 'next-i18next' import { useEffect, useMemo, useState } from 'react' import { numberCompacter } from 'utils/numbers' import MarketSelectDropdown from './MarketSelectDropdown' import PerpFundingRate from './PerpFundingRate' import { useBirdeyeMarketPrices } from 'hooks/useBirdeyeMarketPrices' import SheenLoader from '@components/shared/SheenLoader' import usePrevious from '@components/shared/usePrevious' import PerpMarketDetailsModal from '@components/modals/PerpMarketDetailsModal' import useMangoGroup from 'hooks/useMangoGroup' import OraclePrice from './OraclePrice' import SpotMarketDetailsModal from '@components/modals/SpotMarketDetailsModal' import { useQuery } from '@tanstack/react-query' import { MANGO_DATA_OPENBOOK_URL } from 'utils/constants' import { TickerData } from 'types' import ManualRefresh from '@components/shared/ManualRefresh' import { useViewport } from 'hooks/useViewport' import { breakpoints } from 'utils/theme' export const fetchSpotVolume = async () => { try { const data = await fetch(`${MANGO_DATA_OPENBOOK_URL}/coingecko/tickers`) const res = await data.json() return res } catch (e) { console.log('Failed to fetch spot volume data', e) } } const AdvancedMarketHeader = ({ showChart, setShowChart, }: { showChart?: boolean setShowChart?: (x: boolean) => void }) => { const { t } = useTranslation(['common', 'trade']) const perpStats = mangoStore((s) => s.perpStats.data) const loadingPerpStats = mangoStore((s) => s.perpStats.loading) const { serumOrPerpMarket, price: stalePrice, selectedMarket, } = useSelectedMarket() const selectedMarketName = mangoStore((s) => s.selectedMarket.name) const [changePrice, setChangePrice] = useState(stalePrice) const { data: birdeyePrices, isLoading: loadingPrices } = useBirdeyeMarketPrices() const previousMarketName = usePrevious(selectedMarketName) const [showMarketDetails, setShowMarketDetails] = useState(false) const { group } = useMangoGroup() const { width } = useViewport() const isMobile = width ? width < breakpoints.md : false const { data: spotVolumeData, isLoading: loadingSpotVolume, isFetching: fetchingSpotVolume, } = useQuery(['spot-volume', selectedMarketName], () => fetchSpotVolume(), { cacheTime: 1000 * 60 * 10, staleTime: 1000 * 60, retry: 3, refetchOnWindowFocus: false, enabled: selectedMarket instanceof Serum3Market, }) const spotMarketVolume = useMemo(() => { if (!spotVolumeData || !spotVolumeData.length) return return spotVolumeData.find( (mkt: TickerData) => mkt.ticker_id === selectedMarketName ) }, [selectedMarketName, spotVolumeData]) useEffect(() => { if (group) { const actions = mangoStore.getState().actions actions.fetchPerpStats() } }, [group]) const birdeyeData = useMemo(() => { if ( !birdeyePrices?.length || !selectedMarket || selectedMarket instanceof PerpMarket ) return return birdeyePrices.find( (m) => m.mint === selectedMarket.serumMarketExternal.toString() ) }, [birdeyePrices, selectedMarket]) const oneDayPerpStats = useMemo(() => { if ( !perpStats || !perpStats.length || !selectedMarketName || !selectedMarketName.includes('PERP') ) return [] return getOneDayPerpStats(perpStats, selectedMarketName) }, [perpStats, selectedMarketName]) const change = useMemo(() => { if ( !changePrice || !serumOrPerpMarket || selectedMarketName !== previousMarketName ) return 0 if (serumOrPerpMarket instanceof PerpMarket) { return oneDayPerpStats.length ? ((changePrice - oneDayPerpStats[0].price) / oneDayPerpStats[0].price) * 100 : 0 } else { if (!birdeyeData) return 0 return ( ((changePrice - birdeyeData.data[0].value) / birdeyeData.data[0].value) * 100 ) } }, [ birdeyeData, changePrice, serumOrPerpMarket, oneDayPerpStats, previousMarketName, selectedMarketName, ]) const perpVolume = useMemo(() => { if (!oneDayPerpStats.length) return return ( oneDayPerpStats[oneDayPerpStats.length - 1].cumulative_quote_volume - oneDayPerpStats[0].cumulative_quote_volume ) }, [oneDayPerpStats]) return ( <>
<>
{t('rolling-change')}
{!loadingPrices && !loadingPerpStats ? ( ) : (
)}
{serumOrPerpMarket instanceof PerpMarket ? ( <>
{t('trade:24h-volume')}
{perpVolume ? ( ${numberCompacter.format(perpVolume)}{' '} ) : ( '-' )}
{t('trade:open-interest')}
$ {numberCompacter.format( serumOrPerpMarket.baseLotsToUi( serumOrPerpMarket.openInterest ) * serumOrPerpMarket.uiPrice )} | {numberCompacter.format( serumOrPerpMarket.baseLotsToUi( serumOrPerpMarket.openInterest ) )}{' '} {serumOrPerpMarket.name.split('-')[0]}
) : (
{t('trade:24h-volume')}
{!loadingSpotVolume && !fetchingSpotVolume ? ( spotMarketVolume ? ( {numberCompacter.format(spotMarketVolume.target_volume)}{' '} {selectedMarketName?.split('/')[1]} ) : ( '-' ) ) : (
)}
)}
setShowMarketDetails(true)} > {t('trade:market-details', { market: '' })} {setShowChart ? ( setShowChart(!showChart)} hideBg > ) : null}
{showMarketDetails ? ( selectedMarket instanceof PerpMarket ? ( setShowMarketDetails(false)} market={selectedMarket} /> ) : ( setShowMarketDetails(false)} market={selectedMarket} /> ) ) : null} ) } export default AdvancedMarketHeader