import { useAssetDetailsDialogStore } from '@vegaprotocol/assets'; import { DocsLinks, useEnvironment } from '@vegaprotocol/environment'; import { ButtonLink, ExternalLink, Link } from '@vegaprotocol/ui-toolkit'; import { MarketProposalNotification } from '@vegaprotocol/proposals'; import type { Market } from '@vegaprotocol/markets'; import { addDecimalsFormatNumber, fromNanoSeconds, getExpiryDate, getMarketExpiryDate, } from '@vegaprotocol/utils'; import { Last24hPriceChange, Last24hVolume, getAsset, getDataSourceSpecForSettlementSchedule, isMarketInAuction, marketInfoProvider, useFundingPeriodsQuery, useFundingRate, useMarketTradingMode, useExternalTwap, } from '@vegaprotocol/markets'; import { MarketState as State } from '@vegaprotocol/types'; import { HeaderStat } from '../../components/header'; import { MarketMarkPrice } from '../../components/market-mark-price'; import { HeaderStatMarketTradingMode } from '../../components/market-trading-mode'; import { MarketState } from '../../components/market-state'; import { MarketLiquiditySupplied } from '../../components/liquidity-supplied'; import { useEffect, useState } from 'react'; import { useDataProvider } from '@vegaprotocol/data-provider'; import { PriceCell } from '@vegaprotocol/datagrid'; import { useT } from '../../lib/use-t'; interface MarketHeaderStatsProps { market: Market; } export const MarketHeaderStats = ({ market }: MarketHeaderStatsProps) => { const t = useT(); const { VEGA_EXPLORER_URL } = useEnvironment(); const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore(); const asset = getAsset(market); return ( <> {asset ? (
{ openAssetDetailsDialog(asset.id, e.target as HTMLElement); }} > {asset.symbol}
) : null} {market.tradableInstrument.instrument.product.__typename === 'Future' && ( } testId="market-expiry" > )} {market.tradableInstrument.instrument.product.__typename === 'Perpetual' && (
)} {market.tradableInstrument.instrument.product.__typename === 'Perpetual' && ( {t( 'The external time weighted average price (TWAP) received from the data source defined in the data sourcing specification.' )} {DocsLinks && ( {t('Find out more')} )} } testId="index-price" > )} ); }; type ExpiryLabelProps = { market: Market; }; export const FundingRate = ({ marketId }: { marketId: string }) => { const { data: fundingRate } = useFundingRate(marketId); return (
{fundingRate ? `${(Number(fundingRate) * 100).toFixed(4)}%` : '-'}
); }; export const IndexPrice = ({ marketId, decimalPlaces, }: { marketId: string; decimalPlaces?: number; }) => { const { data: externalTwap } = useExternalTwap(marketId); return externalTwap && decimalPlaces ? ( ) : ( '-' ); }; const useNow = () => { const [now, setNow] = useState(Date.now()); useEffect(() => { const interval = setInterval(() => setNow(Date.now()), 1000); return () => clearInterval(interval); }, []); return now; }; const useEvery = (marketId: string) => { const { data: marketTradingMode } = useMarketTradingMode(marketId); const { data: marketInfo } = useDataProvider({ dataProvider: marketInfoProvider, variables: { marketId }, skip: !marketTradingMode || isMarketInAuction(marketTradingMode), }); let every: number | undefined = undefined; const sourceType = marketInfo && getDataSourceSpecForSettlementSchedule( marketInfo.tradableInstrument.instrument.product )?.data.sourceType.sourceType; if (sourceType?.__typename === 'DataSourceSpecConfigurationTimeTrigger') { every = sourceType.triggers?.[0]?.every ?? undefined; if (every) { every *= 1000; } } return every; }; const useStartTime = (marketId: string) => { const { data: fundingPeriods } = useFundingPeriodsQuery({ variables: { marketId: marketId, pagination: { first: 1 }, }, }); const node = fundingPeriods?.fundingPeriods.edges?.[0]?.node; let startTime: number | undefined = undefined; if (node && node.startTime && !node.endTime) { startTime = fromNanoSeconds(node.startTime).getTime(); } return startTime; }; const padStart = (n: number) => n.toString().padStart(2, '0'); const useFormatCountdown = ( now: number, startTime?: number, every?: number ) => { const t = useT(); if (startTime && every) { const diff = every - ((now - startTime) % every); const hours = (diff / 3.6e6) | 0; const mins = ((diff % 3.6e6) / 6e4) | 0; const secs = Math.round((diff % 6e4) / 1e3); return `${padStart(hours)}:${padStart(mins)}:${padStart(secs)}`; } return t('Unknown'); }; export const FundingCountdown = ({ marketId }: { marketId: string }) => { const now = useNow(); const startTime = useStartTime(marketId); const every = useEvery(marketId); return (
{useFormatCountdown(now, startTime, every)}
); }; const ExpiryLabel = ({ market }: ExpiryLabelProps) => { const content = market.tradableInstrument.instrument.metadata.tags ? getExpiryDate( market.tradableInstrument.instrument.metadata.tags, market.marketTimestamps.close, market.state ) : '-'; return
{content}
; }; type ExpiryTooltipContentProps = { market: Market; explorerUrl?: string; }; const ExpiryTooltipContent = ({ market, explorerUrl, }: ExpiryTooltipContentProps) => { const t = useT(); if (market.marketTimestamps.close === null) { const oracleId = market.tradableInstrument.instrument.product.__typename === 'Future' ? market.tradableInstrument.instrument.product .dataSourceSpecForTradingTermination?.id : undefined; const metadataExpiryDate = getMarketExpiryDate( market.tradableInstrument.instrument.metadata.tags ); const isExpired = metadataExpiryDate && Date.now() - metadataExpiryDate.valueOf() > 0 && (market.state === State.STATE_TRADING_TERMINATED || market.state === State.STATE_SETTLED); return (

{t( 'This market expires when triggered by its oracle, not on a set date.' )}

{metadataExpiryDate && !isExpired && (

{t( 'This timestamp is user curated metadata and does not drive any on-chain functionality.' )}

)} {explorerUrl && oracleId && ( {t('View oracle specification')} )}
); } return null; };