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,