Merge pull request #202 from blockworks-foundation/fix-24h-change
fix 24h spot change
This commit is contained in:
commit
2c3f6208fe
|
@ -8,7 +8,7 @@ import {
|
|||
HourlyFundingData,
|
||||
HourlyFundingStatsData,
|
||||
} from 'types'
|
||||
import { MANGO_DATA_API_URL } from 'utils/constants'
|
||||
import { DAILY_MILLISECONDS, MANGO_DATA_API_URL } from 'utils/constants'
|
||||
import { formatCurrencyValue } from 'utils/numbers'
|
||||
import { TooltipProps } from 'recharts/types/component/Tooltip'
|
||||
import {
|
||||
|
@ -191,7 +191,7 @@ const FundingChart = ({ hideChart }: { hideChart: () => void }) => {
|
|||
|
||||
const filteredData: HourlyFundingChartData[] = useMemo(() => {
|
||||
if (!chartData.length) return []
|
||||
const start = Number(daysToShow) * 86400000
|
||||
const start = Number(daysToShow) * DAILY_MILLISECONDS
|
||||
const filtered = chartData.filter((d: HourlyFundingChartData) => {
|
||||
const date = new Date()
|
||||
if (daysToShow === '30') {
|
||||
|
|
|
@ -24,6 +24,7 @@ import { ArrowLeftIcon, NoSymbolIcon } from '@heroicons/react/20/solid'
|
|||
import { FadeInFadeOut } from '@components/shared/Transitions'
|
||||
import ContentBox from '@components/shared/ContentBox'
|
||||
import SheenLoader from '@components/shared/SheenLoader'
|
||||
import { DAILY_MILLISECONDS } from 'utils/constants'
|
||||
|
||||
const VolumeChart = ({
|
||||
chartData,
|
||||
|
@ -123,7 +124,7 @@ const VolumeChart = ({
|
|||
|
||||
const filteredData: FormattedHourlyAccountVolumeData[] = useMemo(() => {
|
||||
if (!chartData || !chartData.length) return []
|
||||
const start = Number(daysToShow) * 86400000
|
||||
const start = Number(daysToShow) * DAILY_MILLISECONDS
|
||||
const filtered = chartData.filter((d: FormattedHourlyAccountVolumeData) => {
|
||||
const date = new Date()
|
||||
if (daysToShow === '30') {
|
||||
|
|
|
@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react'
|
|||
import { Governance, Proposal } from '@solana/spl-governance'
|
||||
import dayjs from 'dayjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { DAILY_SECONDS } from 'utils/constants'
|
||||
|
||||
interface CountdownState {
|
||||
days: number
|
||||
|
@ -53,8 +54,8 @@ export function VoteCountdown({
|
|||
return ZeroCountdown
|
||||
}
|
||||
|
||||
const days = Math.floor(timeToVoteEnd / 86400)
|
||||
timeToVoteEnd -= days * 86400
|
||||
const days = Math.floor(timeToVoteEnd / DAILY_SECONDS)
|
||||
timeToVoteEnd -= days * DAILY_SECONDS
|
||||
|
||||
const hours = Math.floor(timeToVoteEnd / 3600) % 24
|
||||
timeToVoteEnd -= hours * 3600
|
||||
|
|
|
@ -8,6 +8,7 @@ import SheenLoader from '@components/shared/SheenLoader'
|
|||
import { NoSymbolIcon } from '@heroicons/react/20/solid'
|
||||
import { PerformanceDataItem } from 'types'
|
||||
import useAccountPerformanceData from 'hooks/useAccountPerformanceData'
|
||||
import { DAILY_MILLISECONDS } from 'utils/constants'
|
||||
|
||||
interface PnlChange {
|
||||
time: string
|
||||
|
@ -33,7 +34,7 @@ const PnlHistoryModal = ({
|
|||
if (!performanceData || !performanceData.length) return []
|
||||
|
||||
const dailyPnl = performanceData.filter((d: PerformanceDataItem) => {
|
||||
const startTime = new Date().getTime() - 30 * 86400000
|
||||
const startTime = new Date().getTime() - 30 * DAILY_MILLISECONDS
|
||||
const dataDate = new Date(d.time)
|
||||
const dataTime = dataDate.getTime()
|
||||
return dataTime >= startTime && dataDate.getHours() === 0
|
||||
|
|
|
@ -25,7 +25,7 @@ import { FadeInFadeOut } from './Transitions'
|
|||
import ChartRangeButtons from './ChartRangeButtons'
|
||||
import Change from './Change'
|
||||
import useLocalStorageState from 'hooks/useLocalStorageState'
|
||||
import { ANIMATION_SETTINGS_KEY } from 'utils/constants'
|
||||
import { ANIMATION_SETTINGS_KEY, DAILY_MILLISECONDS } from 'utils/constants'
|
||||
import { formatNumericValue } from 'utils/numbers'
|
||||
import { INITIAL_ANIMATION_SETTINGS } from '@components/settings/AnimationSettings'
|
||||
import { AxisDomain } from 'recharts/types/util/types'
|
||||
|
@ -120,7 +120,7 @@ const DetailedAreaOrBarChart: FunctionComponent<
|
|||
|
||||
const filteredData = useMemo(() => {
|
||||
if (!data.length) return []
|
||||
const start = Number(daysToShow) * 86400000
|
||||
const start = Number(daysToShow) * DAILY_MILLISECONDS
|
||||
const filtered = data.filter((d: any) => {
|
||||
const dataTime = new Date(d[xKey]).getTime()
|
||||
const now = new Date().getTime()
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
import { MinusSmallIcon } from '@heroicons/react/20/solid'
|
||||
import { DownTriangle, UpTriangle } from './DirectionTriangles'
|
||||
import FormatNumericValue from './FormatNumericValue'
|
||||
import { PerpMarket, Serum3Market } from '@blockworks-foundation/mango-v4'
|
||||
import use24HourChange from 'hooks/use24HourChange'
|
||||
import { useMemo } from 'react'
|
||||
import SheenLoader from './SheenLoader'
|
||||
|
||||
const MarketChange = ({
|
||||
market,
|
||||
size,
|
||||
}: {
|
||||
market: PerpMarket | Serum3Market | undefined
|
||||
size?: 'small'
|
||||
}) => {
|
||||
const { loading, spotChange, perpChange } = use24HourChange(market)
|
||||
|
||||
const change = useMemo(() => {
|
||||
if (!market) return
|
||||
return market instanceof PerpMarket ? perpChange : spotChange
|
||||
}, [perpChange, spotChange])
|
||||
|
||||
return loading ? (
|
||||
<SheenLoader className="mt-0.5">
|
||||
<div className="h-3.5 w-12 bg-th-bkg-2" />
|
||||
</SheenLoader>
|
||||
) : change && !isNaN(change) ? (
|
||||
<div className="flex items-center space-x-1.5">
|
||||
{change > 0 ? (
|
||||
<div className="mt-[1px]">
|
||||
<UpTriangle size={size} />
|
||||
</div>
|
||||
) : change < 0 ? (
|
||||
<div className="mt-[1px]">
|
||||
<DownTriangle size={size} />
|
||||
</div>
|
||||
) : (
|
||||
<MinusSmallIcon
|
||||
className={`-mr-1 ${
|
||||
size === 'small' ? 'h-4 w-4' : 'h-6 w-6'
|
||||
} text-th-fgd-4`}
|
||||
/>
|
||||
)}
|
||||
<p
|
||||
className={`font-mono font-normal ${
|
||||
size === 'small' ? 'text-xs' : 'text-sm'
|
||||
} ${
|
||||
change > 0
|
||||
? 'text-th-up'
|
||||
: change < 0
|
||||
? 'text-th-down'
|
||||
: 'text-th-fgd-4'
|
||||
}`}
|
||||
>
|
||||
<FormatNumericValue
|
||||
value={isNaN(change) ? '0.00' : Math.abs(change)}
|
||||
decimals={2}
|
||||
/>
|
||||
%
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<p>–</p>
|
||||
)
|
||||
}
|
||||
|
||||
export default MarketChange
|
|
@ -24,6 +24,7 @@ import SimpleAreaChart from '@components/shared/SimpleAreaChart'
|
|||
import { Disclosure, Transition } from '@headlessui/react'
|
||||
import { LinkButton } from '@components/shared/Button'
|
||||
import SoonBadge from '@components/shared/SoonBadge'
|
||||
import { DAILY_SECONDS } from 'utils/constants'
|
||||
|
||||
export const getOneDayPerpStats = (
|
||||
stats: PerpStatsItem[] | null,
|
||||
|
@ -33,7 +34,7 @@ export const getOneDayPerpStats = (
|
|||
? stats
|
||||
.filter((s) => s.perp_market === marketName)
|
||||
.filter((f) => {
|
||||
const seconds = 86400
|
||||
const seconds = DAILY_SECONDS
|
||||
const dataTime = new Date(f.date_hour).getTime() / 1000
|
||||
const now = new Date().getTime() / 1000
|
||||
const limit = now - seconds
|
||||
|
|
|
@ -20,6 +20,7 @@ import { fetchSpotVolume } from '@components/trade/AdvancedMarketHeader'
|
|||
import { TickerData } from 'types'
|
||||
import { Disclosure, Transition } from '@headlessui/react'
|
||||
import { ChevronDownIcon } from '@heroicons/react/20/solid'
|
||||
import MarketChange from '@components/shared/MarketChange'
|
||||
|
||||
const SpotMarketsTable = () => {
|
||||
const { t } = useTranslation('common')
|
||||
|
@ -87,7 +88,7 @@ const SpotMarketsTable = () => {
|
|||
(m) => m.mint === mkt.serumMarketExternal.toString()
|
||||
)
|
||||
|
||||
const change =
|
||||
const birdeyeChange =
|
||||
birdeyeData && price
|
||||
? ((price - birdeyeData.data[0].value) /
|
||||
birdeyeData.data[0].value) *
|
||||
|
@ -131,7 +132,7 @@ const SpotMarketsTable = () => {
|
|||
<div className="h-10 w-24">
|
||||
<SimpleAreaChart
|
||||
color={
|
||||
change >= 0
|
||||
birdeyeChange >= 0
|
||||
? COLORS.UP[theme]
|
||||
: COLORS.DOWN[theme]
|
||||
}
|
||||
|
@ -153,7 +154,7 @@ const SpotMarketsTable = () => {
|
|||
</Td>
|
||||
<Td>
|
||||
<div className="flex flex-col items-end">
|
||||
<Change change={change} suffix="%" />
|
||||
<MarketChange market={mkt} />
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
|
|
|
@ -13,6 +13,7 @@ import parse from 'html-react-parser'
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import { useMemo, useState } from 'react'
|
||||
import PriceChart from '@components/token/PriceChart'
|
||||
import { DAILY_SECONDS } from 'utils/constants'
|
||||
dayjs.extend(relativeTime)
|
||||
|
||||
const DEFAULT_COINGECKO_VALUES = {
|
||||
|
@ -44,7 +45,7 @@ const fetchBirdeyePrices = async (
|
|||
): Promise<BirdeyePriceResponse[] | []> => {
|
||||
const interval = daysToShow === '1' ? '30m' : daysToShow === '7' ? '1H' : '4H'
|
||||
const queryEnd = Math.floor(Date.now() / 1000)
|
||||
const queryStart = queryEnd - parseInt(daysToShow) * 86400
|
||||
const queryStart = queryEnd - parseInt(daysToShow) * DAILY_SECONDS
|
||||
const query = `defi/history_price?address=${mint}&address_type=token&type=${interval}&time_from=${queryStart}&time_to=${queryEnd}`
|
||||
const response: BirdeyeResponse = await makeApiRequest(query)
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
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'
|
||||
|
@ -10,9 +9,7 @@ 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'
|
||||
|
@ -23,6 +20,7 @@ import { TickerData } from 'types'
|
|||
import ManualRefresh from '@components/shared/ManualRefresh'
|
||||
import { useViewport } from 'hooks/useViewport'
|
||||
import { breakpoints } from 'utils/theme'
|
||||
import MarketChange from '@components/shared/MarketChange'
|
||||
|
||||
export const fetchSpotVolume = async () => {
|
||||
try {
|
||||
|
@ -43,17 +41,8 @@ const AdvancedMarketHeader = ({
|
|||
}) => {
|
||||
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 { serumOrPerpMarket, 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()
|
||||
|
@ -85,18 +74,6 @@ const AdvancedMarketHeader = ({
|
|||
}
|
||||
}, [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 ||
|
||||
|
@ -108,36 +85,6 @@ const AdvancedMarketHeader = ({
|
|||
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 (
|
||||
|
@ -155,19 +102,13 @@ const AdvancedMarketHeader = ({
|
|||
<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">
|
||||
<>
|
||||
<OraclePrice setChangePrice={setChangePrice} />
|
||||
<OraclePrice />
|
||||
</>
|
||||
<div className="ml-6 flex-col whitespace-nowrap">
|
||||
<div className="mb-0.5 text-xs text-th-fgd-4">
|
||||
{t('rolling-change')}
|
||||
</div>
|
||||
{!loadingPrices && !loadingPerpStats ? (
|
||||
<Change change={change} size="small" suffix="%" />
|
||||
) : (
|
||||
<SheenLoader className="mt-0.5">
|
||||
<div className="h-3.5 w-12 bg-th-bkg-2" />
|
||||
</SheenLoader>
|
||||
)}
|
||||
<MarketChange market={selectedMarket} size="small" />
|
||||
</div>
|
||||
{serumOrPerpMarket instanceof PerpMarket ? (
|
||||
<>
|
||||
|
|
|
@ -1,12 +1,7 @@
|
|||
// import ChartRangeButtons from '@components/shared/ChartRangeButtons'
|
||||
import Change from '@components/shared/Change'
|
||||
import FavoriteMarketButton from '@components/shared/FavoriteMarketButton'
|
||||
import SheenLoader from '@components/shared/SheenLoader'
|
||||
import { getOneDayPerpStats } from '@components/stats/PerpMarketsOverviewTable'
|
||||
import { Popover } from '@headlessui/react'
|
||||
import { ChevronDownIcon } from '@heroicons/react/20/solid'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { useBirdeyeMarketPrices } from 'hooks/useBirdeyeMarketPrices'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import useSelectedMarket from 'hooks/useSelectedMarket'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
@ -23,6 +18,7 @@ import SoonBadge from '@components/shared/SoonBadge'
|
|||
import TabButtons from '@components/shared/TabButtons'
|
||||
import { PerpMarket } from '@blockworks-foundation/mango-v4'
|
||||
import Loading from '@components/shared/Loading'
|
||||
import MarketChange from '@components/shared/MarketChange'
|
||||
|
||||
const MARKET_LINK_WRAPPER_CLASSES =
|
||||
'flex items-center justify-between px-4 md:pl-6 md:pr-4'
|
||||
|
@ -41,11 +37,7 @@ const MarketSelectDropdown = () => {
|
|||
)
|
||||
const serumMarkets = mangoStore((s) => s.serumMarkets)
|
||||
const allPerpMarkets = mangoStore((s) => s.perpMarkets)
|
||||
const perpStats = mangoStore((s) => s.perpStats.data)
|
||||
const loadingPerpStats = mangoStore((s) => s.perpStats.loading)
|
||||
const { group } = useMangoGroup()
|
||||
const { data: birdeyePrices, isLoading: loadingPrices } =
|
||||
useBirdeyeMarketPrices()
|
||||
const [spotBaseFilter, setSpotBaseFilter] = useState('All')
|
||||
|
||||
const perpMarkets = useMemo(() => {
|
||||
|
@ -130,14 +122,7 @@ const MarketSelectDropdown = () => {
|
|||
<div className="py-3">
|
||||
{spotOrPerp === 'perp' && perpMarkets?.length
|
||||
? perpMarkets.map((m) => {
|
||||
const changeData = getOneDayPerpStats(perpStats, m.name)
|
||||
const isComingSoon = m.oracleLastUpdatedSlot == 0
|
||||
|
||||
const change = changeData.length
|
||||
? ((m.uiPrice - changeData[0].price) /
|
||||
changeData[0].price) *
|
||||
100
|
||||
: 0
|
||||
return (
|
||||
<div
|
||||
className={MARKET_LINK_WRAPPER_CLASSES}
|
||||
|
@ -167,17 +152,7 @@ const MarketSelectDropdown = () => {
|
|||
getDecimalCount(m.tickSize)
|
||||
)}
|
||||
</span>
|
||||
{!loadingPerpStats ? (
|
||||
<Change
|
||||
change={change}
|
||||
suffix="%"
|
||||
size="small"
|
||||
/>
|
||||
) : (
|
||||
<SheenLoader className="mt-0.5">
|
||||
<div className="h-3.5 w-12 bg-th-bkg-2" />
|
||||
</SheenLoader>
|
||||
)}
|
||||
<MarketChange market={m} size="small" />
|
||||
</div>
|
||||
</Link>
|
||||
<FavoriteMarketButton market={m} />
|
||||
|
@ -216,12 +191,6 @@ const MarketSelectDropdown = () => {
|
|||
.map((x) => x)
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.map((m) => {
|
||||
const birdeyeData = birdeyePrices?.length
|
||||
? birdeyePrices.find(
|
||||
(market) =>
|
||||
market.mint === m.serumMarketExternal.toString()
|
||||
)
|
||||
: null
|
||||
const baseBank = group?.getFirstBankByTokenIndex(
|
||||
m.baseTokenIndex
|
||||
)
|
||||
|
@ -238,12 +207,6 @@ const MarketSelectDropdown = () => {
|
|||
getDecimalCount(market.tickSize)
|
||||
).toNumber()
|
||||
}
|
||||
const change =
|
||||
birdeyeData && price
|
||||
? ((price - birdeyeData.data[0].value) /
|
||||
birdeyeData.data[0].value) *
|
||||
100
|
||||
: 0
|
||||
return (
|
||||
<div
|
||||
className={MARKET_LINK_WRAPPER_CLASSES}
|
||||
|
@ -281,21 +244,7 @@ const MarketSelectDropdown = () => {
|
|||
) : null}
|
||||
</span>
|
||||
) : null}
|
||||
{!loadingPrices ? (
|
||||
change ? (
|
||||
<Change
|
||||
change={change}
|
||||
suffix="%"
|
||||
size="small"
|
||||
/>
|
||||
) : (
|
||||
<span className="text-th-fgd-3">–</span>
|
||||
)
|
||||
) : (
|
||||
<SheenLoader className="mt-0.5">
|
||||
<div className="h-3.5 w-12 bg-th-bkg-2" />
|
||||
</SheenLoader>
|
||||
)}
|
||||
<MarketChange market={m} size="small" />
|
||||
</div>
|
||||
</Link>
|
||||
<FavoriteMarketButton market={m} />
|
||||
|
|
|
@ -17,11 +17,7 @@ import relativeTime from 'dayjs/plugin/relativeTime'
|
|||
import useOracleProvider from 'hooks/useOracleProvider'
|
||||
import { ArrowTopRightOnSquareIcon } from '@heroicons/react/20/solid'
|
||||
|
||||
const OraclePrice = ({
|
||||
setChangePrice,
|
||||
}: {
|
||||
setChangePrice: (price: number) => void
|
||||
}) => {
|
||||
const OraclePrice = () => {
|
||||
const {
|
||||
serumOrPerpMarket,
|
||||
price: stalePrice,
|
||||
|
@ -96,7 +92,6 @@ const OraclePrice = ({
|
|||
|
||||
if (selectedMarket instanceof PerpMarket) {
|
||||
setPrice(uiPrice)
|
||||
setChangePrice(uiPrice)
|
||||
} else {
|
||||
let price
|
||||
if (quoteBank && serumOrPerpMarket) {
|
||||
|
@ -108,7 +103,6 @@ const OraclePrice = ({
|
|||
price = 0
|
||||
}
|
||||
setPrice(price)
|
||||
setChangePrice(price)
|
||||
}
|
||||
},
|
||||
'processed'
|
||||
|
@ -118,14 +112,7 @@ const OraclePrice = ({
|
|||
connection.removeAccountChangeListener(subId)
|
||||
}
|
||||
}
|
||||
}, [
|
||||
connection,
|
||||
selectedMarket,
|
||||
serumOrPerpMarket,
|
||||
setChangePrice,
|
||||
quoteBank,
|
||||
stalePrice,
|
||||
])
|
||||
}, [connection, selectedMarket, serumOrPerpMarket, quoteBank, stalePrice])
|
||||
|
||||
const oracleDecimals = getDecimalCount(serumOrPerpMarket?.tickSize || 0.01)
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import NotificationCookieStore from '@store/notificationCookieStore'
|
|||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import { fetchNotificationSettings } from 'apis/notifications/notificationSettings'
|
||||
import { useIsAuthorized } from './useIsAuthorized'
|
||||
import { DAILY_MILLISECONDS } from 'utils/constants'
|
||||
|
||||
export function useNotificationSettings() {
|
||||
const { publicKey } = useWallet()
|
||||
|
@ -18,7 +19,7 @@ export function useNotificationSettings() {
|
|||
{
|
||||
enabled: !!isAuth,
|
||||
retry: 1,
|
||||
staleTime: 86400000,
|
||||
staleTime: DAILY_MILLISECONDS,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
import { PerpMarket, Serum3Market } from '@blockworks-foundation/mango-v4'
|
||||
import { getOneDayPerpStats } from '@components/stats/PerpMarketsOverviewTable'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import dayjs from 'dayjs'
|
||||
import utc from 'dayjs/plugin/utc'
|
||||
import { useMemo } from 'react'
|
||||
import { DAILY_SECONDS, MANGO_DATA_API_URL } from 'utils/constants'
|
||||
|
||||
dayjs.extend(utc)
|
||||
|
||||
const fetchPrices = async (market: Serum3Market | PerpMarket | undefined) => {
|
||||
if (!market || market instanceof PerpMarket) return
|
||||
const { baseTokenIndex, quoteTokenIndex } = market
|
||||
const nowTimestamp = Date.now() / 1000
|
||||
const changePriceTimestamp = nowTimestamp - DAILY_SECONDS
|
||||
const changePriceTime = dayjs
|
||||
.unix(changePriceTimestamp)
|
||||
.utc()
|
||||
.format('YYYY-MM-DDTHH:mm:ss[Z]')
|
||||
const promises = [
|
||||
fetch(
|
||||
`${MANGO_DATA_API_URL}/stats/token-price?token-index=${baseTokenIndex}&price-time=${changePriceTime}`
|
||||
),
|
||||
fetch(
|
||||
`${MANGO_DATA_API_URL}/stats/token-price?token-index=${quoteTokenIndex}&price-time=${changePriceTime}`
|
||||
),
|
||||
]
|
||||
try {
|
||||
const data = await Promise.all(promises)
|
||||
const baseTokenPriceData = await data[0].json()
|
||||
const quoteTokenPriceData = await data[1].json()
|
||||
const baseTokenPrice = baseTokenPriceData ? baseTokenPriceData.price : 1
|
||||
const quoteTokenPrice = quoteTokenPriceData ? quoteTokenPriceData.price : 1
|
||||
return { baseTokenPrice, quoteTokenPrice }
|
||||
} catch (e) {
|
||||
console.log('failed to fetch 24hr price data', e)
|
||||
return { baseTokenPrice: 1, quoteTokenPrice: 1 }
|
||||
}
|
||||
}
|
||||
|
||||
export default function use24HourChange(
|
||||
market: Serum3Market | PerpMarket | undefined
|
||||
) {
|
||||
const perpStats = mangoStore((s) => s.perpStats.data)
|
||||
const loadingPerpStats = mangoStore((s) => s.perpStats.loading)
|
||||
const {
|
||||
data: priceData,
|
||||
isLoading: loadingPriceData,
|
||||
isFetching: fetchingPriceData,
|
||||
} = useQuery(['token-prices', market], () => fetchPrices(market), {
|
||||
cacheTime: 1000 * 60 * 10,
|
||||
staleTime: 1000 * 60,
|
||||
retry: 3,
|
||||
refetchOnWindowFocus: false,
|
||||
enabled: market && market instanceof Serum3Market,
|
||||
})
|
||||
|
||||
const [currentBasePrice, currentQuotePrice] = useMemo(() => {
|
||||
const group = mangoStore.getState().group
|
||||
if (!group || !market || market instanceof PerpMarket)
|
||||
return [undefined, undefined]
|
||||
const baseBank = group.getFirstBankByTokenIndex(market.baseTokenIndex)
|
||||
const quoteBank = group.getFirstBankByTokenIndex(market.quoteTokenIndex)
|
||||
return [baseBank?.uiPrice, quoteBank?.uiPrice]
|
||||
}, [market])
|
||||
|
||||
const perpChange = useMemo(() => {
|
||||
if (
|
||||
!market ||
|
||||
market instanceof Serum3Market ||
|
||||
!perpStats ||
|
||||
!perpStats.length
|
||||
)
|
||||
return
|
||||
const oneDayStats = getOneDayPerpStats(perpStats, market.name)
|
||||
const currentPrice = market.uiPrice
|
||||
const change = oneDayStats.length
|
||||
? ((currentPrice - oneDayStats[0].price) / oneDayStats[0].price) * 100
|
||||
: undefined
|
||||
return change
|
||||
}, [market, perpStats])
|
||||
|
||||
const spotChange = useMemo(() => {
|
||||
if (!market) return
|
||||
if (!currentBasePrice || !currentQuotePrice || !priceData) return
|
||||
const currentPrice = currentBasePrice / currentQuotePrice
|
||||
const oneDayPrice = priceData.baseTokenPrice / priceData.quoteTokenPrice
|
||||
const change = ((currentPrice - oneDayPrice) / oneDayPrice) * 100
|
||||
return change
|
||||
}, [market, priceData])
|
||||
|
||||
const loading = useMemo(() => {
|
||||
if (!market) return false
|
||||
if (market instanceof PerpMarket) return loadingPerpStats
|
||||
return loadingPriceData || fetchingPriceData
|
||||
}, [market, loadingPerpStats, loadingPriceData, fetchingPriceData])
|
||||
|
||||
return { loading, perpChange, spotChange }
|
||||
}
|
|
@ -3,6 +3,7 @@ import { fetchAccountPerformance } from 'utils/account'
|
|||
import useMangoAccount from './useMangoAccount'
|
||||
import { useMemo } from 'react'
|
||||
import { PerformanceDataItem } from 'types'
|
||||
import { DAILY_MILLISECONDS } from 'utils/constants'
|
||||
|
||||
export default function useAccountPerformanceData() {
|
||||
const { mangoAccountAddress } = useMangoAccount()
|
||||
|
@ -28,7 +29,7 @@ export default function useAccountPerformanceData() {
|
|||
const nowDate = new Date()
|
||||
return performanceData.filter((d) => {
|
||||
const dataTime = new Date(d.time).getTime()
|
||||
return dataTime >= nowDate.getTime() - 86400000
|
||||
return dataTime >= nowDate.getTime() - DAILY_MILLISECONDS
|
||||
})
|
||||
}, [performanceData])
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Serum3Market } from '@blockworks-foundation/mango-v4'
|
|||
import mangoStore from '@store/mangoStore'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { makeApiRequest } from 'apis/birdeye/helpers'
|
||||
import { DAILY_SECONDS } from 'utils/constants'
|
||||
|
||||
export interface BirdeyePriceResponse {
|
||||
address: string
|
||||
|
@ -18,7 +19,7 @@ const fetchBirdeyePrices = async (
|
|||
|
||||
const promises = []
|
||||
const queryEnd = Math.floor(Date.now() / 1000)
|
||||
const queryStart = queryEnd - 86400
|
||||
const queryStart = queryEnd - DAILY_SECONDS
|
||||
for (const mint of mints) {
|
||||
const query = `defi/history_price?address=${mint}&address_type=pair&type=30m&time_from=${queryStart}&time_to=${queryEnd}`
|
||||
promises.push(makeApiRequest(query))
|
||||
|
|
|
@ -128,3 +128,6 @@ export const CUSTOM_TOKEN_ICONS: { [key: string]: boolean } = {
|
|||
wbtcpo: true,
|
||||
'wbtc (portal)': true,
|
||||
}
|
||||
|
||||
export const DAILY_SECONDS = 86400
|
||||
export const DAILY_MILLISECONDS = 86400000
|
||||
|
|
Loading…
Reference in New Issue