diff --git a/src/features/markets/components/table/market-table-body.tsx b/src/features/markets/components/table/market-table-body.tsx index e8611cda..35f6e0bb 100644 --- a/src/features/markets/components/table/market-table-body.tsx +++ b/src/features/markets/components/table/market-table-body.tsx @@ -31,7 +31,7 @@ type MarketTableBodyProps = { export function MarketTableBody({ currentEntries, expandedRowId, setExpandedRowId, trustedVaultMap }: MarketTableBodyProps) { const { columnVisibility, starredMarkets, starMarket, unstarMarket } = useMarketPreferences(); const { success: toastSuccess } = useStyledToast(); - const { isRateEnrichmentLoading } = useProcessedMarkets(); + const { rateEnrichmentPendingChainIds } = useProcessedMarkets(); const { label: supplyRateLabel } = useRateLabel({ prefix: 'Supply' }); const { label: borrowRateLabel } = useRateLabel({ prefix: 'Borrow' }); @@ -42,7 +42,7 @@ export function MarketTableBody({ currentEntries, expandedRowId, setExpandedRowI return ; } - if (isRateEnrichmentLoading) { + if (rateEnrichmentPendingChainIds.has(market.morphoBlue.chain.id)) { return ( ; } - if (isRateEnrichmentLoading) { + if (isRateEnrichmentPending) { return ( getMarketIdentityKey(row.market.morphoBlue.chain.id, row.market.uniqueKey), [row.market.morphoBlue.chain.id, row.market.uniqueKey], @@ -161,6 +161,7 @@ export function BorrowedMorphoBlueRowDetail({ row }: BorrowedMorphoBlueRowDetail : row, [liveMarket, row], ); + const isRateEnrichmentPending = rateEnrichmentPendingChainIds.has(resolvedRow.market.morphoBlue.chain.id); const { currentLtvLabel, displayLtv, liquidationOraclePrice, lltv, lltvLabel, ltvWidth, oraclePrice } = deriveBorrowPositionMetrics(resolvedRow); @@ -214,11 +215,11 @@ export function BorrowedMorphoBlueRowDetail({ row }: BorrowedMorphoBlueRowDetail

Borrow Rates

diff --git a/src/hooks/queries/useMarketRateEnrichmentQuery.ts b/src/hooks/queries/useMarketRateEnrichmentQuery.ts index 37d03fef..bcf0ec5d 100644 --- a/src/hooks/queries/useMarketRateEnrichmentQuery.ts +++ b/src/hooks/queries/useMarketRateEnrichmentQuery.ts @@ -1,26 +1,94 @@ import { useMemo } from 'react'; -import { useQuery } from '@tanstack/react-query'; +import { useQueries } from '@tanstack/react-query'; import { useCustomRpcContext } from '@/components/providers/CustomRpcProvider'; -import { fetchMarketRateEnrichment, getMarketRateEnrichmentKey, type MarketRateEnrichmentMap } from '@/utils/market-rate-enrichment'; +import { + fetchMarketRateEnrichment, + getMarketRateEnrichmentKey, + type MarketRateEnrichment, + type MarketRateEnrichmentMap, +} from '@/utils/market-rate-enrichment'; +import type { SupportedNetworks } from '@/utils/networks'; import type { Market } from '@/utils/types'; const EMPTY_ENRICHMENT_MAP: MarketRateEnrichmentMap = new Map(); +const EMPTY_PENDING_CHAIN_IDS = new Set(); export const useMarketRateEnrichmentQuery = (markets: Market[]) => { const { customRpcUrls } = useCustomRpcContext(); - const marketIdentity = useMemo( - () => markets.map((market) => getMarketRateEnrichmentKey(market.uniqueKey, market.morphoBlue.chain.id)).sort(), - [markets], - ); - const rpcIdentity = useMemo(() => Object.entries(customRpcUrls).sort(([left], [right]) => Number(left) - Number(right)), [customRpcUrls]); - - return useQuery({ - queryKey: ['market-rate-enrichment', marketIdentity, rpcIdentity], - queryFn: async () => fetchMarketRateEnrichment(markets, customRpcUrls), - enabled: markets.length > 0, - staleTime: 15 * 60 * 1000, - gcTime: 30 * 60 * 1000, - refetchOnWindowFocus: false, - placeholderData: (previousData) => previousData ?? EMPTY_ENRICHMENT_MAP, + const marketsByChain = useMemo(() => { + const grouped = new Map(); + + markets.forEach((market) => { + const chainId = market.morphoBlue.chain.id as SupportedNetworks; + const chainMarkets = grouped.get(chainId) ?? []; + chainMarkets.push(market); + grouped.set(chainId, chainMarkets); + }); + + return Array.from(grouped.entries()).sort(([left], [right]) => left - right); + }, [markets]); + const enrichmentQueries = useQueries({ + queries: marketsByChain.map(([chainId, chainMarkets]) => { + const marketIdentity = chainMarkets.map((market) => getMarketRateEnrichmentKey(market.uniqueKey, chainId)).sort(); + const customRpcUrl = customRpcUrls[chainId]; + + return { + queryKey: ['market-rate-enrichment', chainId, marketIdentity, customRpcUrl ?? null], + queryFn: async () => fetchMarketRateEnrichment(chainMarkets, customRpcUrl ? { [chainId]: customRpcUrl } : {}), + enabled: chainMarkets.length > 0, + staleTime: 15 * 60 * 1000, + gcTime: 30 * 60 * 1000, + refetchOnWindowFocus: false, + }; + }), }); + + const data = useMemo(() => { + if (enrichmentQueries.length === 0) { + return EMPTY_ENRICHMENT_MAP; + } + + const merged = new Map(); + + enrichmentQueries.forEach((query) => { + if (!query.data) { + return; + } + + query.data.forEach((value, key) => { + merged.set(key, value); + }); + }); + + return merged.size > 0 ? merged : EMPTY_ENRICHMENT_MAP; + }, [enrichmentQueries]); + + const pendingChainIds = useMemo(() => { + if (marketsByChain.length === 0) { + return EMPTY_PENDING_CHAIN_IDS; + } + + const pending = new Set(); + + marketsByChain.forEach(([chainId], index) => { + const query = enrichmentQueries[index]; + if (!query || query.isPending || (!query.data && query.isFetching)) { + pending.add(chainId); + } + }); + + return pending.size > 0 ? pending : EMPTY_PENDING_CHAIN_IDS; + }, [marketsByChain, enrichmentQueries]); + + const isFetching = enrichmentQueries.some((query) => query.isFetching); + const isRefetching = enrichmentQueries.some((query) => query.isRefetching); + const isLoading = data.size === 0 && pendingChainIds.size > 0; + + return { + data, + pendingChainIds, + isLoading, + isFetching, + isRefetching, + }; }; diff --git a/src/hooks/useProcessedMarkets.ts b/src/hooks/useProcessedMarkets.ts index 50c9e8e5..7fb8ea92 100644 --- a/src/hooks/useProcessedMarkets.ts +++ b/src/hooks/useProcessedMarkets.ts @@ -152,13 +152,17 @@ export const useProcessedMarkets = () => { const { data: marketRateEnrichments = EMPTY_RATE_ENRICHMENTS, + pendingChainIds: rateEnrichmentPendingChainIds, isLoading: isRateEnrichmentQueryLoading, isFetching: isRateEnrichmentFetching, isRefetching: isRateEnrichmentRefetching, } = useMarketRateEnrichmentQuery(processedData.allMarkets); const isRateEnrichmentLoading = - processedData.allMarkets.length > 0 && marketRateEnrichments.size === 0 && (isRateEnrichmentQueryLoading || isRateEnrichmentFetching); + processedData.allMarkets.length > 0 && + marketRateEnrichments.size === 0 && + rateEnrichmentPendingChainIds.size > 0 && + (isRateEnrichmentQueryLoading || isRateEnrichmentFetching); const allMarketsWithRates = useMemo(() => { if (!processedData.allMarkets.length || marketRateEnrichments.size === 0) { @@ -300,6 +304,7 @@ export const useProcessedMarkets = () => { whitelistedMarkets: whitelistedMarketsWithUsd, markets, // Computed from setting (backward compatible with old context) isRateEnrichmentLoading, + rateEnrichmentPendingChainIds, loading: isLoading, isRefetching: isRefetching || isRateEnrichmentRefetching, error,