TL;DR
A leverage-yield vault deposits an LSD (weETH, wstETH) into AAVE, borrows ETH against it, swaps the borrowed ETH back to the LSD, and re-deposits — looping to a multiple of the user’s principal. Yield comes from three places:- AAVE supply rate on the collateral leg (paid by other AAVE borrowers).
- LSD staking yield baked into the token price (the LSD appreciates against ETH).
- The above two amplified by leverage, minus the AAVE borrow rate paid on the borrow leg.
getApr() reads only AAVE rates and misses (2). getEffectiveApr() adds the LSD yield from DefiLlama and reports the honest number.
Formula
targetLTV comes from the vault contract (targetLTV(), returned in basis points), aaveSupply / aaveBorrow come from the AAVE pool’s getReserveData(), and lsdStaking comes from DefiLlama (see LSD Source).
Derivation
The formula is the closed-form limit of the vault’s recursive supply→borrow→re-supply loop. Start with depositX of LSD as collateral:
So in the steady state, per
X of original deposit:
- Total supplied =
1 / (1 − LTV) = 1 + leverage - Total borrowed =
leverage
+ supplyRate is your principal still earning yield; the leverage × spread is the amplified spread from the loop.
The LSD twist
For LSD-backed vaults, the collateral leg earns yield from two independent sources that compose linearly:- AAVE’s
currentLiquidityRatefor the LSD — depends on who else is borrowing it on AAVE. Often near zero for LSDs because they’re not heavily borrowed. - The LSD’s native staking yield — the LSD token (weETH, wstETH) appreciates against ETH at the issuer’s staking rate. This is off-chain yield as far as AAVE is concerned — it doesn’t show in
currentLiquidityRate. It just accrues to the token-price.
supplyRate → aaveSupply + lsdStaking. The formula stays identical, only the supply-side input changes:
LSD source: DefiLlama
The SDK fetcheslsdStaking from DefiLlama’s per-pool chart endpoint:
apy is the compounded yield including reward tokens (what the depositor actually earns). The SDK takes data[data.length - 1].apy (latest entry) and folds it into the formula above.
Each vault registers a DefiLlama poolId in @sodax/types:
The endpoint is CORS-enabled (
access-control-allow-origin: *) so a browser-side dApp can hit it directly — no backend required.
If the live fetch fails, the SDK falls back to a hardcoded fallbackAprPct in the same registry entry and flags the response with stale: true so the UI can show the value as an estimate.
Worked example
A snapshot of the weETH vault at the moment of writing:
For comparison, the AAVE-only number (what
getApr() returns, ignoring the LSD’s staking yield) on the same vault is:
effectiveNet and aaveOnlyNet is the whole reason these vaults exist: it’s the LSD’s staking yield multiplied by leverage.
Code references
- Formula implementation:
LeverageYieldService.getEffectiveApr(packages/sdk/src/leverageYield/LeverageYieldService.ts) - LSD fetcher:
fetchDefillamaAprin the same file - AAVE-only calculation:
LeverageYieldService.getApr(also same file) - Vault registry (with
lsdSource.poolId):packages/types/src/leverageYield/leverageYield.ts
Caveats
- Steady-state, not realised: this is the APR a position earns at
targetLTVif rates stay constant. Realised APY depends on rebalance cadence, rate volatility, and how long the vault sits off-target. lsdStakingis a moving average: DefiLlama publishes the trailing yield, typically a 7- to 30-day window. Spot yield can differ by a few bp.- Vault drift: if
position.ltv<targetLTV, the actual position is under-leveraged and earns less than the headline number. WatchLeverageYieldService.getPosition()for the live LTV. - Negative spread case: if
aaveBorrow > effectiveSupply, leverage amplifies the loss. The formula handles this correctly (effectiveNetgoes negative) — it’s a real signal, not a bug.