From 7e072b1da8113e76d9d623ca93c0ac390989e3ee Mon Sep 17 00:00:00 2001 From: Jared Vu Date: Thu, 21 Nov 2024 12:36:33 -0800 Subject: [PATCH] feat(spot-volume): show spot-volume and market cap (#1318) --- src/constants/markets.ts | 2 + .../tradingView/useTradingViewLaunchable.ts | 2 +- src/hooks/useLaunchableMarkets.ts | 134 +----------------- src/hooks/useMarketsData.ts | 21 ++- src/hooks/useMetadataService.ts | 117 +++++++++++++++ src/hooks/userPerpetualMarkets.ts | 21 +++ src/pages/trade/TradeHeaderMobile.tsx | 2 +- src/styles/constants.css | 2 +- src/views/LaunchableMarketStatsDetails.tsx | 2 +- .../MarketDetails/LaunchableMarketDetails.tsx | 2 +- src/views/MarketLinks.tsx | 2 +- src/views/MarketsDropdown.tsx | 18 ++- src/views/charts/LaunchableMarketChart.tsx | 2 +- .../NewMarketForm/v7/NewMarketPreviewStep.tsx | 2 +- src/views/tables/MarketsTable.tsx | 16 +++ 15 files changed, 201 insertions(+), 144 deletions(-) create mode 100644 src/hooks/useMetadataService.ts create mode 100644 src/hooks/userPerpetualMarkets.ts diff --git a/src/constants/markets.ts b/src/constants/markets.ts index c662623ed..fbf792cfa 100644 --- a/src/constants/markets.ts +++ b/src/constants/markets.ts @@ -30,6 +30,8 @@ export type MarketData = { tickSizeDecimals?: Nullable; trades24H?: Nullable; volume24H?: Nullable; + spotVolume24H?: Nullable; + marketCap?: Nullable; tags?: Nullable; isFavorite: boolean; }; diff --git a/src/hooks/tradingView/useTradingViewLaunchable.ts b/src/hooks/tradingView/useTradingViewLaunchable.ts index 4443c90b7..e9ab1065e 100644 --- a/src/hooks/tradingView/useTradingViewLaunchable.ts +++ b/src/hooks/tradingView/useTradingViewLaunchable.ts @@ -22,7 +22,7 @@ import { getTvChartConfig } from '@/state/tradingViewSelectors'; import { getLaunchableMarketDatafeed } from '@/lib/tradingView/launchableMarketFeed'; import { getSavedResolution, getWidgetOptions, getWidgetOverrides } from '@/lib/tradingView/utils'; -import { useMetadataService } from '../useLaunchableMarkets'; +import { useMetadataService } from '../useMetadataService'; /** * @description Hook to initialize TradingView Chart diff --git a/src/hooks/useLaunchableMarkets.ts b/src/hooks/useLaunchableMarkets.ts index 6aa298abd..ad710c343 100644 --- a/src/hooks/useLaunchableMarkets.ts +++ b/src/hooks/useLaunchableMarkets.ts @@ -1,117 +1,10 @@ import { useMemo } from 'react'; -import { useQueries, useQuery } from '@tanstack/react-query'; - -import { - MetadataServiceAsset, - MetadataServiceCandlesTimeframes, - MetadataServiceInfoResponse, - MetadataServicePricesResponse, -} from '@/constants/assetMetadata'; -import { timeUnits } from '@/constants/time'; - -import metadataClient from '@/clients/metadataService'; -import { getAssetFromMarketId } from '@/lib/assetUtils'; -import { getTickSizeDecimalsFromPrice } from '@/lib/numbers'; -import { mapMetadataServiceCandles } from '@/lib/tradingView/utils'; - -import { useDydxClient } from './useDydxClient'; - -const ASSETS_TO_REMOVE = ['USDC', 'USDT']; - -export const useMetadataService = () => { - const metadataQuery = useQueries({ - queries: [ - { - queryKey: ['marketMapInfo'], - queryFn: async (): Promise => { - return metadataClient.getAssetInfo(); - }, - refetchOnMount: false, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - }, - { - queryKey: ['marketMapPrice'], - queryFn: async (): Promise => { - return metadataClient.getAssetPrices(); - }, - refetchInterval: timeUnits.minute * 5, - }, - ], - combine: (results) => { - const info = results[0].data; - const prices = results[1].data; - const data: Record = {}; - - Object.keys(info ?? {}).forEach((key) => { - const infoKey = info?.[key]; - const pricesKey = prices?.[key]; - - if (infoKey && pricesKey && !ASSETS_TO_REMOVE.includes(key)) { - const tickSizeDecimals = getTickSizeDecimalsFromPrice(pricesKey.price); - - data[key] = { - id: key, - name: infoKey.name, - logo: infoKey.logo, - urls: { - website: infoKey.urls.website, - technicalDoc: infoKey.urls.technical_doc, - cmc: infoKey.urls.cmc, - }, - sectorTags: infoKey.sector_tags, - exchanges: infoKey.exchanges, - price: pricesKey.price, - percentChange24h: pricesKey.percent_change_24h, - marketCap: pricesKey.market_cap, - volume24h: pricesKey.volume_24h, - reportedMarketCap: pricesKey.self_reported_market_cap, - tickSizeDecimals, - }; - } - }); - - return { - data, - isLoading: results.some((result) => result.isLoading), - isError: results.some((result) => result.isError), - isSuccess: results.every((result) => result.isSuccess), - }; - }, - }); - - return metadataQuery; -}; - -export const useMetadataServiceAssetFromId = (marketId?: string) => { - const metadataServiceData = useMetadataService(); - - const launchableAsset = useMemo(() => { - if (!metadataServiceData.data || !marketId) { - return null; - } - - const assetId = getAssetFromMarketId(marketId); - return metadataServiceData.data[assetId]; - }, [metadataServiceData.data, marketId]); - - return launchableAsset; -}; +import { useMetadataService } from './useMetadataService'; +import { usePerpetualMarkets } from './userPerpetualMarkets'; export const useLaunchableMarkets = () => { - const { requestAllPerpetualMarkets } = useDydxClient(); - - const perpetualMarketsFetch = useQuery({ - queryKey: ['requestAllPerpetualMarkets'], - queryFn: requestAllPerpetualMarkets, - refetchInterval: timeUnits.minute, - staleTime: timeUnits.minute, - refetchOnMount: false, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - }); - + const perpetualMarketsFetch = usePerpetualMarkets(); const metadataServiceData = useMetadataService(); const filteredPotentialMarkets: { id: string; asset: string }[] = useMemo(() => { @@ -132,24 +25,3 @@ export const useLaunchableMarkets = () => { data: filteredPotentialMarkets, }; }; - -export const useMetadataServiceCandles = ( - asset?: string, - timeframe?: MetadataServiceCandlesTimeframes -) => { - const candlesQuery = useQuery({ - enabled: !!asset && !!timeframe, - queryKey: ['candles', asset, timeframe], - queryFn: async () => { - return metadataClient.getCandles({ asset: asset!, timeframe: timeframe! }); - }, - refetchInterval: timeUnits.minute * 5, - refetchOnMount: false, - refetchOnWindowFocus: false, - }); - - return { - ...candlesQuery, - data: candlesQuery.data?.[asset ?? '']?.map(mapMetadataServiceCandles), - }; -}; diff --git a/src/hooks/useMarketsData.ts b/src/hooks/useMarketsData.ts index 132d3ab01..7f44f5670 100644 --- a/src/hooks/useMarketsData.ts +++ b/src/hooks/useMarketsData.ts @@ -30,7 +30,7 @@ import { matchesSearchFilter } from '@/lib/search'; import { testFlags } from '@/lib/testFlags'; import { orEmptyObj, orEmptyRecord } from '@/lib/typeUtils'; -import { useMetadataService } from './useLaunchableMarkets'; +import { useMetadataService } from './useMetadataService'; import { useAllStatsigGateValues } from './useStatsig'; const filterFunctions = { @@ -136,7 +136,7 @@ export const useMarketsData = ({ const allAssets = orEmptyRecord(useAppSelector(getAssets, shallowEqual)); const sevenDaysSparklineData = usePerpetualMarketSparklines(); const featureFlags = useAllStatsigGateValues(); - const unlaunchedMarkets = useMetadataService(); + const metadataServiceInfo = useMetadataService(); const shouldHideLaunchableMarkets = useAppSelector(getShouldHideLaunchableMarkets) || hideUnlaunchedMarkets; const favoritedMarkets = useAppSelector(getFavoritedMarkets, shallowEqual); @@ -165,6 +165,12 @@ export const useMarketsData = ({ sevenDaysSparklineData && sevenDaySparklineEntries < SEVEN_DAY_SPARKLINE_ENTRIES ); const clobPairId = allPerpetualClobIds[marketData.id] ?? 0; + const { + volume24h: spotVolume24H, + marketCap, + reportedMarketCap, + } = orEmptyObj(metadataServiceInfo.data[marketData.assetId]); + const { assetId, displayId, @@ -205,13 +211,15 @@ export const useMarketsData = ({ tickSizeDecimals, trades24H, volume24H, + spotVolume24H, + marketCap: marketCap ?? reportedMarketCap, isFavorite: favoritedMarkets.includes(id), } ); }); if (!shouldHideLaunchableMarkets && testFlags.pml) { - const unlaunchedMarketsData = Object.values(unlaunchedMarkets.data) + const unlaunchedMarketsData = Object.values(metadataServiceInfo.data) .sort(sortByMarketCap) .map((market) => { const { @@ -222,6 +230,9 @@ export const useMarketsData = ({ price, percentChange24h, tickSizeDecimals, + volume24h: spotVolume24H, + marketCap, + reportedMarketCap, } = market; if (allPerpetualMarketIdsSet.has(assetId)) return null; @@ -255,6 +266,8 @@ export const useMarketsData = ({ tickSizeDecimals, trades24H: undefined, volume24H: undefined, + spotVolume24H, + marketCap: marketCap ?? reportedMarketCap, isFavorite: favoritedMarkets.includes(id), } ); @@ -271,7 +284,7 @@ export const useMarketsData = ({ sevenDaysSparklineData, allPerpetualClobIds, allAssets, - unlaunchedMarkets.data, + metadataServiceInfo.data, favoritedMarkets, ]); diff --git a/src/hooks/useMetadataService.ts b/src/hooks/useMetadataService.ts new file mode 100644 index 000000000..38c0369bf --- /dev/null +++ b/src/hooks/useMetadataService.ts @@ -0,0 +1,117 @@ +import { useMemo } from 'react'; + +import { useQueries, useQuery } from '@tanstack/react-query'; + +import { + MetadataServiceAsset, + MetadataServiceCandlesTimeframes, + MetadataServiceInfoResponse, + MetadataServicePricesResponse, +} from '@/constants/assetMetadata'; +import { timeUnits } from '@/constants/time'; + +import metadataClient from '@/clients/metadataService'; +import { getAssetFromMarketId } from '@/lib/assetUtils'; +import { getTickSizeDecimalsFromPrice } from '@/lib/numbers'; +import { mapMetadataServiceCandles } from '@/lib/tradingView/utils'; + +const ASSETS_TO_REMOVE = ['USDC', 'USDT']; + +export const useMetadataService = () => { + const metadataQuery = useQueries({ + queries: [ + { + queryKey: ['marketMapInfo'], + queryFn: async (): Promise => { + return metadataClient.getAssetInfo(); + }, + refetchOnMount: false, + refetchOnWindowFocus: false, + refetchOnReconnect: false, + }, + { + queryKey: ['marketMapPrice'], + queryFn: async (): Promise => { + return metadataClient.getAssetPrices(); + }, + refetchInterval: timeUnits.minute * 5, + }, + ], + combine: (results) => { + const info = results[0].data; + const prices = results[1].data; + const data: Record = {}; + + Object.keys(info ?? {}).forEach((key) => { + const infoKey = info?.[key]; + const pricesKey = prices?.[key]; + + if (infoKey && pricesKey && !ASSETS_TO_REMOVE.includes(key)) { + const tickSizeDecimals = getTickSizeDecimalsFromPrice(pricesKey.price); + + data[key] = { + id: key, + name: infoKey.name, + logo: infoKey.logo, + urls: { + website: infoKey.urls.website, + technicalDoc: infoKey.urls.technical_doc, + cmc: infoKey.urls.cmc, + }, + sectorTags: infoKey.sector_tags, + exchanges: infoKey.exchanges, + price: pricesKey.price, + percentChange24h: pricesKey.percent_change_24h, + marketCap: pricesKey.market_cap, + volume24h: pricesKey.volume_24h, + reportedMarketCap: pricesKey.self_reported_market_cap, + tickSizeDecimals, + }; + } + }); + + return { + data, + isLoading: results.some((result) => result.isLoading), + isError: results.some((result) => result.isError), + isSuccess: results.every((result) => result.isSuccess), + }; + }, + }); + + return metadataQuery; +}; + +export const useMetadataServiceAssetFromId = (marketId?: string) => { + const metadataServiceData = useMetadataService(); + + const launchableAsset = useMemo(() => { + if (!metadataServiceData.data || !marketId) { + return null; + } + + const assetId = getAssetFromMarketId(marketId); + return metadataServiceData.data[assetId]; + }, [metadataServiceData.data, marketId]); + + return launchableAsset; +}; + +export const useMetadataServiceCandles = ( + asset?: string, + timeframe?: MetadataServiceCandlesTimeframes +) => { + const candlesQuery = useQuery({ + enabled: !!asset && !!timeframe, + queryKey: ['candles', asset, timeframe], + queryFn: async () => { + const candles = await metadataClient.getCandles({ asset: asset!, timeframe: timeframe! }); + return candles[asset ?? '']?.map(mapMetadataServiceCandles); + }, + refetchInterval: timeUnits.minute * 5, + refetchOnMount: false, + refetchOnWindowFocus: false, + }); + + return candlesQuery; +}; diff --git a/src/hooks/userPerpetualMarkets.ts b/src/hooks/userPerpetualMarkets.ts new file mode 100644 index 000000000..b5adcf0ff --- /dev/null +++ b/src/hooks/userPerpetualMarkets.ts @@ -0,0 +1,21 @@ +import { useQuery } from '@tanstack/react-query'; + +import { timeUnits } from '@/constants/time'; + +import { useDydxClient } from './useDydxClient'; + +export const usePerpetualMarkets = () => { + const { requestAllPerpetualMarkets } = useDydxClient(); + + const perpetualMarketsFetch = useQuery({ + queryKey: ['requestAllPerpetualMarkets'], + queryFn: requestAllPerpetualMarkets, + refetchInterval: timeUnits.minute, + staleTime: timeUnits.minute, + refetchOnMount: false, + refetchOnWindowFocus: false, + refetchOnReconnect: false, + }); + + return perpetualMarketsFetch; +}; diff --git a/src/pages/trade/TradeHeaderMobile.tsx b/src/pages/trade/TradeHeaderMobile.tsx index 5d7fe221c..0f8b01cc7 100644 --- a/src/pages/trade/TradeHeaderMobile.tsx +++ b/src/pages/trade/TradeHeaderMobile.tsx @@ -4,7 +4,7 @@ import styled from 'styled-components'; import { AppRoute } from '@/constants/routes'; -import { useMetadataServiceAssetFromId } from '@/hooks/useLaunchableMarkets'; +import { useMetadataServiceAssetFromId } from '@/hooks/useMetadataService'; import { layoutMixins } from '@/styles/layoutMixins'; diff --git a/src/styles/constants.css b/src/styles/constants.css index 890852c96..0ad3bfd03 100644 --- a/src/styles/constants.css +++ b/src/styles/constants.css @@ -44,7 +44,7 @@ /* Market Selector constants */ --marketsDropdown-openWidth-deprecated: 45rem; - --marketsDropdown-openWidth: 40rem; + --marketsDropdown-openWidth: 50rem; /* Form constants */ --form-input-gap: 0.625rem; diff --git a/src/views/LaunchableMarketStatsDetails.tsx b/src/views/LaunchableMarketStatsDetails.tsx index be7a042c6..b7a218f22 100644 --- a/src/views/LaunchableMarketStatsDetails.tsx +++ b/src/views/LaunchableMarketStatsDetails.tsx @@ -8,7 +8,7 @@ import { USD_DECIMALS } from '@/constants/numbers'; import { TooltipStringKeys } from '@/constants/tooltips'; import { useBreakpoints } from '@/hooks/useBreakpoints'; -import { useMetadataServiceAssetFromId } from '@/hooks/useLaunchableMarkets'; +import { useMetadataServiceAssetFromId } from '@/hooks/useMetadataService'; import { useStringGetter } from '@/hooks/useStringGetter'; import breakpoints from '@/styles/breakpoints'; diff --git a/src/views/MarketDetails/LaunchableMarketDetails.tsx b/src/views/MarketDetails/LaunchableMarketDetails.tsx index 0ba625657..88b2fe052 100644 --- a/src/views/MarketDetails/LaunchableMarketDetails.tsx +++ b/src/views/MarketDetails/LaunchableMarketDetails.tsx @@ -2,7 +2,7 @@ import { STRING_KEYS } from '@/constants/localization'; import { ISOLATED_LIQUIDITY_TIER_INFO } from '@/constants/markets'; import { TooltipStringKeys } from '@/constants/tooltips'; -import { useMetadataServiceAssetFromId } from '@/hooks/useLaunchableMarkets'; +import { useMetadataServiceAssetFromId } from '@/hooks/useMetadataService'; import { useStringGetter } from '@/hooks/useStringGetter'; import { DetailsItem } from '@/components/Details'; diff --git a/src/views/MarketLinks.tsx b/src/views/MarketLinks.tsx index 157fbf0c6..5d8b01a56 100644 --- a/src/views/MarketLinks.tsx +++ b/src/views/MarketLinks.tsx @@ -3,7 +3,7 @@ import styled from 'styled-components'; import { ButtonType } from '@/constants/buttons'; -import { useMetadataServiceAssetFromId } from '@/hooks/useLaunchableMarkets'; +import { useMetadataServiceAssetFromId } from '@/hooks/useMetadataService'; import { IconName } from '@/components/Icon'; import { IconButton } from '@/components/IconButton'; diff --git a/src/views/MarketsDropdown.tsx b/src/views/MarketsDropdown.tsx index 3564996af..20c3a3175 100644 --- a/src/views/MarketsDropdown.tsx +++ b/src/views/MarketsDropdown.tsx @@ -11,9 +11,9 @@ import { MarketFilters, PREDICTION_MARKET, type MarketData } from '@/constants/m import { AppRoute, MarketsRoute } from '@/constants/routes'; import { StatsigFlags } from '@/constants/statsig'; -import { useMetadataServiceAssetFromId } from '@/hooks/useLaunchableMarkets'; import { useLocalStorage } from '@/hooks/useLocalStorage'; import { useMarketsData } from '@/hooks/useMarketsData'; +import { useMetadataServiceAssetFromId } from '@/hooks/useMetadataService'; import { useParameterizedSelector } from '@/hooks/useParameterizedSelector'; import { usePotentialMarkets } from '@/hooks/usePotentialMarkets'; import { useAllStatsigGateValues } from '@/hooks/useStatsig'; @@ -150,6 +150,22 @@ const MarketsDropdownContent = ({ <$Output type={OutputType.CompactFiat} value={row.volume24H} /> ), }, + { + columnKey: 'spotVolume24H', + getCellValue: (row: MarketData) => row.spotVolume24H, + label: stringGetter({ key: STRING_KEYS.SPOT_VOLUME_24H }), + renderCell: (row: MarketData) => ( + <$Output type={OutputType.CompactFiat} value={row.spotVolume24H} /> + ), + }, + { + columnKey: 'marketCap', + getCellValue: (row: MarketData) => row.marketCap, + label: stringGetter({ key: STRING_KEYS.MARKET_CAP }), + renderCell: (row: MarketData) => ( + <$Output type={OutputType.CompactFiat} value={row.marketCap} /> + ), + }, !uiRefresh && { columnKey: 'openInterest', getCellValue: (row: MarketData) => row.openInterestUSDC, diff --git a/src/views/charts/LaunchableMarketChart.tsx b/src/views/charts/LaunchableMarketChart.tsx index 43ab5f496..76aa96028 100644 --- a/src/views/charts/LaunchableMarketChart.tsx +++ b/src/views/charts/LaunchableMarketChart.tsx @@ -17,7 +17,7 @@ import { TooltipStringKeys } from '@/constants/tooltips'; import { useMetadataServiceAssetFromId, useMetadataServiceCandles, -} from '@/hooks/useLaunchableMarkets'; +} from '@/hooks/useMetadataService'; import { useStringGetter } from '@/hooks/useStringGetter'; import { LinkOutIcon } from '@/icons'; diff --git a/src/views/forms/NewMarketForm/v7/NewMarketPreviewStep.tsx b/src/views/forms/NewMarketForm/v7/NewMarketPreviewStep.tsx index 03f29462e..305d1a90b 100644 --- a/src/views/forms/NewMarketForm/v7/NewMarketPreviewStep.tsx +++ b/src/views/forms/NewMarketForm/v7/NewMarketPreviewStep.tsx @@ -14,7 +14,7 @@ import { DEFAULT_VAULT_DEPOSIT_FOR_LAUNCH } from '@/constants/numbers'; import { timeUnits } from '@/constants/time'; import { useCustomNotification } from '@/hooks/useCustomNotification'; -import { useMetadataServiceAssetFromId } from '@/hooks/useLaunchableMarkets'; +import { useMetadataServiceAssetFromId } from '@/hooks/useMetadataService'; import { useNow } from '@/hooks/useNow'; import { useStringGetter } from '@/hooks/useStringGetter'; import { useSubaccount } from '@/hooks/useSubaccount'; diff --git a/src/views/tables/MarketsTable.tsx b/src/views/tables/MarketsTable.tsx index b339236b9..7d94cb1c9 100644 --- a/src/views/tables/MarketsTable.tsx +++ b/src/views/tables/MarketsTable.tsx @@ -224,6 +224,22 @@ export const MarketsTable = forwardRef( <$NumberOutput type={OutputType.CompactFiat} value={row.volume24H} /> ), }, + { + columnKey: 'spotVolume24H', + getCellValue: (row) => row.spotVolume24H, + label: stringGetter({ key: STRING_KEYS.SPOT_VOLUME_24H }), + renderCell: (row) => ( + <$NumberOutput type={OutputType.CompactFiat} value={row.spotVolume24H} /> + ), + }, + { + columnKey: 'market-cap', + getCellValue: (row) => row.marketCap, + label: stringGetter({ key: STRING_KEYS.MARKET_CAP }), + renderCell: (row) => ( + <$NumberOutput type={OutputType.CompactFiat} value={row.marketCap} /> + ), + }, { columnKey: 'trades24H', getCellValue: (row) => row.trades24H,