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.
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.
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:
| field | meaning |
|---|---|
marketUid | unique market ID (chain + lender + asset) |
totalDeposits | total supplied |
totalDebt | total borrowed (variable) |
totalDebtStable | stable borrow (0 where unsupported) |
totalLiquidity | available to withdraw |
depositRate | deposit APR, percent |
variableBorrowRate | borrow APR, percent |
stableBorrowRate | stable borrow APR, percent (0 where unsupported) |
utilization | 0..1 |
config{} | per-mode collateral params (LTV, liquidation threshold) |
borrowCap / supplyCap | 0 = uncapped |
debtCeiling | isolation-mode limit |
collateralActive / borrowingEnabled / depositsEnabled / isActive / isFrozen / hasStable | per-reserve flags |
intrinsicYield | extra 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.
| Feature | V2 | V3 | V4 |
|---|---|---|---|
| Supply / borrow caps | none → uncapped | per-reserve | per-spoke |
| eMode | none → single default mode | categories | replaced by dynamic config |
| Isolation mode | none | debt ceiling | not applicable |
| Stable borrow | yes | yes | removed → always zero |
| Debt model | variable + stable | variable + stable | drawn + 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.
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 V3 | Aave V4 | |
|---|---|---|
| Per-asset thresholds | borrow LTV 75%, liquidation 80% → 5% buffer | one factor 80% |
| Maxing out a borrow | buffer remains before liquidation | borrows 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-keysetup. - 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.