161 lines
5.2 KiB
TypeScript
161 lines
5.2 KiB
TypeScript
import { PerpMarket, Serum3Market } from '@blockworks-foundation/mango-v4'
|
||
import { IconButton } from '@components/shared/Button'
|
||
import Change from '@components/shared/Change'
|
||
import { getOneDayPerpStats } from '@components/stats/PerpMarketsTable'
|
||
import { ChartBarIcon } from '@heroicons/react/20/solid'
|
||
import { Market } from '@project-serum/serum'
|
||
import mangoStore from '@store/mangoStore'
|
||
import { useQuery } from '@tanstack/react-query'
|
||
import useJupiterMints from 'hooks/useJupiterMints'
|
||
import useSelectedMarket from 'hooks/useSelectedMarket'
|
||
import { useTranslation } from 'next-i18next'
|
||
import { useEffect, useMemo } from 'react'
|
||
import { Token } from 'types/jupiter'
|
||
import { getDecimalCount } from 'utils/numbers'
|
||
import MarketSelectDropdown from './MarketSelectDropdown'
|
||
import PerpFundingRate from './PerpFundingRate'
|
||
|
||
type ResponseType = {
|
||
prices: [number, number][]
|
||
market_caps: [number, number][]
|
||
total_volumes: [number, number][]
|
||
}
|
||
|
||
const fetchTokenChange = async (
|
||
mangoTokens: Token[],
|
||
baseAddress: string
|
||
): Promise<ResponseType> => {
|
||
let coingeckoId = mangoTokens.find((t) => t.address === baseAddress)
|
||
?.extensions?.coingeckoId
|
||
|
||
if (baseAddress === '3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh') {
|
||
coingeckoId = 'bitcoin'
|
||
}
|
||
|
||
const response = await fetch(
|
||
`https://api.coingecko.com/api/v3/coins/${coingeckoId}/market_chart?vs_currency=usd&days=1`
|
||
)
|
||
const data = await response.json()
|
||
return data
|
||
}
|
||
|
||
const AdvancedMarketHeader = ({
|
||
showChart,
|
||
setShowChart,
|
||
}: {
|
||
showChart?: boolean
|
||
setShowChart?: (x: boolean) => void
|
||
}) => {
|
||
const { t } = useTranslation(['common', 'trade'])
|
||
const perpStats = mangoStore((s) => s.perpStats.data)
|
||
const { serumOrPerpMarket, price, selectedMarket } = useSelectedMarket()
|
||
const selectedMarketName = mangoStore((s) => s.selectedMarket.name)
|
||
const { mangoTokens } = useJupiterMints()
|
||
|
||
useEffect(() => {
|
||
if (serumOrPerpMarket instanceof PerpMarket) {
|
||
const actions = mangoStore.getState().actions
|
||
actions.fetchPerpStats()
|
||
}
|
||
}, [serumOrPerpMarket])
|
||
|
||
const spotBaseAddress = useMemo(() => {
|
||
const group = mangoStore.getState().group
|
||
if (group && selectedMarket && selectedMarket instanceof Serum3Market) {
|
||
return group
|
||
.getFirstBankByTokenIndex(selectedMarket.baseTokenIndex)
|
||
.mint.toString()
|
||
}
|
||
}, [selectedMarket])
|
||
|
||
const spotChangeResponse = useQuery(
|
||
['coingecko-tokens', spotBaseAddress],
|
||
() => fetchTokenChange(mangoTokens, spotBaseAddress!),
|
||
{
|
||
cacheTime: 1000 * 60 * 15,
|
||
staleTime: 1000 * 60 * 10,
|
||
retry: 3,
|
||
enabled:
|
||
!!spotBaseAddress &&
|
||
serumOrPerpMarket instanceof Market &&
|
||
mangoTokens.length > 0,
|
||
refetchOnWindowFocus: false,
|
||
}
|
||
)
|
||
|
||
const change = useMemo(() => {
|
||
if (!price || !serumOrPerpMarket) return 0
|
||
if (serumOrPerpMarket instanceof PerpMarket) {
|
||
const changeData = getOneDayPerpStats(perpStats, selectedMarketName)
|
||
|
||
return changeData.length
|
||
? ((price - changeData[0].price) / changeData[0].price) * 100
|
||
: 0
|
||
} else {
|
||
if (!spotChangeResponse.data) return 0
|
||
return (
|
||
((price - spotChangeResponse.data.prices?.[0][1]) /
|
||
spotChangeResponse.data.prices?.[0][1]) *
|
||
100
|
||
)
|
||
}
|
||
}, [
|
||
spotChangeResponse,
|
||
price,
|
||
serumOrPerpMarket,
|
||
perpStats,
|
||
selectedMarketName,
|
||
])
|
||
|
||
return (
|
||
<div className="flex flex-col bg-th-bkg-1 md:h-12 md:flex-row md:items-center">
|
||
<div className="w-full px-4 md:w-auto md:px-6 md:py-0 lg:pb-0">
|
||
<MarketSelectDropdown />
|
||
</div>
|
||
<div className="border-t border-th-bkg-3 py-2 px-5 md:border-t-0 md:py-0 md:px-0">
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex items-center">
|
||
<div id="trade-step-two" className="flex-col md:ml-6">
|
||
<div className="text-xs text-th-fgd-4">
|
||
{t('trade:oracle-price')}
|
||
</div>
|
||
<div className="font-mono text-xs text-th-fgd-2">
|
||
{price ? (
|
||
`$${price.toFixed(
|
||
getDecimalCount(serumOrPerpMarket?.tickSize || 0.01)
|
||
)}`
|
||
) : (
|
||
<span className="text-th-fgd-4">–</span>
|
||
)}
|
||
</div>
|
||
</div>
|
||
<div className="ml-6 flex-col">
|
||
<div className="text-xs text-th-fgd-4">{t('rolling-change')}</div>
|
||
<Change change={change} size="small" suffix="%" />
|
||
</div>
|
||
{serumOrPerpMarket instanceof PerpMarket ? (
|
||
<div className="ml-6 flex-col">
|
||
<div className="text-xs text-th-fgd-4">
|
||
{t('trade:funding-rate')}
|
||
</div>
|
||
<PerpFundingRate />
|
||
</div>
|
||
) : null}
|
||
</div>
|
||
{setShowChart ? (
|
||
<IconButton
|
||
className={showChart ? 'text-th-active' : 'text-th-fgd-2'}
|
||
onClick={() => setShowChart(!showChart)}
|
||
hideBg
|
||
>
|
||
<ChartBarIcon className="h-5 w-5" />
|
||
</IconButton>
|
||
) : null}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default AdvancedMarketHeader
|