import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
import { DocsLinks, useEnvironment } from '@vegaprotocol/environment';
import { ButtonLink, ExternalLink, Link } from '@vegaprotocol/ui-toolkit';
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,
getQuoteName,
} 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);
const quoteUnit = getQuoteName(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, skip: boolean) => {
const { data: marketInfo } = useDataProvider({
dataProvider: marketInfoProvider,
variables: { marketId },
skip,
});
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, skip: boolean) => {
const { data: fundingPeriods } = useFundingPeriodsQuery({
pollInterval: 5000,
skip,
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 { data: marketTradingMode } = useMarketTradingMode(marketId);
const skip = !marketTradingMode || isMarketInAuction(marketTradingMode);
const startTime = useStartTime(marketId, skip);
const every = useEvery(marketId, skip);
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;
};