How we make Aave V2, V3 & V4 comparable

Three generations of Aave have three different on-chain shapes - different fields, different rate math, different architectures. Here's how we map all three into one unified data model, so you query them identically.


A previous article made the general case: on-chain lending is fragmented, and the 1delta API collapses 3,000+ disjoint markets into a single, queryable surface. This piece zooms in on the hardest single-protocol version of that problem - Aave - because Aave isn't one protocol. It's three.

Aave V2, V3 and V4 are three generations of the same brand with genuinely different on-chain shapes: different field layouts, different rate encodings, and - in V4's case - a completely re-architected liquidity model. An integrator who treats them as three separate protocols ends up writing three separate front-ends. We map all three into one unified record, so the code that consumes our API never has to know which version it's looking at.

This is the story of how those three versions differ, why the differences are bigger than the version numbers suggest, and what it takes to make a V2 rate and a V4 rate directly comparable.

The shape of the problem

Here's the same question - "how much can I borrow against this collateral, and when do I get liquidated?" - across the three versions.

Aave V2 one pool · global params One LTV per asset One liquidation threshold Stable + variable borrow No caps. No eMode. No isolation. The simplest case. Aave V3 one pool · per-mode params LTV per eMode category Supply + borrow caps Isolation debt ceilings Siloed borrowing eMode boosts LTV for correlated assets. Re-encoded again in V3.2. Aave V4 Hub & Spoke · per-spoke One factor - both the borrow & liquidation LTV Per-spoke caps Risk-premium pricing No stable borrow. Risk lives per-spoke; liquidity lives in the hub.

Same question, three vocabularies. V2 gives you one LTV per asset. V3 gives you an LTV per eMode category, plus caps, isolation debt ceilings, and siloed-borrowing rules - and then changes how eModes are encoded again in V3.2. V4 throws out the layout entirely: a single factor that serves as both the borrow LTV and the liquidation threshold, attached to a per-spoke risk silo sitting on top of a shared liquidity hub.

The differences are bigger than "v2 → v3 → v4" suggests. These aren't incremental field additions - they're three different mental models of what a lending market is. Our job is to make all three emerge as the same object, where the absence of a feature is a well-defined default rather than a special case you have to code around.

One model, three mappings

Every Aave version flows through the same idea: read each version in its own native shape, then map it onto one common record. The version-specific quirks are absorbed during that mapping. What comes out the other side is identical regardless of where it came from.

Aave V2 single pool · stable + variable Aave V3 / V3.2 caps · eMode · isolation Aave V4 Hub & Spoke · risk premium Normalize: rates → one unit · amounts → one scale · utilization → one formula version-specific quirks absorbed here ONE UNIFIED RESERVE RECORD total deposits · total debt · deposit & borrow APR utilization · risk config · caps · flags · rewards

The payoff is that new features don't ripple outward. When V3.2 changed how eModes are encoded, only the V3 mapping changed - the unified record and everyone downstream stayed put. When V4 introduced the Hub & Spoke model, it got its own mapping, but it lands in the same record. You integrate once, against one shape, and every Aave generation - plus every future one - arrives in that shape.

The unified output - what every version becomes

Regardless of version, every Aave reserve comes out as the same object:

fieldmeaning
marketUidunique market ID (chain + lender + asset)
totalDepositstotal supplied
totalDebttotal borrowed (variable)
totalDebtStablestable borrow (0 where unsupported)
totalLiquidityavailable to withdraw
depositRatedeposit APR, percent
variableBorrowRateborrow APR, percent
stableBorrowRatestable borrow APR, percent (0 where unsupported)
utilization0..1
config{}per-mode collateral params (LTV, liquidation threshold)
borrowCap / supplyCap0 = uncapped
debtCeilingisolation-mode limit
collateralActive / borrowingEnabled / depositsEnabled / isActive / isFrozen / hasStableper-reserve flags
intrinsicYieldextra yield (staking / rebasing)
rewards[]incentive APRs

You read depositRate and get a percent. You read borrowCap and get 0 for "uncapped". You read the risk config and get a comparable LTV and liquidation threshold. You never ask "which Aave version is this?"

The comparability work

Making the shape identical is the easy half. The hard half is making the numbers mean the same thing.

Interest rates - one unit. Aave stores rates in a fixed-point format, but the different generations don't even store the same kind of number - some are expressed as a compounding yield, others as a flat annualized rate. We convert every one of them to a plain APR in percent. The result is that a V2 borrow rate and a V4 borrow rate are directly comparable values you can sort in a single column without misleading anyone.

Amounts - one scale. Raw on-chain balances are unscaled integers that routinely overflow ordinary number types. We scale every amount by its token's decimals and carry it losslessly, so a "total deposits" figure means the same thing across versions and never silently rounds.

Utilization - one formula. Debt over deposits, computed the same way everywhere. One caveat worth flagging for integrators: in V2/V3 utilization is per-reserve, but in V4 the rate-setting utilization is hub-level - pooled across every spoke that shares the asset. The number you see for a V4 spoke reflects the shared pool that actually drives its rate, not just that spoke's local activity.

Features that don't exist in older versions

The guiding rule: a missing feature maps to a neutral default, never an empty value you have to special-case.

FeatureV2V3V4
Supply / borrow capsnone → uncappedper-reserveper-spoke
eModenone → single default modecategoriesreplaced by dynamic config
Isolation modenonedebt ceilingnot applicable
Stable borrowyesyesremoved → always zero
Debt modelvariable + stablevariable + stabledrawn + premium, summed

An uncapped cap reads as 0. A version with no eMode exposes a single default config entry. A version with no stable borrow reports zero and a hasStable: false flag. You read the same fields everywhere and get answers that are simply neutral where the feature is absent - no version branching in your code.

Where each version actually differs

The abstraction is clean. The protocols behind it are not. Here's what each generation brings.

V2 - the simple case (with a long tail of forks)

V2 is the easy one: a single pool, no caps, no eMode, no isolation, one LTV per asset. It still has stable borrow, so its stable rate is real - a feature that simply doesn't exist in V4.

The real complexity in V2 isn't Aave V2 itself; it's the ecosystem of forks built on its design - Aurelius, Lendle, Granary, Radiant and others - each with its own reward tokens and incentive quirks layered on top. We fold all of them into the same V2 mapping so they surface as ordinary Aave-shaped reserves.

V3 - caps, eMode, isolation, and a mid-life format change

V3 is where the per-asset surface explodes. On top of the basics, every asset now carries supply and borrow caps, an isolation-mode debt ceiling, a siloed-borrowing rule, and an LTV that varies by eMode category - the mechanism that lets correlated assets (say, ETH and an ETH liquid-staking token) borrow against each other at much higher efficiency.

Then V3.2 changed how eMode membership is encoded mid-life. An asset that qualified for an eMode under the original layout has to be re-derived under the new one - exactly the kind of change that breaks naïve integrations silently. We absorb both encodings into the same risk-config shape, so from the outside an eMode looks the same whether it came from legacy V3 or V3.2.

V4 - a different protocol wearing the same name

V4 is not a bigger V3. It's a re-architecture, and it's where most of the interesting complexity lives.

Hub & Spoke. A V4 deployment splits into a central Liquidity Hub that holds the actual assets and runs the interest-rate model - and many Spokes, each a front that routes flows in and out of that hub. The key idea: liquidity is shared at the hub, but risk is isolated at the spoke. Each spoke has its own collateral rules, its own health factors, its own risk pricing. The hub is a shared liquidity layer, not a shared risk surface.

Liquidity Hub shared assets · rate model pooled utilization Spoke "Main" own risk · own market Spoke "Lido" own risk · own market Spoke "Kelp" own risk · own market Spoke "Prime" own risk · own market Shared liquidity, isolated risk - each spoke is its own market.

That architecture forces a modeling decision: what is a "market" in V4? It can't be the hub - a user supplying an asset to one spoke and borrowing it from another holds two completely independent positions, with different collateral mixes, LTVs and liquidation prices. So we treat each spoke as its own market, the same way other isolated-market protocols expose one market per pair. Two spokes that share a hub still surface as two distinct markets.

One LTV instead of two. This is the change most likely to surprise integrators coming from V3. In V3, each asset has a borrow LTV and a separate, higher liquidation threshold - the gap between them is your safety buffer. V4 collapses both into a single factor: the level you can borrow up to is the same level at which you get liquidated.

Aave V3Aave V4
Per-asset thresholdsborrow LTV 75%, liquidation 80% → 5% bufferone factor 80%
Maxing out a borrowbuffer remains before liquidationborrows right up to the liquidation point

To keep the unified record consistent, V4 reports the same value for both the borrow and the liquidation factor - so multi-protocol consumers don't need V4-specific handling - but it's worth surfacing to users that in V4 the two are equal by design. The risk discrimination that V3 expressed as a gap between LTV and liquidation threshold, V4 expresses instead as a risk premium: a surcharge on the borrow rate that scales with the quality of the borrower's collateral. We expose its governance bounds so a UI can show the realistic borrow-rate range for an asset.

No stable borrow. The concept is gone in V4. Stable debt and the stable rate are always zero, the stable flag is false, and the debt that does exist is rolled into the single variable-debt figure.

One-sentence takeaway

By mapping each Aave generation through one normalization step into a single record shape, we turn three structurally different protocols into one comparable dataset - where the absence of a feature is a well-defined default, not a special case you have to know about.

Where to go next

  • API reference - endpoints, response shapes, and x-api-key setup.
  • Docs site - supported protocols, network coverage, and the rest of this series.
  • Cross-protocol lending aggregation - how this unified shape lets you compare, optimize, and migrate across every lender we index, not just the three Aaves.
← All posts