Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 12 additions & 29 deletions src/data-sources/morpho-api/market.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Market[]> => {
const allMarkets: Market[] = [];
let skip = 0;
Expand All @@ -68,67 +69,49 @@ export const fetchMorphoMarkets = async (network: SupportedNetworks): Promise<Ma
try {
do {
queryCount++;
console.log(`Fetching markets query ${queryCount}, skip: ${skip}, pageSize: ${pageSize}`);

// Construct the variables object including the where clause and pagination
const variables = {
first: pageSize,
skip,
where: {
chainId_in: [network],

// Remove whitelisted filter to fetch all markets
// Add other potential filters to 'where' if needed in the future
// Filter for whitelisted markets only - this avoids corrupted/orphaned market
// records in the Morpho API that cause "cannot find market config" errors.
// Non-whitelisted markets for user positions are fetched individually on demand.
whitelisted: true,
},
};

const response = await morphoGraphqlFetcher<MarketsGraphQLResponse>(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;
}
};
27 changes: 26 additions & 1 deletion src/hooks/useUserPositions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, Market>();
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<string, PositionSnapshot>();
await Promise.all(
Expand Down