Skip to content

perf: optimize rpc usage#515

Merged
antoncoding merged 2 commits into
masterfrom
perf/rpc
May 8, 2026
Merged

perf: optimize rpc usage#515
antoncoding merged 2 commits into
masterfrom
perf/rpc

Conversation

@antoncoding
Copy link
Copy Markdown
Owner

@antoncoding antoncoding commented May 8, 2026

Summary by CodeRabbit

  • New Features

    • Configurable APY thresholds for different asset types in locked markets.
    • Support for resolving unknown tokens with trusted token options.
    • Market liquidity exposure-based logic for rate calculations.
  • Improvements

    • Rate enrichment now pagination-aware for improved performance.
    • RPC fallback calculations now gated by market exposure thresholds.
    • RPC fallback disabled by default.

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 8, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
monarch Ready Ready Preview, Comment May 8, 2026 9:00am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

Review Change Stack

Warning

Rate limit exceeded

@antoncoding has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 8 minutes and 31 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: f8f04dbd-03e9-4854-818a-e97aa3487dcf

📥 Commits

Reviewing files that changed from the base of the PR and between f591751 and f1d2338.

📒 Files selected for processing (2)
  • src/components/DataPrefetcher.tsx
  • src/data-sources/monarch-api/markets.ts
📝 Walkthrough

Walkthrough

Market rate enrichment pipeline refactored for configurability and efficiency: token resolution made optional with trust lists, rate enrichment now per-chain-cached and conditionally applied per component, RPC fallback gating driven by market exposure thresholds, hooks redesigned to enable/disable enrichment on-demand.

Changes

Market Rate Enrichment Pipeline Refactor

Layer / File(s) Summary
Configuration & Constants
.env.local.example, src/constants/markets.ts
RPC fallback env var default flipped to false; locked-market APY threshold restructured into a typed object with ETH-pegged-asset-specific value.
Token Resolution Options
src/utils/tokenMetadata.ts, src/data-sources/monarch-api/markets.ts
resolveTokenInfos accepts ResolveTokenInfosOptions to control unknown-token resolution and trust lists; Monarch market mapping threads these options through market-row processing.
Markets Query Hook Options
src/hooks/queries/useMarketsQuery.ts
useMarketsQuery now accepts includeUnknownTokens option and gates execution on token loading; passes resolved tokens to Monarch fetch for conditional unknown-token handling.
RPC Fallback Exposure Gating
src/utils/market-rpc-gating.ts
New module defines exposure thresholds per peg type (USD/ETH/BTC/HYPE); shouldUseRateRpcFallbackForMarket decides fallback use based on market liquidity and peg type matching.
Rate Enrichment Caching & Fetching
src/utils/market-rate-enrichment.ts
Morpho bulk enrichments cached per-chain with TTL; RPC fallback applied only to markets passing exposure gating; env parsing requires explicit "true" string.
Processed Markets Hook Defaults
src/hooks/useProcessedMarkets.ts
enableRateEnrichment now defaults to false and forwards includeUnknownTokens to useMarketsQuery.
Filtered Markets Hook Refactoring
src/hooks/useFilteredMarkets.ts
Hook accepts currentPage and enableRateEnrichment options; splits filtering/sorting/enrichment into separate memos; returns rateEnrichmentPendingChainIds for loading UI.
Markets Table Wiring
src/features/markets/components/table/markets-table.tsx
Passes includeUnknownTokens and currentPage to hooks; forwards rateEnrichmentPendingChainIds to MarketTableBody.
Market Table Body Props
src/features/markets/components/table/market-table-body.tsx
Accepts rateEnrichmentPendingChainIds prop to drive loading indicators for historical rate fields.
Markets View Refactoring
src/features/markets/markets-view.tsx
Refactored to call useMarketsQuery with includeUnknownTokens and useFilteredMarkets with enableRateEnrichment: false.
Borrowed Position Enrichment
src/features/positions/components/borrowed-morpho-blue-row-detail.tsx
Uses useMarketRateEnrichmentQuery for per-market enrichment instead of relying on hook-level enrichment; disables enrichment in useProcessedMarkets.
Locked Market APY Filter
src/utils/marketFilters.ts
Filter now uses dynamic ETH-specific threshold for ETH-pegged loan assets via getLockedMarketApyThreshold helper.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • antoncoding/monarch#491: Both PRs modify RPC rate fallback behavior and the NEXT_PUBLIC_ENABLE_MARKET_RATE_RPC_FALLBACK flag.
  • antoncoding/monarch#485: Both PRs refactor rate enrichment to support per-chain pending-chain-id propagation throughout hooks and components.
  • antoncoding/monarch#471: Both PRs touch Monarch market fetching, token resolution, and the rate-enrichment pipeline as core changes.

Suggested labels

feature request

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly describes the main optimization focus: reducing RPC usage through configurable enrichment, gating mechanisms, and token resolution improvements across the codebase.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/rpc

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot added the feature request Specific feature ready to be implemented label May 8, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/hooks/useProcessedMarkets.ts (1)

45-70: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Update JSDoc and verify callers need rate enrichment enabled.

enableRateEnrichment now defaults to false, but the JSDoc describes rate enrichment as always-on (step 4 and the "Identity-only consumers can disable…" paragraph suggest it's enabled by default). This mismatch is confusing. More importantly, no callers currently pass enableRateEnrichment: true, meaning all 23 call sites silently lost rate enrichment without explicit opt-in.

Clarify the JSDoc to reflect that rate enrichment is opt-in, then audit callers like useVaultAllocations, useUserPositions, and useMonarchTransactions to confirm whether they actually need rolling rates and should pass enableRateEnrichment: true.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/useProcessedMarkets.ts` around lines 45 - 70, The JSDoc for
useProcessedMarkets incorrectly implies rate enrichment is enabled by default
while the implementation sets enableRateEnrichment = false; update the JSDoc (in
useProcessedMarkets) to state that rolling 24h/7d/30d rate enrichment is opt-in
and disabled by default, then audit all callers (e.g., useVaultAllocations,
useUserPositions, useMonarchTransactions and other ~23 call sites) to determine
which actually need rates and explicitly pass { enableRateEnrichment: true }
where required; ensure you run/adjust any unit/integration tests or type checks
to confirm callers that require enrichment opt in and document the decision in
comments or changelog.
🧹 Nitpick comments (3)
src/utils/tokenMetadata.ts (2)

100-109: 💤 Low value

Tiny perf nit: normalize the address once.

address.toLowerCase() runs on every network for every trusted token. Hoist it out of the inner check.

♻️ suggested change
-const findResolvedToken = (address: string, chainId: SupportedNetworks, trustedTokens: ERC20Token[] = []): ERC20Token | undefined => {
-  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 findResolvedToken = (address: string, chainId: SupportedNetworks, trustedTokens: ERC20Token[] = []): ERC20Token | undefined => {
+  const localToken = findToken(address, chainId);
+  if (localToken) {
+    return localToken;
+  }
+
+  const normalizedAddress = address.toLowerCase();
+  return trustedTokens.find((token) =>
+    token.networks.some((network) => network.chain.id === chainId && network.address.toLowerCase() === normalizedAddress),
+  );
+};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils/tokenMetadata.ts` around lines 100 - 109, In findResolvedToken,
avoid calling address.toLowerCase() repeatedly by normalizing it once at the
start (e.g., const target = address.toLowerCase()) and use target in the
trustedTokens.find predicate when comparing network.address; update the inner
comparison from network.address.toLowerCase() === address.toLowerCase() to
network.address.toLowerCase() === target (and similarly for any other address
comparisons) so the lowercase is computed a single time per function call.

339-351: 💤 Low value

Avoid resolving each token twice.

findResolvedToken is called once in the filter (343) and again in the loop (351) for every token. For larger market sets with sizeable trustedTokens, that doubles the trusted-token scan. Resolve once and reuse.

♻️ suggested change
   const uniqueTokens = dedupeTokenInputs(tokens);
   const shouldResolveUnknownTokens = options.resolveUnknownTokens ?? true;
   const trustedTokens = options.trustedTokens ?? [];
   const resolvedTokenInfos = new Map<string, ResolvedTokenInfo>();
-  const unresolvedTokens = uniqueTokens.filter((token) => !findResolvedToken(token.address, token.chainId, trustedTokens));
+  const knownByKey = new Map<string, ERC20Token>();
+  const unresolvedTokens: TokenAddressInput[] = [];
+  for (const token of uniqueTokens) {
+    const known = findResolvedToken(token.address, token.chainId, trustedTokens);
+    if (known) {
+      knownByKey.set(infoToKey(token.address, token.chainId), known);
+    } else {
+      unresolvedTokens.push(token);
+    }
+  }
   const unresolvedTokenInfos =
     !shouldResolveUnknownTokens || unresolvedTokens.length === 0
       ? new Map<string, ResolvedTokenInfo>()
       : await fetchResolvedUnknownTokenInfosFromServer(unresolvedTokens).catch(() => new Map<string, ResolvedTokenInfo>());

   for (const token of uniqueTokens) {
     const key = infoToKey(token.address, token.chainId);
-    const knownToken = findResolvedToken(token.address, token.chainId, trustedTokens);
+    const knownToken = knownByKey.get(key);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils/tokenMetadata.ts` around lines 339 - 351, The code calls
findResolvedToken twice per token (once when building unresolvedTokens and again
in the for-loop), causing duplicated scans; to fix, compute and cache the
resolved/trusted lookup once for each deduped token (e.g., build a Map keyed by
infoToKey from uniqueTokens to the result of findResolvedToken using
trustedTokens), use that map to derive unresolvedTokens and to get knownToken
inside the for-loop, and keep using
fetchResolvedUnknownTokenInfosFromServer/unresolvedTokenInfos and
resolvedTokenInfos as before so you only resolve each token a single time.
src/data-sources/monarch-api/markets.ts (1)

231-244: 💤 Low value

Compute the flag once.

options.resolveUnknownTokens ?? true is evaluated twice; share a single local so the warn behavior can never drift from the resolution behavior.

♻️ suggested change
-  const tokenInfos = await resolveTokenInfos(getMarketTokenInputs(rows), customRpcUrls, {
-    resolveUnknownTokens: options.resolveUnknownTokens ?? true,
-    trustedTokens: options.trustedTokens,
-  });
-
-  const shouldResolveUnknownTokens = options.resolveUnknownTokens ?? true;
+  const shouldResolveUnknownTokens = options.resolveUnknownTokens ?? true;
+  const tokenInfos = await resolveTokenInfos(getMarketTokenInputs(rows), customRpcUrls, {
+    resolveUnknownTokens: shouldResolveUnknownTokens,
+    trustedTokens: options.trustedTokens,
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/data-sources/monarch-api/markets.ts` around lines 231 - 244, Compute the
resolveUnknownTokens flag once and reuse it so resolution and warning behavior
stay consistent: create a local like shouldResolveUnknownTokens =
options.resolveUnknownTokens ?? true, pass that variable into resolveTokenInfos
(instead of options.resolveUnknownTokens ?? true) and into the
mapMonarchMarketToMarket call (warnOnMissingTokenInfo), ensuring tokenInfos
(from resolveTokenInfos), getMarketTokenInputs, mapMonarchMarketToMarket, and
the Market filtering logic all use the same shouldResolveUnknownTokens value.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/hooks/useProcessedMarkets.ts`:
- Around line 45-70: The JSDoc for useProcessedMarkets incorrectly implies rate
enrichment is enabled by default while the implementation sets
enableRateEnrichment = false; update the JSDoc (in useProcessedMarkets) to state
that rolling 24h/7d/30d rate enrichment is opt-in and disabled by default, then
audit all callers (e.g., useVaultAllocations, useUserPositions,
useMonarchTransactions and other ~23 call sites) to determine which actually
need rates and explicitly pass { enableRateEnrichment: true } where required;
ensure you run/adjust any unit/integration tests or type checks to confirm
callers that require enrichment opt in and document the decision in comments or
changelog.

---

Nitpick comments:
In `@src/data-sources/monarch-api/markets.ts`:
- Around line 231-244: Compute the resolveUnknownTokens flag once and reuse it
so resolution and warning behavior stay consistent: create a local like
shouldResolveUnknownTokens = options.resolveUnknownTokens ?? true, pass that
variable into resolveTokenInfos (instead of options.resolveUnknownTokens ??
true) and into the mapMonarchMarketToMarket call (warnOnMissingTokenInfo),
ensuring tokenInfos (from resolveTokenInfos), getMarketTokenInputs,
mapMonarchMarketToMarket, and the Market filtering logic all use the same
shouldResolveUnknownTokens value.

In `@src/utils/tokenMetadata.ts`:
- Around line 100-109: In findResolvedToken, avoid calling address.toLowerCase()
repeatedly by normalizing it once at the start (e.g., const target =
address.toLowerCase()) and use target in the trustedTokens.find predicate when
comparing network.address; update the inner comparison from
network.address.toLowerCase() === address.toLowerCase() to
network.address.toLowerCase() === target (and similarly for any other address
comparisons) so the lowercase is computed a single time per function call.
- Around line 339-351: The code calls findResolvedToken twice per token (once
when building unresolvedTokens and again in the for-loop), causing duplicated
scans; to fix, compute and cache the resolved/trusted lookup once for each
deduped token (e.g., build a Map keyed by infoToKey from uniqueTokens to the
result of findResolvedToken using trustedTokens), use that map to derive
unresolvedTokens and to get knownToken inside the for-loop, and keep using
fetchResolvedUnknownTokenInfosFromServer/unresolvedTokenInfos and
resolvedTokenInfos as before so you only resolve each token a single time.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 18847ea2-6ea4-44a1-bf24-f0cf3df82159

📥 Commits

Reviewing files that changed from the base of the PR and between 874ebdb and f591751.

📒 Files selected for processing (14)
  • .env.local.example
  • src/constants/markets.ts
  • src/data-sources/monarch-api/markets.ts
  • src/features/markets/components/table/market-table-body.tsx
  • src/features/markets/components/table/markets-table.tsx
  • src/features/markets/markets-view.tsx
  • src/features/positions/components/borrowed-morpho-blue-row-detail.tsx
  • src/hooks/queries/useMarketsQuery.ts
  • src/hooks/useFilteredMarkets.ts
  • src/hooks/useProcessedMarkets.ts
  • src/utils/market-rate-enrichment.ts
  • src/utils/market-rpc-gating.ts
  • src/utils/marketFilters.ts
  • src/utils/tokenMetadata.ts

Copy link
Copy Markdown
Collaborator

@starksama starksama left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking this, narrowed to the request-path issues only.

I rechecked the Vercel preview with includeUnknownTokens=true forced in monarch_store_marketPreferences.

Observed on preview /markets:

  • includeUnknownTokens=true
  • 105 fetch/xhr total
  • 28 Morpho GraphQL requests
  • 18 Monarch GraphQL requests
  • 19 data.monarchlend.xyz/v1/tokens/metadata requests
  • 4 Pendle asset requests
  • I still did not see direct browser RPC endpoint calls on initial load; the expensive path I can reproduce is GraphQL/token metadata fanout, not wallet/archive RPC from the browser.

Required changes:

  1. useMarketsQuery must not block the normal markets fetch on useTokensQuery when includeUnknownTokens=false. Right now line 45 always starts the Pendle token query and line 163 gates markets on !tokensLoading, even though line 84 only needs trusted external tokens when resolving unknown tokens. Use the local token list/default immediately for the default path; only wait for external tokens when unknown-token resolution is explicitly enabled.

  2. The markets query key is wrong for trustedTokens. Line 50 only keys by allTokens.length, while the query function uses the actual token contents at line 85. If token addresses/symbols change with the same length, React Query can keep stale market resolution. Either remove trustedTokens from the default query path, or key by a stable token identity/fingerprint.

  3. includeUnknownTokens=true still creates the expensive unknown-token metadata path and likely double markets fetch path. DataPrefetcher calls useMarketsQuery() with the default includeUnknownTokens=false, while the markets page calls useMarketsQuery({ includeUnknownTokens }). Since the boolean is in the query key, users with unknown tokens enabled do not share the prefetched markets query; in my preview run this path produced 18 Monarch GraphQL requests plus 19 token-metadata calls. Make prefetcher use the same preference, remove global markets prefetch, or otherwise ensure one canonical markets query per page state.

Validation:

  • pnpm check passed.
  • Vercel checks passed.
  • Preview network was measured directly in browser performance entries after forcing includeUnknownTokens=true.

Copy link
Copy Markdown
Collaborator

@starksama starksama left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed after f1d2338.

My previous blocking request is addressed enough to unblock:

  • DataPrefetcher no longer prefetches markets, so it no longer creates a second canonical useMarketsQuery() with includeUnknownTokens=false while the markets page uses the user preference.
  • The trustedTokens / Pendle dependency is an acceptable short-term tradeoff in the current architecture because it preserves external trusted-asset classification. Moving trusted token metadata/classification into Monarch market rows is the cleaner future fix.
  • I am not blocking on a token fingerprint key because that adds churn/complexity for a path that should be removed once the backend carries classification.

Validation:

  • pnpm check passed.
  • pnpm build passed locally.
  • Local production server with includeUnknownTokens=true showed no token-metadata fanout from duplicate market prefetch; the remaining request shape is from the single markets page query path / fallback path.

Approving.

@antoncoding antoncoding merged commit 090f84a into master May 8, 2026
4 checks passed
@antoncoding antoncoding deleted the perf/rpc branch May 8, 2026 09:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature request Specific feature ready to be implemented

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants