-
Notifications
You must be signed in to change notification settings - Fork 3
refactor: decouple morpho whitelist refresh #477
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6cf4e14
fa7db82
56d9e59
979ea8d
e019848
1a1b27d
76f613b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,158 @@ | ||
| import { supportsMorphoApi } from '@/config/dataSources'; | ||
| import { marketsWhitelistStatusQuery } from '@/graphql/morpho-api-queries'; | ||
| import { getMarketIdentityKey } from '@/utils/market-identity'; | ||
| import { ALL_SUPPORTED_NETWORKS, type SupportedNetworks } from '@/utils/networks'; | ||
| import { morphoGraphqlFetcher } from './fetchers'; | ||
|
|
||
| type MorphoWhitelistMarket = { | ||
| uniqueKey: string; | ||
| listed: boolean; | ||
| morphoBlue: { | ||
| chain: { | ||
| id: number; | ||
| }; | ||
| }; | ||
| }; | ||
|
|
||
| type MorphoWhitelistStatusResponse = { | ||
| data?: { | ||
| markets?: { | ||
| items?: MorphoWhitelistMarket[]; | ||
| pageInfo?: { | ||
| countTotal: number; | ||
| }; | ||
| }; | ||
| }; | ||
| errors?: { message: string }[]; | ||
| }; | ||
|
|
||
| type MorphoWhitelistStatusPage = { | ||
| items: MorphoWhitelistStatus[]; | ||
| totalCount: number; | ||
| }; | ||
|
|
||
| export type MorphoWhitelistStatus = { | ||
| chainId: number; | ||
| uniqueKey: string; | ||
| listed: boolean; | ||
| }; | ||
|
|
||
| export type MorphoWhitelistStatusRefresh = { | ||
| network: SupportedNetworks; | ||
| statuses: MorphoWhitelistStatus[]; | ||
| }; | ||
|
|
||
| const MORPHO_WHITELIST_PAGE_SIZE = 1_000; | ||
| const MORPHO_WHITELIST_TIMEOUT_MS = 15_000; | ||
| const MORPHO_WHITELIST_PAGE_BATCH_SIZE = 4; | ||
|
|
||
| const MORPHO_SUPPORTED_NETWORKS = ALL_SUPPORTED_NETWORKS.filter((network) => supportsMorphoApi(network)); | ||
|
|
||
| const fetchMorphoWhitelistStatusPage = async ( | ||
| network: SupportedNetworks, | ||
| skip: number, | ||
| pageSize: number, | ||
| ): Promise<MorphoWhitelistStatusPage | null> => { | ||
| const response = await morphoGraphqlFetcher<MorphoWhitelistStatusResponse>( | ||
| marketsWhitelistStatusQuery, | ||
| { | ||
| first: pageSize, | ||
| skip, | ||
| where: { | ||
| chainId_in: [network], | ||
| }, | ||
| }, | ||
| { | ||
| timeoutMs: MORPHO_WHITELIST_TIMEOUT_MS, | ||
| }, | ||
| ); | ||
|
|
||
| if (!response?.data?.markets?.items || !response.data.markets.pageInfo) { | ||
| console.warn(`[WhitelistStatus] Skipping failed page at skip=${skip} for network ${network}`); | ||
| return null; | ||
| } | ||
|
|
||
| return { | ||
| items: response.data.markets.items.map((market) => ({ | ||
| chainId: market.morphoBlue.chain.id, | ||
| uniqueKey: market.uniqueKey, | ||
| listed: market.listed, | ||
| })), | ||
| totalCount: response.data.markets.pageInfo.countTotal, | ||
| }; | ||
| }; | ||
|
|
||
| const fetchMorphoWhitelistStatusesForNetwork = async (network: SupportedNetworks): Promise<MorphoWhitelistStatus[]> => { | ||
| const firstPage = await fetchMorphoWhitelistStatusPage(network, 0, MORPHO_WHITELIST_PAGE_SIZE); | ||
| if (!firstPage) { | ||
| throw new Error(`[WhitelistStatus] Failed to fetch first page for network ${network}.`); | ||
| } | ||
|
|
||
| const allStatuses = [...firstPage.items]; | ||
| const firstPageCount = firstPage.items.length; | ||
| const totalCount = firstPage.totalCount; | ||
|
|
||
| if (firstPageCount === 0 && totalCount > 0) { | ||
| throw new Error(`[WhitelistStatus] Received empty first page for network ${network} while totalCount=${totalCount}.`); | ||
| } | ||
|
|
||
| const remainingOffsets: number[] = []; | ||
| for (let nextSkip = firstPageCount; nextSkip < totalCount; nextSkip += MORPHO_WHITELIST_PAGE_SIZE) { | ||
| remainingOffsets.push(nextSkip); | ||
| } | ||
|
|
||
| for (let index = 0; index < remainingOffsets.length; index += MORPHO_WHITELIST_PAGE_BATCH_SIZE) { | ||
| const offsetBatch = remainingOffsets.slice(index, index + MORPHO_WHITELIST_PAGE_BATCH_SIZE); | ||
| const settledPages = await Promise.allSettled( | ||
| offsetBatch.map((skip) => fetchMorphoWhitelistStatusPage(network, skip, MORPHO_WHITELIST_PAGE_SIZE)), | ||
| ); | ||
|
|
||
| for (const settledPage of settledPages) { | ||
| if (settledPage.status === 'rejected') { | ||
| throw settledPage.reason; | ||
| } | ||
| if (!settledPage.value) { | ||
| throw new Error(`[WhitelistStatus] Failed to fetch one of the paginated whitelist pages for network ${network}.`); | ||
| } | ||
|
|
||
| allStatuses.push(...settledPage.value.items); | ||
| } | ||
| } | ||
|
|
||
| if (allStatuses.length < totalCount) { | ||
| throw new Error( | ||
| `[WhitelistStatus] Incomplete whitelist dataset for network ${network}: fetched ${allStatuses.length} of ${totalCount}.`, | ||
| ); | ||
| } | ||
|
|
||
| return allStatuses; | ||
|
Comment on lines
+122
to
+128
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Validate unique market count before returning success. Line 122 checks the raw row count, but Lines 146-149 later collapse rows by Possible fix- if (allStatuses.length < totalCount) {
+ const statusesByKey = new Map<string, MorphoWhitelistStatus>();
+ for (const status of allStatuses) {
+ statusesByKey.set(getMarketIdentityKey(status.chainId, status.uniqueKey), status);
+ }
+
+ if (statusesByKey.size !== totalCount) {
throw new Error(
- `[WhitelistStatus] Incomplete whitelist dataset for network ${network}: fetched ${allStatuses.length} of ${totalCount}.`,
+ `[WhitelistStatus] Incomplete whitelist dataset for network ${network}: fetched ${statusesByKey.size} of ${totalCount} unique markets.`,
);
}
- return allStatuses;
+ return Array.from(statusesByKey.values());Then Also applies to: 146-149 🤖 Prompt for AI Agents |
||
| }; | ||
|
|
||
| export const fetchAllMorphoWhitelistStatuses = async (): Promise<MorphoWhitelistStatusRefresh[]> => { | ||
| const settledResults = await Promise.allSettled( | ||
| MORPHO_SUPPORTED_NETWORKS.map(async (network) => ({ | ||
| network, | ||
| statuses: await fetchMorphoWhitelistStatusesForNetwork(network), | ||
| })), | ||
| ); | ||
| const successfulRefreshes: MorphoWhitelistStatusRefresh[] = []; | ||
|
|
||
| for (const settledResult of settledResults) { | ||
| if (settledResult.status === 'rejected') { | ||
| console.warn('[WhitelistStatus] Failed to fetch one network; continuing with cached/partial data.', settledResult.reason); | ||
| continue; | ||
| } | ||
|
|
||
| const statusByKey = new Map<string, MorphoWhitelistStatus>(); | ||
| for (const status of settledResult.value.statuses) { | ||
| statusByKey.set(getMarketIdentityKey(status.chainId, status.uniqueKey), status); | ||
| } | ||
|
|
||
| successfulRefreshes.push({ | ||
| network: settledResult.value.network, | ||
| statuses: Array.from(statusByKey.values()), | ||
| }); | ||
| } | ||
|
|
||
| return successfulRefreshes; | ||
|
Comment on lines
+141
to
+157
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fail closed when every network refresh fails. Lines 141-143 swallow each rejection, and Line 157 still returns Possible fix for (const settledResult of settledResults) {
if (settledResult.status === 'rejected') {
console.warn('[WhitelistStatus] Failed to fetch one network; continuing with cached/partial data.', settledResult.reason);
continue;
}
@@
successfulRefreshes.push({
network: settledResult.value.network,
statuses: Array.from(statusByKey.values()),
});
}
+
+ if (successfulRefreshes.length === 0 && MORPHO_SUPPORTED_NETWORKS.length > 0) {
+ throw new Error('[WhitelistStatus] Failed to refresh whitelist statuses for every Morpho network.');
+ }
return successfulRefreshes;🤖 Prompt for AI Agents |
||
| }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Offset calculation uses item count instead of page size.
If the first page returns fewer items than
MORPHO_WHITELIST_PAGE_SIZE(e.g., 450 instead of 500), the loop starts at 450 and increments by 500. This skips items 450-499.Use fixed page size for offset calculation:
Proposed fix
🤖 Prompt for AI Agents