From 247023db72c58e784e4c38de82e47545ac9858e8 Mon Sep 17 00:00:00 2001 From: starksama <257340800+starksama@users.noreply.github.com> Date: Tue, 17 Feb 2026 12:27:16 +0800 Subject: [PATCH] fix: fetch whitelisted markets only, load missing markets on demand The Morpho API has corrupted market records that cause bulk queries to fail with 'cannot find market config' errors. These are orphaned/junk records that shouldn't affect the user experience. Solution: 1. Markets bulk fetch: Filter for whitelisted=true only - Avoids corrupted records entirely - Whitelisted markets are the ones users care about anyway 2. User positions: Fetch missing market data on demand - If a user has a position in a non-whitelisted market, fetch it individually - Uses fetchMorphoMarket() which works for individual market lookups This is cleaner than page size hacks because: - Bulk queries never hit corrupted records - Individual fetches work reliably - No retry/skip complexity needed Reported issue: User 0x15b406194bfc09a92a3fadc40e7091f61ad07c4c position in market 0x214c2bf3c899c913efda9c4a49adff23f77bbc2dc525af7c05be7ec93f32d561 (wrsETH/WETH on Base) was not showing in portfolio. --- src/data-sources/morpho-api/market.ts | 41 ++++++++------------------- src/hooks/useUserPositions.ts | 27 +++++++++++++++++- 2 files changed, 38 insertions(+), 30 deletions(-) diff --git a/src/data-sources/morpho-api/market.ts b/src/data-sources/morpho-api/market.ts index 16a57a8a..4d8972c3 100644 --- a/src/data-sources/morpho-api/market.ts +++ b/src/data-sources/morpho-api/market.ts @@ -58,6 +58,7 @@ export const fetchMorphoMarket = async (uniqueKey: string, network: SupportedNet }; // Fetcher for multiple markets from Morpho API with pagination +// Uses whitelisted filter to avoid corrupted/junk market records that cause API errors export const fetchMorphoMarkets = async (network: SupportedNetworks): Promise => { const allMarkets: Market[] = []; let skip = 0; @@ -68,67 +69,49 @@ export const fetchMorphoMarkets = async (network: SupportedNetworks): Promise(marketsQuery, variables); - // Handle NOT_FOUND - break pagination loop - if (!response) { - console.warn(`No markets found in Morpho API for network ${network} at skip ${skip}.`); - break; - } - - if (!response.data || !response.data.markets) { - console.warn(`Market data not found in Morpho API response for network ${network} at skip ${skip}.`); + if (!response?.data?.markets?.items || !response.data.markets.pageInfo) { + console.warn(`[Markets] No data in response for network ${network} at skip ${skip}`); break; } const { items, pageInfo } = response.data.markets; - if (!items || !Array.isArray(items) || !pageInfo) { - console.warn(`No market items or page info found in response for network ${network} at skip ${skip}.`); - break; - } - - // Process and add markets to the collection const processedMarkets = items.map(processMarketData); allMarkets.push(...processedMarkets); - // Update pagination info totalCount = pageInfo.countTotal; skip += pageInfo.count; - console.log(`Query ${queryCount}: Fetched ${pageInfo.count} markets, total so far: ${allMarkets.length}/${totalCount}`); + console.log(`[Markets] Query ${queryCount}: Fetched ${pageInfo.count} markets, total: ${allMarkets.length}/${totalCount}`); - // Safety break if pageInfo.count is 0 to prevent infinite loop - if (pageInfo.count === 0 && skip < totalCount) { - console.warn('Received 0 items in a page, but not yet at total count. Breaking loop.'); - break; - } + if (pageInfo.count === 0) break; } while (skip < totalCount); - console.log(`Completed fetching all markets for network ${network}. Total queries: ${queryCount}, Total markets: ${allMarkets.length}`); + console.log(`[Markets] Completed for network ${network}. Queries: ${queryCount}, Markets: ${allMarkets.length}`); - // final filter: remove scam markets + // Final filter: remove scam markets return allMarkets.filter( (market) => !blacklistTokens.includes(market.collateralAsset?.address.toLowerCase() ?? '') && !blacklistTokens.includes(market.loanAsset?.address.toLowerCase() ?? ''), ); } catch (error) { - console.error(`Error fetching markets via Morpho API for network ${network}:`, error); + console.error(`[Markets] Error fetching markets for network ${network}:`, error); throw error; } }; diff --git a/src/hooks/useUserPositions.ts b/src/hooks/useUserPositions.ts index f8f40363..dc53f07f 100644 --- a/src/hooks/useUserPositions.ts +++ b/src/hooks/useUserPositions.ts @@ -170,12 +170,37 @@ const useUserPositions = (user: string | undefined, showEmpty = false, chainIds? marketsByChain.set(marketInfo.chainId, existing); }); - // Build market data map from allMarkets context (no need to fetch individually) + // Build market data map from allMarkets context const marketDataMap = new Map(); allMarkets.forEach((market) => { marketDataMap.set(market.uniqueKey.toLowerCase(), market); }); + // Find markets that need to be fetched individually (not in allMarkets cache) + const missingMarkets: PositionMarket[] = []; + finalMarketKeys.forEach((marketInfo) => { + if (!marketDataMap.has(marketInfo.marketUniqueKey.toLowerCase())) { + missingMarkets.push(marketInfo); + } + }); + + // Fetch missing markets individually (for non-whitelisted markets with positions) + if (missingMarkets.length > 0) { + console.log(`[Positions] Fetching ${missingMarkets.length} missing markets individually`); + const { fetchMorphoMarket } = await import('@/data-sources/morpho-api/market'); + + await Promise.all( + missingMarkets.map(async (marketInfo) => { + try { + const market = await fetchMorphoMarket(marketInfo.marketUniqueKey, marketInfo.chainId as SupportedNetworks); + marketDataMap.set(market.uniqueKey.toLowerCase(), market); + } catch (error) { + console.warn(`[Positions] Failed to fetch market ${marketInfo.marketUniqueKey}:`, error); + } + }), + ); + } + // Fetch snapshots for each chain using batched multicall const allSnapshots = new Map(); await Promise.all(