mango-v4-ui/components/trade/AdvancedMarketHeader.tsx

255 lines
8.6 KiB
TypeScript
Raw Normal View History

2023-04-03 16:20:59 -07:00
import { Bank, PerpMarket } from '@blockworks-foundation/mango-v4'
2023-03-28 19:01:53 -07:00
import { IconButton, LinkButton } 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-03-28 19:01:53 -07:00
import { ChartBarIcon, InformationCircleIcon } from '@heroicons/react/20/solid'
2023-02-09 19:20:46 -08:00
import mangoStore from '@store/mangoStore'
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'
import { useEffect, useMemo, useState } from 'react'
2023-03-31 16:11:49 -07:00
import {
2023-04-25 05:41:23 -07:00
floorToDecimal,
2023-03-31 16:11:49 -07:00
formatCurrencyValue,
getDecimalCount,
numberCompacter,
} from 'utils/numbers'
2022-11-30 07:46:20 -08:00
import MarketSelectDropdown from './MarketSelectDropdown'
import PerpFundingRate from './PerpFundingRate'
import { BorshAccountsCoder } from '@coral-xyz/anchor'
2023-04-03 16:20:59 -07:00
import { useBirdeyeMarketPrices } from 'hooks/useBirdeyeMarketPrices'
import SheenLoader from '@components/shared/SheenLoader'
import usePrevious from '@components/shared/usePrevious'
2023-04-26 19:36:00 -07:00
import PerpMarketDetailsModal from '@components/modals/PerpMarketDetailsModal'
2023-04-03 20:49:28 -07:00
import useMangoGroup from 'hooks/useMangoGroup'
2023-03-04 21:47:29 -08:00
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-04-03 19:48:31 -07:00
const loadingPerpStats = mangoStore((s) => s.perpStats.loading)
2023-03-25 14:16:42 -07:00
const {
serumOrPerpMarket,
price: stalePrice,
selectedMarket,
2023-04-25 05:41:23 -07:00
quoteBank,
2023-03-25 14:16:42 -07:00
} = useSelectedMarket()
2023-02-09 19:20:46 -08:00
const selectedMarketName = mangoStore((s) => s.selectedMarket.name)
const connection = mangoStore((s) => s.connection)
2023-03-25 14:16:42 -07:00
const [price, setPrice] = useState(stalePrice)
2023-04-03 16:20:59 -07:00
const { data: birdeyePrices, isLoading: loadingPrices } =
useBirdeyeMarketPrices()
const previousMarketName = usePrevious(selectedMarketName)
2023-03-28 19:01:53 -07:00
const [showMarketDetails, setShowMarketDetails] = useState(false)
2023-04-03 20:49:28 -07:00
const { group } = useMangoGroup()
//subscribe to the market oracle account
useEffect(() => {
const client = mangoStore.getState().client
const group = mangoStore.getState().group
if (!group || !selectedMarket) return
2023-04-25 05:41:23 -07:00
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) => {
// 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
marketOrBank._oracleLastUpdatedSlot = lastUpdatedSlot
2023-04-25 05:41:23 -07:00
if (selectedMarket instanceof PerpMarket) {
setPrice(uiPrice)
} else {
let price
if (quoteBank && serumOrPerpMarket) {
price = floorToDecimal(
uiPrice / quoteBank.uiPrice,
getDecimalCount(serumOrPerpMarket.tickSize)
).toNumber()
} else {
price = 0
}
setPrice(price)
}
},
'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(() => {
2023-04-03 20:49:28 -07:00
if (group) {
2023-02-09 19:20:46 -08:00
const actions = mangoStore.getState().actions
actions.fetchPerpStats()
}
2023-04-03 20:49:28 -07:00
}, [group])
2023-02-09 19:20:46 -08:00
2023-04-03 16:20:59 -07:00
const birdeyeData = useMemo(() => {
if (
!birdeyePrices?.length ||
!selectedMarket ||
selectedMarket instanceof PerpMarket
)
return
return birdeyePrices.find(
(m) => m.mint === selectedMarket.serumMarketExternal.toString()
)
}, [birdeyePrices, selectedMarket])
2022-09-13 23:24:26 -07:00
2023-01-14 21:01:30 -08:00
const change = useMemo(() => {
2023-04-03 19:48:31 -07:00
if (
!price ||
!serumOrPerpMarket ||
selectedMarketName !== previousMarketName
)
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-04-03 19:48:31 -07:00
if (!birdeyeData) return 0
2023-03-04 21:47:29 -08:00
return (
2023-04-03 16:20:59 -07:00
((price - birdeyeData.data[0].value) / birdeyeData.data[0].value) * 100
2023-03-04 21:47:29 -08:00
)
2023-02-09 19:20:46 -08:00
}
2023-03-10 10:17:24 -08:00
}, [
2023-04-03 16:20:59 -07:00
birdeyeData,
2023-03-10 10:17:24 -08:00
price,
serumOrPerpMarket,
perpStats,
2023-04-03 16:20:59 -07:00
previousMarketName,
2023-03-10 10:17:24 -08:00
selectedMarketName,
])
2022-09-13 23:24:26 -07:00
return (
2023-03-28 19:01:53 -07:00
<>
<div className="flex flex-col bg-th-bkg-1 md:h-12 md:flex-row md:items-center">
<div className="w-full pl-4 md:w-auto md:py-0 md:pl-6 lg:pb-0">
2023-03-28 19:01:53 -07:00
<MarketSelectDropdown />
</div>
<div className="hide-scroll flex w-full items-center justify-between overflow-x-auto border-t border-th-bkg-3 py-2 px-5 md:border-t-0 md:py-0 md:px-0 md:pr-6">
<div className="flex items-center">
<div
id="trade-step-two"
className="flex-col whitespace-nowrap 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 ? (
`${formatCurrencyValue(
price,
getDecimalCount(serumOrPerpMarket?.tickSize || 0.01)
2023-03-28 19:01:53 -07:00
)}`
) : (
<span className="text-th-fgd-4"></span>
)}
</div>
2022-12-06 21:25:37 -08:00
</div>
2023-03-28 19:01:53 -07:00
<div className="ml-6 flex-col whitespace-nowrap">
<div className="text-xs text-th-fgd-4">{t('rolling-change')}</div>
2023-04-03 20:49:28 -07:00
{!loadingPrices && !loadingPerpStats ? (
<Change change={change} size="small" suffix="%" />
) : (
2023-04-03 20:49:28 -07:00
<SheenLoader className="mt-0.5">
<div className="h-3.5 w-12 bg-th-bkg-2" />
</SheenLoader>
)}
2023-01-18 18:42:29 -08:00
</div>
2023-03-28 19:01:53 -07:00
{serumOrPerpMarket instanceof PerpMarket ? (
<>
2023-04-17 09:25:59 -07:00
<PerpFundingRate />
2023-03-28 19:01:53 -07:00
<div className="ml-6 flex-col whitespace-nowrap text-xs">
<div className="text-th-fgd-4">
{t('trade:open-interest')}
</div>
<span className="font-mono">
$
{numberCompacter.format(
serumOrPerpMarket.baseLotsToUi(
serumOrPerpMarket.openInterest
) * serumOrPerpMarket.uiPrice
)}
<span className="mx-1">|</span>
{numberCompacter.format(
serumOrPerpMarket.baseLotsToUi(
serumOrPerpMarket.openInterest
)
)}{' '}
<span className="font-body text-th-fgd-3">
{serumOrPerpMarket.name.split('-')[0]}
</span>
</span>
2023-03-28 19:01:53 -07:00
</div>
</>
) : null}
</div>
<div className="ml-6 flex items-center space-x-4">
{selectedMarket instanceof PerpMarket ? (
<LinkButton
2023-04-20 19:32:20 -07:00
className="flex items-center whitespace-nowrap text-th-fgd-3"
2023-03-28 19:01:53 -07:00
onClick={() => setShowMarketDetails(true)}
>
<InformationCircleIcon className="h-5 w-5 flex-shrink-0 md:mr-1.5 md:h-4 md:w-4" />
<span className="hidden text-xs md:inline">
{t('trade:market-details', { market: '' })}
</span>
2023-03-28 19:01:53 -07:00
</LinkButton>
) : null}
{setShowChart ? (
<IconButton
className={showChart ? 'text-th-active' : 'text-th-fgd-2'}
onClick={() => setShowChart(!showChart)}
hideBg
>
<ChartBarIcon className="h-5 w-5" />
</IconButton>
) : null}
</div>
2023-03-28 19:01:53 -07:00
</div>
2022-12-06 21:25:37 -08:00
</div>
2023-03-28 19:01:53 -07:00
{showMarketDetails ? (
<PerpMarketDetailsModal
isOpen={showMarketDetails}
onClose={() => setShowMarketDetails(false)}
/>
) : null}
</>
2022-09-13 23:24:26 -07:00
)
}
export default AdvancedMarketHeader