Development of APY/APR protocol comparison system
User compares yields manually: Aave shows 4.2% APY on USDC, Compound — 5.1%, Morpho — 5.8%. But these numbers can't be compared directly: Aave accounts for compound interest (APY), Compound historically showed APR (without reinvestment accounting), Morpho aggregates with additional MORPHO rewards. Without data normalization any comparison is misleading.
Main problem: metric normalization
APR vs APY and different compounding periods
APR (Annual Percentage Rate) — simple rate without reinvestment. APY (Annual Percentage Yield) — with compound effect. Difference on frequent compounding is significant:
APY = (1 + APR/n)^n - 1
With APR = 10% and compounding every block (≈ 2190 times per year on Ethereum) APY ≈ 10.52%. With daily compounding — 10.516%. Difference small, but when comparing protocols with different compounding periods you need uniform base.
For correct comparison system normalizes everything to Daily APR: divide annualized rate by 365. Show user APY (more understandable metric), calculate internally in Daily APR.
Reward tokens: how to include in calculation
Many protocols on top of base rate pay native tokens (COMP, AAVE, MORPHO). Must include in total APY, but accounting for:
- Current reward token price (volatile — price can drop by claim time)
- Vesting/lock: AAVE Safety Module rewards vest 10 days
- Emission decay: decreasing emissions over time (many protocols reduce emissions every N days)
Approach: show base APY (no rewards) and total APY (with rewards) separately. User decides whether to account for unstable rewards.
Getting data on-chain
Each protocol provides data differently:
Aave V3: getReserveData(asset) returns currentLiquidityRate in ray (1e27). Conversion: APR = rate / 1e27 * SECONDS_PER_YEAR. Rewards through getRewardsData() in RewardsController.
Compound V3 (Comet): getSupplyRate(utilization) returns rate per second. APR = ratePerSecond * SECONDS_PER_YEAR.
Morpho: aggregates Aave and Compound markets with optimization. Base rate = underlying market rate, but with peer-to-peer matching can be higher. supplyAPR() from Morpho Lens contract.
Curve: yield from multiple sources — trading fees (automatically reinvested), CRV emissions (via gauge), boost. CRV APY depends on user's veCRV balance. For comparison systems take "base" APY without boost.
Pendle: YT (Yield Token) implied APY calculated through PT price — not direct contract value, but calculation through market price.
System architecture
Data fetching layer
On-chain calls expensive (in RPC request terms). With 10 protocols × 20 assets × 5 metrics = 1000 calls on each update. Solution: Multicall3 batching — all calls in one transaction.
const calls = protocols.flatMap(protocol =>
assets.map(asset => ({
target: protocol.address,
callData: protocol.interface.encodeFunctionData("getReserveData", [asset])
}))
);
const results = await multicall.aggregate(calls);
For historical data — The Graph subgraphs. Most large protocols (Aave, Compound, Uniswap) have official subgraphs with historical hourly rate data.
Caching and updates
APY in DeFi changes every block but significantly — every few minutes. Aggressive caching:
- L1 cache (Redis): current rates, TTL 60 seconds
- L2 cache (PostgreSQL): hourly snapshots for historical charts
- Real-time updates: WebSocket for change subscriptions (if available)
Background job every 60 seconds fetches on-chain data, updates Redis, once per hour writes snapshot to PostgreSQL.
Historical dynamics
Current APY — momentary snapshot. For decision-making need historical dynamics: how did APY change last 7/30/90 days, how did it react to market events.
From hourly snapshots build:
- Moving average (7d, 30d) for understanding average yield
- Volatility metric (std deviation APY) — how stable is yield
- Min/max for period — floor and ceiling of expected yield
Volatile APY (changes in 5x range monthly) — different risk profile than stable APY (±0.5%). Important info for user.
Ranking and comparison
Just sorting by APY — insufficient. Ranking should account for:
- Risk-adjusted yield: different protocols have different risk profiles (audit history, TVL, age)
- Liquidity: available deposit capacity — important for large sums
- Stable vs volatile APY: rewards in native tokens less reliable
- Network: gas costs on Ethereum vs L2 — matter for small deposits
Simple solution: show filters (stable APY only, audited protocols only, minimum TVL) without creating single "score" — user weighs priorities themselves.
Technical stack
Backend: Node.js + TypeScript. Viem for on-chain calls (lighter ethers.js, tree-shakeable). PostgreSQL for historical data. Redis for caching. Bullmq for background job queue.
Frontend: Next.js, TanStack Query for data fetching with caching and refetch intervals. Recharts or Tremor for historical yield charts.
Subgraphs: The Graph for historical Aave, Compound, Uniswap data. For protocols without official subgraph — custom event indexer through Ponder or custom indexer.
Timeline estimates
MVP with 3-5 protocols, current rates without history — 3-5 days. Full system with historical data, normalization, filters and visualization — 2-3 weeks. Cost calculated individually.







