From f59175118da2e6f16256633a085f1729194fd2a3 Mon Sep 17 00:00:00 2001 From: antoncoding Date: Fri, 8 May 2026 14:09:34 +0800 Subject: [PATCH 1/2] perf: optimize rpc usage --- .env.local.example | 4 +- src/constants/markets.ts | 13 +- src/data-sources/monarch-api/markets.ts | 53 ++++- .../components/table/market-table-body.tsx | 11 +- .../components/table/markets-table.tsx | 39 ++-- src/features/markets/markets-view.tsx | 18 +- .../borrowed-morpho-blue-row-detail.tsx | 35 ++- src/hooks/queries/useMarketsQuery.ts | 12 +- src/hooks/useFilteredMarkets.ts | 199 ++++++++++++++---- src/hooks/useProcessedMarkets.ts | 4 +- src/utils/market-rate-enrichment.ts | 90 +++++--- src/utils/market-rpc-gating.ts | 141 +++++++++++++ src/utils/marketFilters.ts | 19 +- src/utils/tokenMetadata.ts | 27 ++- 14 files changed, 542 insertions(+), 123 deletions(-) create mode 100644 src/utils/market-rpc-gating.ts diff --git a/.env.local.example b/.env.local.example index aad27e48..85528b6f 100644 --- a/.env.local.example +++ b/.env.local.example @@ -44,8 +44,8 @@ NEXT_PUBLIC_ARBITRUM_RPC= NEXT_PUBLIC_ETHERLINK_RPC= NEXT_PUBLIC_HYPEREVM_RPC= NEXT_PUBLIC_MONAD_RPC= -# Set to "false" locally to skip RPC historical-rate fallback when Morpho API rolling rates fail. -NEXT_PUBLIC_ENABLE_MARKET_RATE_RPC_FALLBACK=true +# Set to "true" only when RPC historical-rate fallback is intentionally needed. +NEXT_PUBLIC_ENABLE_MARKET_RATE_RPC_FALLBACK=false # ==================== End of RPC Settings ==================== diff --git a/src/constants/markets.ts b/src/constants/markets.ts index 2780a6a3..6dc04a8b 100644 --- a/src/constants/markets.ts +++ b/src/constants/markets.ts @@ -1,3 +1,14 @@ export const DEFAULT_MIN_SUPPLY_USD = 1000; export const DEFAULT_MIN_LIQUIDITY_USD = 10_000; -export const LOCKED_MARKET_APY_THRESHOLD = 15; // APY where 1.0 = 100%, so 15 = 1500% + +// APY values are stored as decimals: 1 = 100%, 15 = 1500%. +// The default lock guard only hides clearly frozen markets with absurd APY. +// ETH-pegged loan markets get a tighter guard because several locked WETH +// markets sit around 200%, which is far below the generic 1500% cutoff but +// still not a useful live market signal. +export const LOCKED_MARKET_APY_THRESHOLDS = { + default: 15, + ethPegLoanAsset: 1, +} as const; + +export const LOCKED_MARKET_APY_THRESHOLD = LOCKED_MARKET_APY_THRESHOLDS.default; diff --git a/src/data-sources/monarch-api/markets.ts b/src/data-sources/monarch-api/markets.ts index 60a092c2..43ad3a93 100644 --- a/src/data-sources/monarch-api/markets.ts +++ b/src/data-sources/monarch-api/markets.ts @@ -6,6 +6,7 @@ import { isMarketRegistryEntryAllowed } from '@/utils/markets'; import { getMorphoAddress } from '@/utils/morpho'; import { isSupportedChain, type SupportedNetworks } from '@/utils/networks'; import { infoToKey } from '@/utils/tokens'; +import type { ERC20Token } from '@/utils/tokens'; import { resolveTokenInfos, type ResolvedTokenInfo, type TokenAddressInput } from '@/utils/tokenMetadata'; import type { Market, MarketWarning } from '@/utils/types'; import { UNRECOGNIZED_COLLATERAL, UNRECOGNIZED_LOAN } from '@/utils/warnings'; @@ -35,6 +36,15 @@ type MonarchMarketsPageResponse = { }; }; +type MapMonarchMarketRowsOptions = { + resolveUnknownTokens?: boolean; + trustedTokens?: ERC20Token[]; +}; + +type MapMonarchMarketOptions = { + warnOnMissingTokenInfo?: boolean; +}; + const MONARCH_MARKETS_PAGE_SIZE = 1_000; const MONARCH_MARKETS_TIMEOUT_MS = 15_000; const MONARCH_MARKETS_ZERO_ADDRESS = zeroAddress.toLowerCase(); @@ -127,7 +137,11 @@ const getMarketTokenInputs = (markets: MonarchMarketRow[]): TokenAddressInput[] return tokens; }; -const mapMonarchMarketToMarket = (market: MonarchMarketRow, tokenInfos: Map): Market | null => { +const mapMonarchMarketToMarket = ( + market: MonarchMarketRow, + tokenInfos: Map, + options: MapMonarchMarketOptions = {}, +): Market | null => { if (!isSupportedChain(market.chainId)) { return null; } @@ -150,7 +164,9 @@ const mapMonarchMarketToMarket = (market: MonarchMarketRow, tokenInfos: Map => { +const mapMonarchMarketRows = async ( + rows: MonarchMarketRow[], + customRpcUrls: CustomRpcUrls = {}, + options: MapMonarchMarketRowsOptions = {}, +): Promise => { if (rows.length === 0) { return []; } - const tokenInfos = await resolveTokenInfos(getMarketTokenInputs(rows), customRpcUrls); + const tokenInfos = await resolveTokenInfos(getMarketTokenInputs(rows), customRpcUrls, { + resolveUnknownTokens: options.resolveUnknownTokens ?? true, + trustedTokens: options.trustedTokens, + }); + + const shouldResolveUnknownTokens = options.resolveUnknownTokens ?? true; - return rows.map((market) => mapMonarchMarketToMarket(market, tokenInfos)).filter((market): market is Market => market !== null); + return rows + .map((market) => + mapMonarchMarketToMarket(market, tokenInfos, { + warnOnMissingTokenInfo: shouldResolveUnknownTokens, + }), + ) + .filter((market): market is Market => market !== null); }; // If `network` is omitted, this fetches the merged multi-chain market registry in one query path. -export const fetchMonarchMarkets = async (network?: SupportedNetworks, customRpcUrls: CustomRpcUrls = {}): Promise => { +export const fetchMonarchMarkets = async ( + network?: SupportedNetworks, + customRpcUrls: CustomRpcUrls = {}, + options: MapMonarchMarketRowsOptions = {}, +): Promise => { const query = buildEnvioMarketsPageQuery({ useChainIdFilter: network !== undefined, }); @@ -242,7 +277,7 @@ export const fetchMonarchMarkets = async (network?: SupportedNetworks, customRpc offset += rows.length; } - return mapMonarchMarketRows(allRows, customRpcUrls); + return mapMonarchMarketRows(allRows, customRpcUrls, options); }; export const fetchMonarchMarket = async ( @@ -256,6 +291,8 @@ export const fetchMonarchMarket = async ( zeroAddress: MONARCH_MARKETS_ZERO_ADDRESS, }); - const [market] = await mapMonarchMarketRows(rows, customRpcUrls); + const [market] = await mapMonarchMarketRows(rows, customRpcUrls, { + resolveUnknownTokens: true, + }); return market ?? null; }; diff --git a/src/features/markets/components/table/market-table-body.tsx b/src/features/markets/components/table/market-table-body.tsx index b0e1a048..669bf3d2 100644 --- a/src/features/markets/components/table/market-table-body.tsx +++ b/src/features/markets/components/table/market-table-body.tsx @@ -10,7 +10,6 @@ import { MarketRiskIndicators } from '@/features/markets/components/market-risk- import OracleVendorBadge from '@/features/markets/components/oracle-vendor-badge'; import { TrustedByCell } from '@/features/autovault/components/trusted-vault-badges'; import { getVaultKey, type TrustedVault } from '@/constants/vaults/known_vaults'; -import { useProcessedMarkets } from '@/hooks/useProcessedMarkets'; import { useRateLabel } from '@/hooks/useRateLabel'; import { useStyledToast } from '@/hooks/useStyledToast'; import { useMarketPreferences } from '@/stores/useMarketPreferences'; @@ -26,14 +25,20 @@ type MarketTableBodyProps = { expandedRowId: string | null; setExpandedRowId: (id: string | null) => void; trustedVaultMap: Map; + rateEnrichmentPendingChainIds: Set; }; type HistoricalRateField = Exclude; -export function MarketTableBody({ currentEntries, expandedRowId, setExpandedRowId, trustedVaultMap }: MarketTableBodyProps) { +export function MarketTableBody({ + currentEntries, + expandedRowId, + setExpandedRowId, + trustedVaultMap, + rateEnrichmentPendingChainIds, +}: MarketTableBodyProps) { const { columnVisibility, starredMarkets, starMarket, unstarMarket } = useMarketPreferences(); const { success: toastSuccess } = useStyledToast(); - const { rateEnrichmentPendingChainIds } = useProcessedMarkets(); const { label: supplyRateLabel } = useRateLabel({ prefix: 'Supply' }); const { label: borrowRateLabel } = useRateLabel({ prefix: 'Borrow' }); diff --git a/src/features/markets/components/table/markets-table.tsx b/src/features/markets/components/table/markets-table.tsx index 9f627129..75ce815c 100644 --- a/src/features/markets/components/table/markets-table.tsx +++ b/src/features/markets/components/table/markets-table.tsx @@ -27,18 +27,6 @@ type MarketsTableProps = { }; function MarketsTable({ currentPage, setCurrentPage, className, tableClassName, onRefresh, isMobile }: MarketsTableProps) { - // Get loading states directly from query (no prop drilling!) - const { isLoading: loading, isRefetching, data: rawMarkets, dataUpdatedAt } = useMarketsQuery(); - - // Get trusted vaults directly from store (no prop drilling!) - const { vaults: trustedVaults } = useTrustedVaults(); - - const { markets, isLoading: filteredMarketsLoading, isWhitelistUnavailable } = useFilteredMarkets(); - const isEmpty = !rawMarkets; - const [expandedRowId, setExpandedRowId] = useState(null); - const { label: supplyRateLabel } = useRateLabel({ prefix: 'Supply' }); - const { label: borrowRateLabel } = useRateLabel({ prefix: 'Borrow' }); - const { columnVisibility, sortColumn, @@ -55,6 +43,32 @@ function MarketsTable({ currentPage, setCurrentPage, className, tableClassName, starredMarkets, } = useMarketPreferences(); + // Get loading states directly from query (no prop drilling!) + const { + isLoading: loading, + isRefetching, + data: rawMarkets, + dataUpdatedAt, + } = useMarketsQuery({ + includeUnknownTokens, + }); + + // Get trusted vaults directly from store (no prop drilling!) + const { vaults: trustedVaults } = useTrustedVaults(); + + const { + markets, + isLoading: filteredMarketsLoading, + isWhitelistUnavailable, + rateEnrichmentPendingChainIds, + } = useFilteredMarkets({ + currentPage, + }); + const isEmpty = !rawMarkets; + const [expandedRowId, setExpandedRowId] = useState(null); + const { label: supplyRateLabel } = useRateLabel({ prefix: 'Supply' }); + const { label: borrowRateLabel } = useRateLabel({ prefix: 'Borrow' }); + const { starredOnly } = useMarketsFilters(); // Handle column header clicks for sorting @@ -339,6 +353,7 @@ function MarketsTable({ currentPage, setCurrentPage, className, tableClassName, expandedRowId={expandedRowId} setExpandedRowId={setExpandedRowId} trustedVaultMap={trustedVaultMap} + rateEnrichmentPendingChainIds={rateEnrichmentPendingChainIds} /> )} diff --git a/src/features/markets/markets-view.tsx b/src/features/markets/markets-view.tsx index b010f4e6..83e6d023 100644 --- a/src/features/markets/markets-view.tsx +++ b/src/features/markets/markets-view.tsx @@ -22,10 +22,23 @@ export default function Markets() { const toast = useStyledToast(); const appliedUrlSignatureRef = useRef(null); const [currentSearchParams, setCurrentSearchParams] = useState(''); + const { tableViewMode, includeUnknownTokens } = useMarketPreferences(); // Data fetching with React Query - const { data: rawMarkets, isLoading: loading, refetch } = useMarketsQuery(); - const { markets, isLoading: filteredMarketsLoading, isWhitelistUnavailable } = useFilteredMarkets(); + const { + data: rawMarkets, + isLoading: loading, + refetch, + } = useMarketsQuery({ + includeUnknownTokens, + }); + const { + markets, + isLoading: filteredMarketsLoading, + isWhitelistUnavailable, + } = useFilteredMarkets({ + enableRateEnrichment: false, + }); const filters = useMarketsFilters(); const persistedFilters = useMarketFilterPreferences(); @@ -39,7 +52,6 @@ export default function Markets() { // Store hooks const { currentPage, setCurrentPage, resetPage } = usePagination(); const { allTokens } = useTokensQuery(); - const { tableViewMode, includeUnknownTokens } = useMarketPreferences(); useLayoutEffect(() => { setCurrentSearchParams(window.location.search.startsWith('?') ? window.location.search.slice(1) : window.location.search); diff --git a/src/features/positions/components/borrowed-morpho-blue-row-detail.tsx b/src/features/positions/components/borrowed-morpho-blue-row-detail.tsx index 9e843c13..63c44874 100644 --- a/src/features/positions/components/borrowed-morpho-blue-row-detail.tsx +++ b/src/features/positions/components/borrowed-morpho-blue-row-detail.tsx @@ -6,6 +6,7 @@ import { PulseLoader } from 'react-spinners'; import { RateFormatted } from '@/components/shared/rate-formatted'; import { TooltipContent } from '@/components/shared/tooltip-content'; import { Tooltip } from '@/components/ui/tooltip'; +import { useMarketRateEnrichmentQuery } from '@/hooks/queries/useMarketRateEnrichmentQuery'; import { useProcessedMarkets } from '@/hooks/useProcessedMarkets'; import { computeLiquidationOraclePrice, @@ -19,6 +20,7 @@ import { isInfiniteLtv, } from '@/modals/borrow/components/helpers'; import { getMarketIdentityKey } from '@/utils/market-identity'; +import { getMarketRateEnrichmentKey } from '@/utils/market-rate-enrichment'; import type { BorrowPositionRow } from '@/utils/positions'; type BorrowedMorphoBlueRowDetailProps = { @@ -142,7 +144,9 @@ function renderHistoricalRateValue(value: number | null | undefined, isRateEnric } export function BorrowedMorphoBlueRowDetail({ row }: BorrowedMorphoBlueRowDetailProps) { - const { allMarkets, rateEnrichmentPendingChainIds } = useProcessedMarkets(); + const { allMarkets } = useProcessedMarkets({ + enableRateEnrichment: false, + }); const marketIdentityKey = useMemo( () => getMarketIdentityKey(row.market.morphoBlue.chain.id, row.market.uniqueKey), [row.market.morphoBlue.chain.id, row.market.uniqueKey], @@ -151,15 +155,28 @@ export function BorrowedMorphoBlueRowDetail({ row }: BorrowedMorphoBlueRowDetail () => allMarkets.find((market) => getMarketIdentityKey(market.morphoBlue.chain.id, market.uniqueKey) === marketIdentityKey), [allMarkets, marketIdentityKey], ); + const rateMarket = liveMarket ?? row.market; + const { data: marketRateEnrichments, pendingChainIds: rateEnrichmentPendingChainIds } = useMarketRateEnrichmentQuery([rateMarket]); + const marketWithRates = useMemo(() => { + const enrichment = marketRateEnrichments.get(getMarketRateEnrichmentKey(rateMarket.uniqueKey, rateMarket.morphoBlue.chain.id)); + if (!enrichment) { + return rateMarket; + } + + return { + ...rateMarket, + state: { + ...rateMarket.state, + ...enrichment, + }, + }; + }, [marketRateEnrichments, rateMarket]); const resolvedRow = useMemo( - () => - liveMarket - ? { - ...row, - market: liveMarket, - } - : row, - [liveMarket, row], + () => ({ + ...row, + market: marketWithRates, + }), + [marketWithRates, row], ); const isRateEnrichmentPending = rateEnrichmentPendingChainIds.has(resolvedRow.market.morphoBlue.chain.id); diff --git a/src/hooks/queries/useMarketsQuery.ts b/src/hooks/queries/useMarketsQuery.ts index 1bf006d1..10bbc4f7 100644 --- a/src/hooks/queries/useMarketsQuery.ts +++ b/src/hooks/queries/useMarketsQuery.ts @@ -4,6 +4,7 @@ import { supportsMorphoApi } from '@/config/dataSources'; import { fetchMonarchMarkets } from '@/data-sources/monarch-api'; import { fetchMorphoMarkets } from '@/data-sources/morpho-api/market'; import { fetchSubgraphMarkets } from '@/data-sources/subgraph/market'; +import { useTokensQuery } from '@/hooks/queries/useTokensQuery'; import { getMarketIdentityKey } from '@/utils/market-identity'; import { ALL_SUPPORTED_NETWORKS, isSupportedChain, type SupportedNetworks } from '@/utils/networks'; import type { Market } from '@/utils/types'; @@ -16,6 +17,7 @@ const toError = (error: unknown): Error => { type UseMarketsQueryOptions = { refetchInterval?: number | false; refetchOnWindowFocus?: boolean; + includeUnknownTokens?: boolean; }; /** @@ -40,10 +42,12 @@ type UseMarketsQueryOptions = { */ export const useMarketsQuery = (options?: UseMarketsQueryOptions) => { const { customRpcUrls } = useCustomRpcContext(); + const { allTokens, isLoading: tokensLoading } = useTokensQuery(); const rpcIdentity = Object.entries(customRpcUrls).sort(([left], [right]) => Number(left) - Number(right)); + const includeUnknownTokens = options?.includeUnknownTokens ?? false; return useQuery({ - queryKey: ['markets', rpcIdentity], + queryKey: ['markets', rpcIdentity, includeUnknownTokens, allTokens.length], queryFn: async () => { const fetchErrors: Error[] = []; const marketsByChain = new Map(); @@ -76,7 +80,10 @@ export const useMarketsQuery = (options?: UseMarketsQueryOptions) => { }; try { - const monarchMarkets = await fetchMonarchMarkets(undefined, customRpcUrls); + const monarchMarkets = await fetchMonarchMarkets(undefined, customRpcUrls, { + resolveUnknownTokens: includeUnknownTokens, + trustedTokens: allTokens, + }); const monarchMarketsByChain = partitionMarketsByChain(monarchMarkets); for (const [network, markets] of monarchMarketsByChain.entries()) { @@ -153,5 +160,6 @@ export const useMarketsQuery = (options?: UseMarketsQueryOptions) => { staleTime: 5 * 60 * 1000, // Data is fresh for 5 minutes refetchInterval: options?.refetchInterval ?? 5 * 60 * 1000, // Auto-refetch every 5 minutes in background by default refetchOnWindowFocus: options?.refetchOnWindowFocus ?? true, // Refetch when user returns to tab by default + enabled: !tokensLoading, }); }; diff --git a/src/hooks/useFilteredMarkets.ts b/src/hooks/useFilteredMarkets.ts index 5b76c0ff..3fc8ddf7 100644 --- a/src/hooks/useFilteredMarkets.ts +++ b/src/hooks/useFilteredMarkets.ts @@ -1,4 +1,5 @@ import { useMemo } from 'react'; +import { useMarketRateEnrichmentQuery } from '@/hooks/queries/useMarketRateEnrichmentQuery'; import { useProcessedMarkets } from '@/hooks/useProcessedMarkets'; import { useMarketFilterPreferences } from '@/stores/useMarketFilterPreferences'; import { useMorphoWhitelistStatusQuery } from '@/hooks/queries/useMorphoWhitelistStatusQuery'; @@ -12,18 +13,94 @@ import { useOfficialTrendingMarketKeys, useCustomTagMarketKeys, getMetricsKey } import { filterMarkets, sortMarkets, createPropertySort, createStarredSort } from '@/utils/marketFilters'; import { SortColumn } from '@/features/markets/components/constants'; import { getVaultKey } from '@/constants/vaults/known_vaults'; +import { getMarketRateEnrichmentKey, type MarketRateEnrichmentMap } from '@/utils/market-rate-enrichment'; import type { Market } from '@/utils/types'; +type UseFilteredMarketsOptions = { + currentPage?: number; + enableRateEnrichment?: boolean; +}; + type UseFilteredMarketsResult = { markets: Market[]; isLoading: boolean; isWhitelistUnavailable: boolean; + rateEnrichmentPendingChainIds: Set; }; -export const useFilteredMarkets = (): UseFilteredMarketsResult => { +const HISTORICAL_RATE_SORT_COLUMNS = new Set([ + SortColumn.DailySupplyAPY, + SortColumn.DailyBorrowAPY, + SortColumn.WeeklySupplyAPY, + SortColumn.WeeklyBorrowAPY, + SortColumn.MonthlySupplyAPY, + SortColumn.MonthlyBorrowAPY, +]); +const EMPTY_PENDING_CHAIN_IDS = new Set(); + +const getSortPropertyPath = (sortColumn: SortColumn): string => { + const sortPropertyMap: Record = { + [SortColumn.Starred]: 'uniqueKey', + [SortColumn.LoanAsset]: 'loanAsset.name', + [SortColumn.CollateralAsset]: 'collateralAsset.name', + [SortColumn.LLTV]: 'lltv', + [SortColumn.Supply]: 'state.supplyAssetsUsd', + [SortColumn.Borrow]: 'state.borrowAssetsUsd', + [SortColumn.SupplyAPY]: 'state.supplyApy', + [SortColumn.Liquidity]: 'state.liquidityAssetsUsd', + [SortColumn.BorrowAPY]: 'state.borrowApy', + [SortColumn.RateAtTarget]: 'state.apyAtTarget', + [SortColumn.TrustedBy]: '', + [SortColumn.UtilizationRate]: 'state.utilization', + [SortColumn.Trend]: '', + [SortColumn.DailySupplyAPY]: 'state.dailySupplyApy', + [SortColumn.DailyBorrowAPY]: 'state.dailyBorrowApy', + [SortColumn.WeeklySupplyAPY]: 'state.weeklySupplyApy', + [SortColumn.WeeklyBorrowAPY]: 'state.weeklyBorrowApy', + [SortColumn.MonthlySupplyAPY]: 'state.monthlySupplyApy', + [SortColumn.MonthlyBorrowAPY]: 'state.monthlyBorrowApy', + }; + + return sortPropertyMap[sortColumn]; +}; + +const mergeRateEnrichments = (markets: Market[], marketRateEnrichments: MarketRateEnrichmentMap): Market[] => { + if (markets.length === 0 || marketRateEnrichments.size === 0) { + return markets; + } + + return markets.map((market) => { + const enrichment = marketRateEnrichments.get(getMarketRateEnrichmentKey(market.uniqueKey, market.morphoBlue.chain.id)); + if (!enrichment) { + return market; + } + + return { + ...market, + state: { + ...market.state, + ...enrichment, + }, + }; + }); +}; + +export const useFilteredMarkets = (options?: UseFilteredMarketsOptions): UseFilteredMarketsResult => { const preferences = useMarketPreferences(); const persistedFilters = useMarketFilterPreferences(); - const { allMarkets, whitelistedMarkets } = useProcessedMarkets(); + const isHistoricalRateSort = HISTORICAL_RATE_SORT_COLUMNS.has(preferences.sortColumn); + const historicalRateColumnsVisible = + preferences.columnVisibility.dailySupplyAPY || + preferences.columnVisibility.dailyBorrowAPY || + preferences.columnVisibility.weeklySupplyAPY || + preferences.columnVisibility.weeklyBorrowAPY || + preferences.columnVisibility.monthlySupplyAPY || + preferences.columnVisibility.monthlyBorrowAPY; + const shouldEnableRateEnrichment = options?.enableRateEnrichment ?? (isHistoricalRateSort || historicalRateColumnsVisible); + const { allMarkets, whitelistedMarkets } = useProcessedMarkets({ + enableRateEnrichment: false, + includeUnknownTokens: preferences.includeUnknownTokens, + }); const { whitelistLookup, isLoading: whitelistLoading, isFetching: whitelistFetching } = useMorphoWhitelistStatusQuery(); const { data: oracleMetadataMap } = useAllOracleMetadata(); const filters = useMarketsFilters(); @@ -34,7 +111,7 @@ export const useFilteredMarkets = (): UseFilteredMarketsResult => { const customTagKeys = useCustomTagMarketKeys(); const shouldBlockWhitelistedFiltering = !showUnwhitelistedMarkets && whitelistLookup.size === 0; - const markets = useMemo(() => { + const filteredCandidates = useMemo(() => { if (shouldBlockWhitelistedFiltering) { return []; } @@ -103,14 +180,35 @@ export const useFilteredMarkets = (): UseFilteredMarketsResult => { filteredMarkets = filteredMarkets.filter((market) => starredSet.has(market.uniqueKey)); } + return filteredMarkets; + }, [ + allMarkets, + whitelistedMarkets, + shouldBlockWhitelistedFiltering, + showUnwhitelistedMarkets, + filters, + persistedFilters, + preferences, + trustedVaults, + findToken, + officialTrendingKeys, + customTagKeys, + oracleMetadataMap, + ]); + + const sortedCandidates = useMemo(() => { + if (filteredCandidates.length === 0) { + return filteredCandidates; + } + if (preferences.sortColumn === SortColumn.Starred) { - return sortMarkets(filteredMarkets, createStarredSort(preferences.starredMarkets), 1); + return sortMarkets(filteredCandidates, createStarredSort(preferences.starredMarkets), 1); } if (preferences.sortColumn === SortColumn.TrustedBy) { const trustedVaultKeys = new Set(trustedVaults.map((vault) => getVaultKey(vault.address, vault.chainId))); return sortMarkets( - filteredMarkets, + filteredCandidates, (a, b) => { const aHasTrusted = a.supplyingVaults?.some((v) => v.address && trustedVaultKeys.has(getVaultKey(v.address, a.morphoBlue.chain.id))) ?? false; @@ -122,52 +220,69 @@ export const useFilteredMarkets = (): UseFilteredMarketsResult => { ); } - const sortPropertyMap: Record = { - [SortColumn.Starred]: 'uniqueKey', - [SortColumn.LoanAsset]: 'loanAsset.name', - [SortColumn.CollateralAsset]: 'collateralAsset.name', - [SortColumn.LLTV]: 'lltv', - [SortColumn.Supply]: 'state.supplyAssetsUsd', - [SortColumn.Borrow]: 'state.borrowAssetsUsd', - [SortColumn.SupplyAPY]: 'state.supplyApy', - [SortColumn.Liquidity]: 'state.liquidityAssetsUsd', - [SortColumn.BorrowAPY]: 'state.borrowApy', - [SortColumn.RateAtTarget]: 'state.apyAtTarget', - [SortColumn.TrustedBy]: '', - [SortColumn.UtilizationRate]: 'state.utilization', - [SortColumn.Trend]: '', // Trend is a filter mode, not a sort - [SortColumn.DailySupplyAPY]: 'state.dailySupplyApy', - [SortColumn.DailyBorrowAPY]: 'state.dailyBorrowApy', - [SortColumn.WeeklySupplyAPY]: 'state.weeklySupplyApy', - [SortColumn.WeeklyBorrowAPY]: 'state.weeklyBorrowApy', - [SortColumn.MonthlySupplyAPY]: 'state.monthlySupplyApy', - [SortColumn.MonthlyBorrowAPY]: 'state.monthlyBorrowApy', - }; - - const propertyPath = sortPropertyMap[preferences.sortColumn]; + const propertyPath = getSortPropertyPath(preferences.sortColumn); if (propertyPath) { - return sortMarkets(filteredMarkets, createPropertySort(propertyPath), preferences.sortDirection as 1 | -1); + return sortMarkets(filteredCandidates, createPropertySort(propertyPath), preferences.sortDirection as 1 | -1); } - return filteredMarkets; + return filteredCandidates; + }, [filteredCandidates, preferences.sortColumn, preferences.sortDirection, preferences.starredMarkets, trustedVaults]); + + const rateEnrichmentTargets = useMemo(() => { + if (!shouldEnableRateEnrichment) { + return []; + } + + if (isHistoricalRateSort) { + return filteredCandidates; + } + + const currentPage = Math.max(1, options?.currentPage ?? 1); + const startIndex = (currentPage - 1) * preferences.entriesPerPage; + return sortedCandidates.slice(startIndex, startIndex + preferences.entriesPerPage); }, [ - allMarkets, - whitelistedMarkets, - shouldBlockWhitelistedFiltering, - showUnwhitelistedMarkets, - filters, - persistedFilters, - preferences, - trustedVaults, - findToken, - officialTrendingKeys, - customTagKeys, - oracleMetadataMap, + shouldEnableRateEnrichment, + isHistoricalRateSort, + filteredCandidates, + sortedCandidates, + options?.currentPage, + preferences.entriesPerPage, + ]); + + const { data: marketRateEnrichments, pendingChainIds: rateEnrichmentPendingChainIds } = + useMarketRateEnrichmentQuery(rateEnrichmentTargets); + + const markets = useMemo(() => { + if (!shouldEnableRateEnrichment || marketRateEnrichments.size === 0) { + return sortedCandidates; + } + + if (!isHistoricalRateSort) { + return mergeRateEnrichments(sortedCandidates, marketRateEnrichments); + } + + const enrichedCandidates = mergeRateEnrichments(filteredCandidates, marketRateEnrichments); + const propertyPath = getSortPropertyPath(preferences.sortColumn); + + if (!propertyPath) { + return enrichedCandidates; + } + + return sortMarkets(enrichedCandidates, createPropertySort(propertyPath), preferences.sortDirection as 1 | -1); + }, [ + shouldEnableRateEnrichment, + marketRateEnrichments, + sortedCandidates, + isHistoricalRateSort, + filteredCandidates, + preferences.sortColumn, + preferences.sortDirection, ]); return { markets, isLoading: shouldBlockWhitelistedFiltering && (whitelistLoading || whitelistFetching), isWhitelistUnavailable: shouldBlockWhitelistedFiltering && !whitelistLoading && !whitelistFetching, + rateEnrichmentPendingChainIds: shouldEnableRateEnrichment ? rateEnrichmentPendingChainIds : EMPTY_PENDING_CHAIN_IDS, }; }; diff --git a/src/hooks/useProcessedMarkets.ts b/src/hooks/useProcessedMarkets.ts index 7bf3813b..d34ff1f6 100644 --- a/src/hooks/useProcessedMarkets.ts +++ b/src/hooks/useProcessedMarkets.ts @@ -16,6 +16,7 @@ type UseProcessedMarketsOptions = { enableMorphoMetadata?: boolean; enableRateEnrichment?: boolean; enableUsdEnrichment?: boolean; + includeUnknownTokens?: boolean; }; const EMPTY_RATE_ENRICHMENTS: MarketRateEnrichmentMap = new Map(); @@ -66,7 +67,7 @@ const hasSameSupplyingVaults = (current: Market['supplyingVaults'], next: Market * ``` */ export const useProcessedMarkets = (options?: UseProcessedMarketsOptions) => { - const enableRateEnrichment = options?.enableRateEnrichment ?? true; + const enableRateEnrichment = options?.enableRateEnrichment ?? false; const enableUsdEnrichment = options?.enableUsdEnrichment ?? true; const enableMorphoMetadata = options?.enableMorphoMetadata ?? true; const { @@ -78,6 +79,7 @@ export const useProcessedMarkets = (options?: UseProcessedMarketsOptions) => { } = useMarketsQuery({ refetchInterval: options?.marketsRefetchInterval, refetchOnWindowFocus: options?.marketsRefetchOnWindowFocus, + includeUnknownTokens: options?.includeUnknownTokens, }); const { whitelistLookup, supplyingVaultsLookup } = useMorphoWhitelistStatusQuery({ enabled: enableMorphoMetadata, diff --git a/src/utils/market-rate-enrichment.ts b/src/utils/market-rate-enrichment.ts index 8468ba35..a350dd0e 100644 --- a/src/utils/market-rate-enrichment.ts +++ b/src/utils/market-rate-enrichment.ts @@ -1,5 +1,4 @@ import { supportsMorphoApi } from '@/config/dataSources'; -import { fetchMorphoMarket } from '@/data-sources/morpho-api/market'; import { fetchMorphoMarketRateEnrichments, getMorphoMarketRateFieldsKey } from '@/data-sources/morpho-api/market-rate-fields'; import { AdaptiveCurveIrmLib, MarketUtils, MathLib, SharesMath } from '@morpho-org/blue-sdk'; import type { Address } from 'viem'; @@ -11,11 +10,13 @@ import { fetchMarketsSnapshots, type MarketSnapshot } from '@/utils/positions'; import { computeAnnualizedApyFromGrowth } from '@/utils/rateMath'; import { getClient } from '@/utils/rpc'; import type { Market } from '@/utils/types'; +import { shouldUseRateRpcFallbackForMarket } from '@/utils/market-rpc-gating'; const SECONDS_PER_DAY = 24 * 60 * 60; const BORROW_RATE_BATCH_SIZE = 100; const BORROW_RATE_PARALLEL_BATCHES = 2; -const MARKET_RATE_RPC_FALLBACK_ENABLED = process.env.NEXT_PUBLIC_ENABLE_MARKET_RATE_RPC_FALLBACK?.trim().toLowerCase() !== 'false'; +const MARKET_RATE_RPC_FALLBACK_ENABLED = process.env.NEXT_PUBLIC_ENABLE_MARKET_RATE_RPC_FALLBACK?.trim().toLowerCase() === 'true'; +const MORPHO_RATE_FIELDS_CACHE_TTL_MS = 15 * 60 * 1000; const LOOKBACK_WINDOWS = [ { @@ -36,8 +37,20 @@ const LOOKBACK_WINDOWS = [ ] as const; export type RateEnrichmentMarketInput = Pick & { - loanAsset: Pick; - collateralAsset: Pick; + loanAsset: Pick; + collateralAsset: Pick; + state?: Partial< + Pick< + Market['state'], + | 'supplyAssets' + | 'borrowAssets' + | 'liquidityAssets' + | 'collateralAssets' + | 'supplyAssetsUsd' + | 'borrowAssetsUsd' + | 'liquidityAssetsUsd' + > + >; morphoBlue: { chain: { id: number; @@ -238,22 +251,35 @@ const computeWindowRates = (startState: BoundaryState, endState: BoundaryState): }; }; -const buildApiEnrichmentFromMarket = (market: Market): MarketRateEnrichment => ({ - apyAtTarget: market.state.apyAtTarget, - rateAtTarget: market.state.rateAtTarget, - dailySupplyApy: market.state.dailySupplyApy ?? null, - dailyBorrowApy: market.state.dailyBorrowApy ?? null, - weeklySupplyApy: market.state.weeklySupplyApy ?? null, - weeklyBorrowApy: market.state.weeklyBorrowApy ?? null, - monthlySupplyApy: market.state.monthlySupplyApy ?? null, - monthlyBorrowApy: market.state.monthlyBorrowApy ?? null, -}); - type MorphoRateFetchResult = { enrichments: Map; failed: boolean; }; +type CachedMorphoRateFields = Awaited>; +const morphoRateFieldsCache = new Map }>(); + +const fetchCachedMorphoMarketRateEnrichments = (chainId: SupportedNetworks): Promise => { + const now = Date.now(); + const cached = morphoRateFieldsCache.get(chainId); + + if (cached && cached.expiresAt > now) { + return cached.promise; + } + + const promise = fetchMorphoMarketRateEnrichments(chainId).catch((error) => { + morphoRateFieldsCache.delete(chainId); + throw error; + }); + + morphoRateFieldsCache.set(chainId, { + expiresAt: now + MORPHO_RATE_FIELDS_CACHE_TTL_MS, + promise, + }); + + return promise; +}; + const fetchMorphoRateMarketsByKey = async ( chainMarkets: RateEnrichmentMarketInput[], chainId: SupportedNetworks, @@ -266,21 +292,7 @@ const fetchMorphoRateMarketsByKey = async ( } try { - if (chainMarkets.length === 1) { - const morphoMarket = await fetchMorphoMarket(chainMarkets[0].uniqueKey, chainId); - if (!morphoMarket) { - return { - enrichments: new Map(), - failed: false, - }; - } - - return { - enrichments: new Map([[getMarketRateEnrichmentKey(morphoMarket.uniqueKey, chainId), buildApiEnrichmentFromMarket(morphoMarket)]]), - failed: false, - }; - } - const morphoEnrichments = await fetchMorphoMarketRateEnrichments(chainId); + const morphoEnrichments = await fetchCachedMorphoMarketRateEnrichments(chainId); return { enrichments: chainMarkets.reduce((acc, market) => { const enrichment = morphoEnrichments.get(getMorphoMarketRateFieldsKey(chainId, market.uniqueKey)); @@ -636,13 +648,27 @@ export async function fetchMarketRateEnrichment( }); if (morphoRateFetchFailed && MARKET_RATE_RPC_FALLBACK_ENABLED) { + const rpcFallbackMarkets: RateEnrichmentMarketInput[] = []; + + chainMarkets.forEach((market) => { + if (!shouldUseRateRpcFallbackForMarket(market)) { + return; + } + + rpcFallbackMarkets.push(market); + }); + + if (rpcFallbackMarkets.length === 0) { + continue; + } + const windowRates = await fetchRealizedMarketWindowRates( - chainMarkets, + rpcFallbackMarkets, LOOKBACK_WINDOWS.map((window) => window.seconds), customRpcUrls, ); - chainMarkets.forEach((market) => { + rpcFallbackMarkets.forEach((market) => { const key = getMarketRateEnrichmentKey(market.uniqueKey, chainId); const ratesByWindow = windowRates.get(key); diff --git a/src/utils/market-rpc-gating.ts b/src/utils/market-rpc-gating.ts new file mode 100644 index 00000000..edab9cbd --- /dev/null +++ b/src/utils/market-rpc-gating.ts @@ -0,0 +1,141 @@ +import { formatUnits } from 'viem'; +import { DEFAULT_MIN_LIQUIDITY_USD } from '@/constants/markets'; +import { findToken, TokenPeg } from '@/utils/tokens'; +import type { Market } from '@/utils/types'; + +type MarketRpcExposureInput = { + loanAsset?: Pick; + collateralAsset?: Pick; + morphoBlue?: { + chain?: { + id?: number; + }; + }; + state?: Partial< + Pick< + Market['state'], + | 'supplyAssets' + | 'borrowAssets' + | 'liquidityAssets' + | 'collateralAssets' + | 'supplyAssetsUsd' + | 'borrowAssetsUsd' + | 'liquidityAssetsUsd' + > + >; +}; + +const toFiniteUsd = (value: number | null | undefined): number => { + if (value == null || !Number.isFinite(value)) { + return 0; + } + + return value; +}; + +export const getMarketRpcExposureUsd = (market: MarketRpcExposureInput): number => + Math.max( + toFiniteUsd(market.state?.supplyAssetsUsd), + toFiniteUsd(market.state?.borrowAssetsUsd), + toFiniteUsd(market.state?.liquidityAssetsUsd), + ); + +// RPC fallback is only for material markets where missing Morpho rate fields are +// worth an archive-node read. USD pegs use value; ETH/BTC/HYPE pegs use units so +// cross-asset markets such as BTC<>ETH must clear both relevant sides. +export const RATE_RPC_FALLBACK_MIN_USD_PEG_EXPOSURE_USD = DEFAULT_MIN_LIQUIDITY_USD; +export const RATE_RPC_FALLBACK_MIN_ETH = 5; +export const RATE_RPC_FALLBACK_MIN_BTC = 1; +export const RATE_RPC_FALLBACK_MIN_HYPE = 300; + +const parseAssetUnits = (value: string | undefined, decimals: number | undefined): number => { + if (!value || decimals == null) { + return 0; + } + + try { + const units = Number(formatUnits(BigInt(value), decimals)); + return Number.isFinite(units) && units > 0 ? units : 0; + } catch { + return 0; + } +}; + +const getRecognizedPeg = (asset: Pick | undefined, chainId: number | undefined): TokenPeg | null => { + if (!asset?.address || chainId == null) { + return null; + } + + return findToken(asset.address, chainId)?.peg ?? null; +}; + +const getLoanAssetExposureUnits = (market: MarketRpcExposureInput): number => + Math.max( + parseAssetUnits(market.state?.supplyAssets, market.loanAsset?.decimals), + parseAssetUnits(market.state?.borrowAssets, market.loanAsset?.decimals), + parseAssetUnits(market.state?.liquidityAssets, market.loanAsset?.decimals), + ); + +const getCollateralAssetExposureUnits = (market: MarketRpcExposureInput): number => + parseAssetUnits(market.state?.collateralAssets, market.collateralAsset?.decimals); + +const getPegThreshold = (peg: TokenPeg): number | null => { + switch (peg) { + case TokenPeg.USD: + return RATE_RPC_FALLBACK_MIN_USD_PEG_EXPOSURE_USD; + case TokenPeg.ETH: + return RATE_RPC_FALLBACK_MIN_ETH; + case TokenPeg.BTC: + return RATE_RPC_FALLBACK_MIN_BTC; + case TokenPeg.HYPE: + return RATE_RPC_FALLBACK_MIN_HYPE; + default: + return null; + } +}; + +const getPegExposureUnits = (market: MarketRpcExposureInput, peg: TokenPeg): number => { + const chainId = market.morphoBlue?.chain?.id; + const loanPeg = getRecognizedPeg(market.loanAsset, chainId); + const collateralPeg = getRecognizedPeg(market.collateralAsset, chainId); + const loanExposure = loanPeg === peg ? getLoanAssetExposureUnits(market) : 0; + const collateralExposure = collateralPeg === peg ? getCollateralAssetExposureUnits(market) : 0; + + return Math.max(loanExposure, collateralExposure); +}; + +const hasPegExposure = (market: MarketRpcExposureInput, peg: TokenPeg): boolean => { + const threshold = getPegThreshold(peg); + if (threshold == null) { + return false; + } + + if (threshold <= 0) { + return true; + } + + if (peg === TokenPeg.USD) { + return Math.max(getMarketRpcExposureUsd(market), getPegExposureUnits(market, peg)) >= threshold; + } + + return getPegExposureUnits(market, peg) >= threshold; +}; + +export const shouldUseRateRpcFallbackForMarket = (market: MarketRpcExposureInput): boolean => { + const chainId = market.morphoBlue?.chain?.id; + const loanPeg = getRecognizedPeg(market.loanAsset, chainId); + const collateralPeg = getRecognizedPeg(market.collateralAsset, chainId); + + if (!loanPeg) { + return false; + } + + if ( + ((loanPeg === TokenPeg.BTC && collateralPeg === TokenPeg.ETH) || (loanPeg === TokenPeg.ETH && collateralPeg === TokenPeg.BTC)) && + !(hasPegExposure(market, TokenPeg.BTC) && hasPegExposure(market, TokenPeg.ETH)) + ) { + return false; + } + + return hasPegExposure(market, loanPeg); +}; diff --git a/src/utils/marketFilters.ts b/src/utils/marketFilters.ts index df02b551..e3510a41 100644 --- a/src/utils/marketFilters.ts +++ b/src/utils/marketFilters.ts @@ -5,13 +5,13 @@ * different market views (main markets page, same-loan-asset tables, etc.) */ -import { LOCKED_MARKET_APY_THRESHOLD } from '@/constants/markets'; +import { LOCKED_MARKET_APY_THRESHOLD, LOCKED_MARKET_APY_THRESHOLDS } from '@/constants/markets'; import type { OracleMetadataRecord } from '@/hooks/useOracleMetadata'; import { marketFilterSelectionIncludesAsset } from '@/features/markets/market-filter-selection'; import { parseNumericThreshold } from '@/utils/markets'; import type { SupportedNetworks } from '@/utils/networks'; import { type PriceFeedVendors, getOracleType, getOracleVendorInfo, OracleType } from '@/utils/oracle'; -import type { ERC20Token } from '@/utils/tokens'; +import { findToken as findSupportedToken, TokenPeg, type ERC20Token } from '@/utils/tokens'; import type { Market } from '@/utils/types'; // ============================================================================ @@ -115,8 +115,19 @@ export const createUnknownOracleFilter = (showUnknownOracle: boolean, oracleMeta }; }; +const getLockedMarketApyThreshold = (market: Market): number => { + const chainId = market.morphoBlue.chain.id; + const loanAsset = findSupportedToken(market.loanAsset.address, chainId); + const isEthPegLoanMarket = loanAsset?.peg === TokenPeg.ETH || market.loanAsset.symbol.toUpperCase() === 'WETH'; + + return isEthPegLoanMarket ? LOCKED_MARKET_APY_THRESHOLDS.ethPegLoanAsset : LOCKED_MARKET_APY_THRESHOLD; +}; + /** - * Filter out locked/frozen markets with extreme APY (> 1500%) + * Filter out locked/frozen markets with extreme supply APY. + * + * The generic threshold is 1500%, but ETH-pegged loan markets use 100% + * because locked WETH markets have appeared around 200%. */ export const createLockedMarketFilter = (showLocked: boolean): MarketFilter => { if (showLocked) { @@ -124,7 +135,7 @@ export const createLockedMarketFilter = (showLocked: boolean): MarketFilter => { } return (market) => { const supplyApy = market.state?.supplyApy ?? 0; - return supplyApy <= LOCKED_MARKET_APY_THRESHOLD; + return supplyApy <= getLockedMarketApyThreshold(market); }; }; diff --git a/src/utils/tokenMetadata.ts b/src/utils/tokenMetadata.ts index a45c7679..ad9f98b0 100644 --- a/src/utils/tokenMetadata.ts +++ b/src/utils/tokenMetadata.ts @@ -2,7 +2,7 @@ import { erc20Abi, parseAbi, type Address, type Hex } from 'viem'; import type { CustomRpcUrls } from '@/stores/useCustomRpc'; import type { SupportedNetworks } from './networks'; import { getClient } from './rpc'; -import { findToken, infoToKey } from './tokens'; +import { findToken, infoToKey, type ERC20Token } from './tokens'; import type { TokenInfo } from './types'; import { DATA_API_BASE_URL } from './urls'; @@ -23,6 +23,11 @@ export type OnchainTokenMetadata = { export type SerializedResolvedTokenInfos = Record; +type ResolveTokenInfosOptions = { + resolveUnknownTokens?: boolean; + trustedTokens?: ERC20Token[]; +}; + const TOKEN_METADATA_BATCH_SIZE = 200; const erc20SymbolBytes32Abi = parseAbi(['function symbol() view returns (bytes32)']); @@ -92,6 +97,17 @@ const groupTokenInputsByChain = (tokens: TokenAddressInput[]): Map { + const localToken = findToken(address, chainId); + if (localToken) { + return localToken; + } + + return trustedTokens.find((token) => + token.networks.some((network) => network.address.toLowerCase() === address.toLowerCase() && network.chain.id === chainId), + ); +}; + const getOrCreateTokenMetadata = (metadataByToken: Map, key: string): OnchainTokenMetadata => { const existing = metadataByToken.get(key); if (existing) { @@ -318,18 +334,21 @@ const fetchResolvedUnknownTokenInfosFromServer = async (tokens: TokenAddressInpu export const resolveTokenInfos = async ( tokens: TokenAddressInput[], _customRpcUrls: CustomRpcUrls = {}, + options: ResolveTokenInfosOptions = {}, ): Promise> => { const uniqueTokens = dedupeTokenInputs(tokens); + const shouldResolveUnknownTokens = options.resolveUnknownTokens ?? true; + const trustedTokens = options.trustedTokens ?? []; const resolvedTokenInfos = new Map(); - const unresolvedTokens = uniqueTokens.filter((token) => !findToken(token.address, token.chainId)); + const unresolvedTokens = uniqueTokens.filter((token) => !findResolvedToken(token.address, token.chainId, trustedTokens)); const unresolvedTokenInfos = - unresolvedTokens.length === 0 + !shouldResolveUnknownTokens || unresolvedTokens.length === 0 ? new Map() : await fetchResolvedUnknownTokenInfosFromServer(unresolvedTokens).catch(() => new Map()); for (const token of uniqueTokens) { const key = infoToKey(token.address, token.chainId); - const knownToken = findToken(token.address, token.chainId); + const knownToken = findResolvedToken(token.address, token.chainId, trustedTokens); if (knownToken) { resolvedTokenInfos.set(key, { From f1d2338382ac7143b018d27be71c51d97d00e63a Mon Sep 17 00:00:00 2001 From: antoncoding Date: Fri, 8 May 2026 15:08:36 +0800 Subject: [PATCH 2/2] chore: review fixes --- src/components/DataPrefetcher.tsx | 4 +--- src/data-sources/monarch-api/markets.ts | 5 ++--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/components/DataPrefetcher.tsx b/src/components/DataPrefetcher.tsx index 37bdd0b8..de206984 100644 --- a/src/components/DataPrefetcher.tsx +++ b/src/components/DataPrefetcher.tsx @@ -1,14 +1,12 @@ 'use client'; import { usePathname } from 'next/navigation'; -import { useMarketsQuery } from '@/hooks/queries/useMarketsQuery'; import { useMorphoWhitelistStatusQuery } from '@/hooks/queries/useMorphoWhitelistStatusQuery'; import { useTokensQuery } from '@/hooks/queries/useTokensQuery'; import { useMerklCampaignsQuery } from '@/hooks/queries/useMerklCampaignsQuery'; function DataPrefetcherContent() { useMorphoWhitelistStatusQuery(); - useMarketsQuery(); useTokensQuery(); useMerklCampaignsQuery(); @@ -16,7 +14,7 @@ function DataPrefetcherContent() { } /** - * Triggeres data prefetching for markets, tokens, and Merkl campaigns. + * Triggeres data prefetching for tokens, whitelist metadata, and Merkl campaigns. * These hooks use React Query under the hood, which will cache the data for future use. * @returns */ diff --git a/src/data-sources/monarch-api/markets.ts b/src/data-sources/monarch-api/markets.ts index 43ad3a93..ecaf4a51 100644 --- a/src/data-sources/monarch-api/markets.ts +++ b/src/data-sources/monarch-api/markets.ts @@ -228,13 +228,12 @@ const mapMonarchMarketRows = async ( return []; } + const shouldResolveUnknownTokens = options.resolveUnknownTokens ?? true; const tokenInfos = await resolveTokenInfos(getMarketTokenInputs(rows), customRpcUrls, { - resolveUnknownTokens: options.resolveUnknownTokens ?? true, + resolveUnknownTokens: shouldResolveUnknownTokens, trustedTokens: options.trustedTokens, }); - const shouldResolveUnknownTokens = options.resolveUnknownTokens ?? true; - return rows .map((market) => mapMonarchMarketToMarket(market, tokenInfos, {