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

188 lines
6.1 KiB
TypeScript
Raw Normal View History

2023-05-11 21:08:06 -07:00
import { ExclamationTriangleIcon } from '@heroicons/react/24/outline'
2023-04-25 13:21:32 -07:00
import useSelectedMarket from 'hooks/useSelectedMarket'
import Tooltip from '@components/shared/Tooltip'
import { useTranslation } from 'next-i18next'
import mangoStore from '@store/mangoStore'
import { useEffect, useState } from 'react'
import { PerpMarket, Bank } from '@blockworks-foundation/mango-v4'
2023-04-25 13:21:32 -07:00
import { BorshAccountsCoder } from '@coral-xyz/anchor'
import {
floorToDecimal,
2023-06-15 04:24:54 -07:00
formatNumericValue,
2023-04-25 13:21:32 -07:00
getDecimalCount,
} from 'utils/numbers'
import dayjs from 'dayjs'
import duration from 'dayjs/plugin/duration'
import relativeTime from 'dayjs/plugin/relativeTime'
import useOracleProvider from 'hooks/useOracleProvider'
import { CURRENCY_SYMBOLS } from './MarketSelectDropdown'
2024-01-22 20:22:03 -08:00
import OracleProvider from '@components/shared/OracleProvider'
2023-04-25 13:21:32 -07:00
2023-07-13 22:47:05 -07:00
const OraclePrice = () => {
2023-04-25 13:21:32 -07:00
const {
serumOrPerpMarket,
price: stalePrice,
selectedMarket,
quoteBank,
} = useSelectedMarket()
dayjs.extend(duration)
dayjs.extend(relativeTime)
2023-04-25 13:21:32 -07:00
const connection = mangoStore((s) => s.connection)
const [price, setPrice] = useState(stalePrice)
const [oracleLastUpdatedSlot, setOracleLastUpdatedSlot] = useState(0)
const [highestSlot, setHighestSlot] = useState(0)
const [isStale, setIsStale] = useState(false)
2024-01-22 20:22:03 -08:00
const { oracleProvider } = useOracleProvider()
2023-04-25 13:21:32 -07:00
const { t } = useTranslation(['common', 'trade'])
//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(
2023-07-21 11:47:53 -07:00
selectedMarket.baseTokenIndex,
2023-04-25 13:21:32 -07:00
)
marketOrBank = baseBank
decimals = group.getMintDecimals(baseBank.mint)
}
const coder = new BorshAccountsCoder(client.program.idl)
setPrice(stalePrice)
2023-04-25 13:21:32 -07:00
const subId = connection.onAccountChange(
marketOrBank.oracle,
async (info, context) => {
2023-04-25 13:21:32 -07:00
const { price, uiPrice, lastUpdatedSlot } =
await group.decodePriceFromOracleAi(
coder,
marketOrBank.oracle,
info,
decimals,
2023-07-21 11:47:53 -07:00
client,
2023-04-25 13:21:32 -07:00
)
marketOrBank._price = price
marketOrBank._uiPrice = uiPrice
marketOrBank._oracleLastUpdatedSlot = lastUpdatedSlot
setOracleLastUpdatedSlot(lastUpdatedSlot)
const marketSlot = mangoStore.getState().selectedMarket.lastSeenSlot
const oracleWriteSlot = context.slot
const accountSlot = mangoStore.getState().mangoAccount.lastSlot
const highestSlot = Math.max(
marketSlot.bids,
marketSlot.asks,
oracleWriteSlot,
2023-07-21 11:47:53 -07:00
accountSlot,
)
2023-04-27 11:27:56 -07:00
const maxStalenessSlots =
marketOrBank.oracleConfig.maxStalenessSlots.toNumber()
setHighestSlot(highestSlot)
setIsStale(
2023-04-27 11:01:52 -07:00
maxStalenessSlots > 0 &&
2023-07-21 11:47:53 -07:00
highestSlot - lastUpdatedSlot > maxStalenessSlots,
)
2023-04-25 13:21:32 -07:00
if (selectedMarket instanceof PerpMarket) {
setPrice(uiPrice)
} else {
let price
if (quoteBank && serumOrPerpMarket) {
price = floorToDecimal(
uiPrice / quoteBank.uiPrice,
2023-07-21 11:47:53 -07:00
getDecimalCount(serumOrPerpMarket.tickSize),
2023-04-25 13:21:32 -07:00
).toNumber()
} else {
price = 0
}
setPrice(price)
}
},
2023-07-21 11:47:53 -07:00
'processed',
2023-04-25 13:21:32 -07:00
)
return () => {
if (typeof subId !== 'undefined') {
connection.removeAccountChangeListener(subId)
}
}
2023-07-13 22:47:05 -07:00
}, [connection, selectedMarket, serumOrPerpMarket, quoteBank, stalePrice])
2023-04-25 13:21:32 -07:00
2023-06-15 04:24:54 -07:00
const oracleDecimals = getDecimalCount(serumOrPerpMarket?.tickSize || 0.01)
2023-12-05 01:52:31 -08:00
const isStub = oracleProvider === 'Stub'
2023-06-15 04:24:54 -07:00
2023-04-25 13:21:32 -07:00
return (
<>
<div id="trade-step-two" className="flex-col whitespace-nowrap md:ml-6">
<Tooltip
placement="bottom"
2023-04-25 13:21:32 -07:00
content={
2023-12-05 01:52:31 -08:00
!isStub ? (
<>
2024-01-22 20:22:03 -08:00
<div className="flex text-sm">
2023-12-05 01:52:31 -08:00
<span className="mr-1">{t('trade:price-provided-by')}</span>
2024-01-22 20:22:03 -08:00
<OracleProvider />
</div>
2023-12-05 01:52:31 -08:00
<div className="mt-2">
{t('trade:last-updated')}{' '}
{dayjs
.duration({
seconds: -((highestSlot - oracleLastUpdatedSlot) * 0.5),
})
.humanize(true)}
.
</div>
{isStale ? (
<div className="mt-2 font-black">
{t('trade:oracle-not-updated')}
<br />
{t('trade:oracle-not-updated-warning')}
</div>
) : undefined}
</>
) : (
t('trade:stub-oracle-description', {
market: selectedMarket?.name || t('trade:this-market'),
})
)
2023-04-25 13:21:32 -07:00
}
>
<div className="flex items-center">
2023-05-11 21:08:06 -07:00
<div className="tooltip-underline mb-0.5 text-xs text-th-fgd-4">
2023-04-25 13:21:32 -07:00
{t('trade:oracle-price')}
</div>
2023-12-05 01:52:31 -08:00
{isStale || isStub ? (
<ExclamationTriangleIcon className="ml-1 h-4 w-4 text-th-warning" />
2023-05-11 21:08:06 -07:00
) : null}
2023-04-25 13:21:32 -07:00
</div>
</Tooltip>
<div className="font-mono text-xs text-th-fgd-2">
{price ? (
2023-06-15 04:24:54 -07:00
<>
{quoteBank?.name === 'USDC' ? '$' : ''}
{formatNumericValue(price, oracleDecimals)}
{quoteBank?.name && quoteBank.name !== 'USDC' ? (
2023-06-15 04:24:54 -07:00
<span className="font-body text-th-fgd-3">
{' '}
{CURRENCY_SYMBOLS[quoteBank.name] || quoteBank.name}
2023-06-15 04:24:54 -07:00
</span>
) : null}
</>
2023-04-25 13:21:32 -07:00
) : (
<span className="text-th-fgd-4"></span>
)}
</div>
</div>
</>
)
}
export default OraclePrice