2023-03-25 11:19:57 -07:00
|
|
|
|
import { Bank, PerpMarket, Serum3Market } from '@blockworks-foundation/mango-v4'
|
2023-01-18 18:42:29 -08:00
|
|
|
|
import { IconButton } from '@components/shared/Button'
|
2022-10-05 22:11:28 -07:00
|
|
|
|
import Change from '@components/shared/Change'
|
2023-02-09 19:20:46 -08:00
|
|
|
|
import { getOneDayPerpStats } from '@components/stats/PerpMarketsTable'
|
2023-01-18 18:42:29 -08:00
|
|
|
|
import { ChartBarIcon } from '@heroicons/react/20/solid'
|
2023-03-04 21:47:29 -08:00
|
|
|
|
import { Market } from '@project-serum/serum'
|
2023-02-09 19:20:46 -08:00
|
|
|
|
import mangoStore from '@store/mangoStore'
|
2023-03-04 21:47:29 -08:00
|
|
|
|
import { useQuery } from '@tanstack/react-query'
|
|
|
|
|
import useJupiterMints from 'hooks/useJupiterMints'
|
2022-11-20 12:20:27 -08:00
|
|
|
|
import useSelectedMarket from 'hooks/useSelectedMarket'
|
2022-09-13 23:24:26 -07:00
|
|
|
|
import { useTranslation } from 'next-i18next'
|
2023-03-25 11:19:57 -07:00
|
|
|
|
import { useEffect, useMemo, useState } from 'react'
|
2023-03-04 21:47:29 -08:00
|
|
|
|
import { Token } from 'types/jupiter'
|
2023-01-14 21:01:30 -08:00
|
|
|
|
import { getDecimalCount } from 'utils/numbers'
|
2022-11-30 07:46:20 -08:00
|
|
|
|
import MarketSelectDropdown from './MarketSelectDropdown'
|
|
|
|
|
import PerpFundingRate from './PerpFundingRate'
|
2023-03-25 11:19:57 -07:00
|
|
|
|
import { BorshAccountsCoder } from '@coral-xyz/anchor'
|
2022-09-13 23:24:26 -07:00
|
|
|
|
|
2023-03-04 21:47:29 -08:00
|
|
|
|
type ResponseType = {
|
|
|
|
|
prices: [number, number][]
|
|
|
|
|
market_caps: [number, number][]
|
|
|
|
|
total_volumes: [number, number][]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fetchTokenChange = async (
|
|
|
|
|
mangoTokens: Token[],
|
2023-03-10 10:17:24 -08:00
|
|
|
|
baseAddress: string
|
2023-03-04 21:47:29 -08:00
|
|
|
|
): Promise<ResponseType> => {
|
2023-03-10 10:17:24 -08:00
|
|
|
|
let coingeckoId = mangoTokens.find((t) => t.address === baseAddress)
|
|
|
|
|
?.extensions?.coingeckoId
|
|
|
|
|
|
|
|
|
|
if (baseAddress === '3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh') {
|
|
|
|
|
coingeckoId = 'bitcoin'
|
|
|
|
|
}
|
2023-03-04 21:47:29 -08:00
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-18 18:42:29 -08:00
|
|
|
|
const AdvancedMarketHeader = ({
|
|
|
|
|
showChart,
|
|
|
|
|
setShowChart,
|
|
|
|
|
}: {
|
|
|
|
|
showChart?: boolean
|
|
|
|
|
setShowChart?: (x: boolean) => void
|
|
|
|
|
}) => {
|
2022-10-03 03:38:05 -07:00
|
|
|
|
const { t } = useTranslation(['common', 'trade'])
|
2023-02-09 19:20:46 -08:00
|
|
|
|
const perpStats = mangoStore((s) => s.perpStats.data)
|
2023-03-25 11:19:57 -07:00
|
|
|
|
const { serumOrPerpMarket, selectedMarket } = useSelectedMarket()
|
2023-02-09 19:20:46 -08:00
|
|
|
|
const selectedMarketName = mangoStore((s) => s.selectedMarket.name)
|
2023-03-04 21:47:29 -08:00
|
|
|
|
const { mangoTokens } = useJupiterMints()
|
2023-03-25 11:19:57 -07:00
|
|
|
|
const connection = mangoStore((s) => s.connection)
|
|
|
|
|
const [price, setPrice] = useState(0)
|
|
|
|
|
|
|
|
|
|
//subscribe to the market oracle account
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const client = mangoStore.getState().client
|
|
|
|
|
const group = mangoStore.getState().group
|
|
|
|
|
if (!group || !selectedMarket) return
|
|
|
|
|
let marketOrBank: PerpMarket | Bank
|
|
|
|
|
let decimals: number
|
|
|
|
|
if (selectedMarket instanceof PerpMarket) {
|
|
|
|
|
marketOrBank = selectedMarket
|
|
|
|
|
decimals = selectedMarket.baseDecimals
|
|
|
|
|
} else {
|
|
|
|
|
const baseBank = group.getFirstBankByTokenIndex(
|
|
|
|
|
selectedMarket.baseTokenIndex
|
|
|
|
|
)
|
|
|
|
|
marketOrBank = baseBank
|
|
|
|
|
decimals = group.getMintDecimals(baseBank.mint)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const coder = new BorshAccountsCoder(client.program.idl)
|
|
|
|
|
const subId = connection.onAccountChange(
|
|
|
|
|
marketOrBank.oracle,
|
|
|
|
|
async (info, _context) => {
|
|
|
|
|
console.log('oracle account changed')
|
|
|
|
|
// selectedMarket = mangoStore.getState().selectedMarket.current
|
|
|
|
|
// if (!(selectedMarket instanceof PerpMarket)) return
|
|
|
|
|
const { price, uiPrice, lastUpdatedSlot } =
|
|
|
|
|
await group.decodePriceFromOracleAi(
|
|
|
|
|
coder,
|
|
|
|
|
marketOrBank.oracle,
|
|
|
|
|
info,
|
|
|
|
|
decimals,
|
|
|
|
|
client
|
|
|
|
|
)
|
|
|
|
|
marketOrBank._price = price
|
|
|
|
|
marketOrBank._uiPrice = uiPrice
|
|
|
|
|
setPrice(uiPrice)
|
|
|
|
|
marketOrBank._oracleLastUpdatedSlot = lastUpdatedSlot
|
|
|
|
|
},
|
|
|
|
|
'processed'
|
|
|
|
|
)
|
|
|
|
|
return () => {
|
|
|
|
|
if (typeof subId !== 'undefined') {
|
|
|
|
|
connection.removeAccountChangeListener(subId)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}, [connection, selectedMarket])
|
2022-11-09 17:48:03 -08:00
|
|
|
|
|
2023-02-09 19:20:46 -08:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (serumOrPerpMarket instanceof PerpMarket) {
|
|
|
|
|
const actions = mangoStore.getState().actions
|
|
|
|
|
actions.fetchPerpStats()
|
|
|
|
|
}
|
|
|
|
|
}, [serumOrPerpMarket])
|
|
|
|
|
|
2023-03-10 10:17:24 -08:00
|
|
|
|
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!),
|
2023-03-04 21:47:29 -08:00
|
|
|
|
{
|
|
|
|
|
cacheTime: 1000 * 60 * 15,
|
|
|
|
|
staleTime: 1000 * 60 * 10,
|
|
|
|
|
retry: 3,
|
2023-03-05 14:44:33 -08:00
|
|
|
|
enabled:
|
2023-03-10 10:17:24 -08:00
|
|
|
|
!!spotBaseAddress &&
|
2023-03-05 14:44:33 -08:00
|
|
|
|
serumOrPerpMarket instanceof Market &&
|
|
|
|
|
mangoTokens.length > 0,
|
2023-03-04 21:47:29 -08:00
|
|
|
|
refetchOnWindowFocus: false,
|
2023-02-09 19:20:46 -08:00
|
|
|
|
}
|
2023-03-04 21:47:29 -08:00
|
|
|
|
)
|
2022-09-13 23:24:26 -07:00
|
|
|
|
|
2023-01-14 21:01:30 -08:00
|
|
|
|
const change = useMemo(() => {
|
2023-03-04 21:47:29 -08:00
|
|
|
|
if (!price || !serumOrPerpMarket) return 0
|
2023-02-09 19:20:46 -08:00
|
|
|
|
if (serumOrPerpMarket instanceof PerpMarket) {
|
2023-03-04 21:47:29 -08:00
|
|
|
|
const changeData = getOneDayPerpStats(perpStats, selectedMarketName)
|
|
|
|
|
|
2023-02-09 19:20:46 -08:00
|
|
|
|
return changeData.length
|
|
|
|
|
? ((price - changeData[0].price) / changeData[0].price) * 100
|
|
|
|
|
: 0
|
|
|
|
|
} else {
|
2023-03-10 10:17:24 -08:00
|
|
|
|
if (!spotChangeResponse.data) return 0
|
2023-03-04 21:47:29 -08:00
|
|
|
|
return (
|
2023-03-10 10:17:24 -08:00
|
|
|
|
((price - spotChangeResponse.data.prices?.[0][1]) /
|
|
|
|
|
spotChangeResponse.data.prices?.[0][1]) *
|
2023-03-04 21:47:29 -08:00
|
|
|
|
100
|
|
|
|
|
)
|
2023-02-09 19:20:46 -08:00
|
|
|
|
}
|
2023-03-10 10:17:24 -08:00
|
|
|
|
}, [
|
|
|
|
|
spotChangeResponse,
|
|
|
|
|
price,
|
|
|
|
|
serumOrPerpMarket,
|
|
|
|
|
perpStats,
|
|
|
|
|
selectedMarketName,
|
|
|
|
|
])
|
2022-09-13 23:24:26 -07:00
|
|
|
|
|
|
|
|
|
return (
|
2022-12-06 21:25:37 -08:00
|
|
|
|
<div className="flex flex-col bg-th-bkg-1 md:h-12 md:flex-row md:items-center">
|
2023-02-13 18:53:13 -08:00
|
|
|
|
<div className="w-full px-4 md:w-auto md:px-6 md:py-0 lg:pb-0">
|
2022-12-06 21:25:37 -08:00
|
|
|
|
<MarketSelectDropdown />
|
2022-09-13 23:24:26 -07:00
|
|
|
|
</div>
|
2023-01-18 18:42:29 -08:00
|
|
|
|
<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(
|
2023-03-25 11:19:57 -07:00
|
|
|
|
getDecimalCount(serumOrPerpMarket?.tickSize || 0.01) + 1
|
2023-01-18 18:42:29 -08:00
|
|
|
|
)}`
|
|
|
|
|
) : (
|
|
|
|
|
<span className="text-th-fgd-4">–</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
2022-12-06 21:25:37 -08:00
|
|
|
|
</div>
|
2023-01-18 18:42:29 -08:00
|
|
|
|
<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}
|
2022-12-06 21:25:37 -08:00
|
|
|
|
</div>
|
2023-01-18 18:42:29 -08:00
|
|
|
|
{setShowChart ? (
|
|
|
|
|
<IconButton
|
|
|
|
|
className={showChart ? 'text-th-active' : 'text-th-fgd-2'}
|
|
|
|
|
onClick={() => setShowChart(!showChart)}
|
|
|
|
|
hideBg
|
|
|
|
|
>
|
|
|
|
|
<ChartBarIcon className="h-5 w-5" />
|
|
|
|
|
</IconButton>
|
|
|
|
|
) : null}
|
|
|
|
|
</div>
|
2022-12-06 21:25:37 -08:00
|
|
|
|
</div>
|
2022-09-13 23:24:26 -07:00
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default AdvancedMarketHeader
|