import { BookSide, PerpMarket } from '@blockworks-foundation/mango-v4' import { InformationCircleIcon } from '@heroicons/react/24/outline' import { useQuery } from '@tanstack/react-query' import useMangoGroup from 'hooks/useMangoGroup' import useSelectedMarket from 'hooks/useSelectedMarket' import { useMemo } from 'react' import { MANGO_DATA_API_URL } from 'utils/constants' import Tooltip from '@components/shared/Tooltip' import { useTranslation } from 'next-i18next' import mangoStore from '@store/mangoStore' import { OrderbookL2 } from 'types' import Link from 'next/link' const fetchFundingRate = async (groupPk: string | undefined) => { const res = await fetch( `${MANGO_DATA_API_URL}/one-hour-funding-rate?mango-group=${groupPk}` ) return await res.json() } export const usePerpFundingRate = () => { const { group } = useMangoGroup() const res = useQuery< { market_index: number; funding_rate_hourly: number }[], Error >(['funding-rate'], () => fetchFundingRate(group?.publicKey?.toString()), { cacheTime: 1000 * 60 * 10, staleTime: 1000 * 60, retry: 3, enabled: !!group, }) return Array.isArray(res?.data) ? res : { isSuccess: false, data: null } } export const formatFunding = Intl.NumberFormat('en', { minimumSignificantDigits: 1, maximumSignificantDigits: 2, style: 'percent', }) function getImpactPriceL2( bookside: number[][], baseDepth: number ): number | undefined { let total = 0 for (const level of bookside) { total += level[1] if (total >= baseDepth) { return level[0] } } return undefined } function getInstantaneousFundingRateL2( market: PerpMarket, orderbook: OrderbookL2 ) { const MIN_FUNDING = market.minFunding.toNumber() const MAX_FUNDING = market.maxFunding.toNumber() const bid = getImpactPriceL2( orderbook.bids, market.baseLotsToUi(market.impactQuantity) ) const ask = getImpactPriceL2( orderbook.asks, market.baseLotsToUi(market.impactQuantity) ) const indexPrice = market._uiPrice let funding if (bid !== undefined && ask !== undefined) { const bookPrice = (bid + ask) / 2 funding = Math.min( Math.max(bookPrice / indexPrice - 1, MIN_FUNDING), MAX_FUNDING ) } else if (bid !== undefined) { funding = MAX_FUNDING } else if (ask !== undefined) { funding = MIN_FUNDING } else { funding = 0 } return funding } const PerpFundingRate = () => { const { selectedMarket } = useSelectedMarket() const rate = usePerpFundingRate() const { t } = useTranslation(['common', 'trade']) const bids = mangoStore((s) => s.selectedMarket.bidsAccount) const asks = mangoStore((s) => s.selectedMarket.asksAccount) const orderbook = mangoStore((s) => s.selectedMarket.orderbook) const fundingRate = useMemo(() => { if (rate.isSuccess && selectedMarket instanceof PerpMarket) { const marketRate = rate?.data?.find( (r) => r.market_index === selectedMarket.perpMarketIndex ) const funding = marketRate?.funding_rate_hourly return typeof funding === 'number' ? funding : undefined } }, [rate, selectedMarket]) const instantaneousRate = useMemo(() => { if (!(selectedMarket instanceof PerpMarket)) return undefined if (bids instanceof BookSide && asks instanceof BookSide) { return selectedMarket.getInstantaneousFundingRateUi(bids, asks).toFixed(4) } if (orderbook.asks.length && orderbook.bids.length) { return ( getInstantaneousFundingRateL2(selectedMarket, orderbook) * 100 ).toFixed(4) } return undefined }, [orderbook, bids, asks, selectedMarket]) return ( <>
Funding is paid continuously. The 1hr rate displayed is a rolling average of the past 60 mins.
When positive, longs will pay shorts and when negative shorts pay longs.
{typeof fundingRate === 'number' ? (
The 1hr rate as an APR is{' '} {formatFunding.format(fundingRate * 8760)}
) : null} {instantaneousRate ? (
The latest instantaneous rate is{' '} {instantaneousRate}%
) : null} View Chart } >
{t('trade:funding-rate')}

{selectedMarket instanceof PerpMarket && typeof fundingRate === 'number' ? ( {formatFunding.format(fundingRate)} ) : ( - )}

) } export default PerpFundingRate