Lender
The Lender is the lending pool that allows whitelisted Operators to borrow backing assets from the cUSD vault against delegated collateral. It tracks debt per Operator per asset, accrues two separate interest streams (vault interest paid to stcUSD holders, restaker interest paid to Delegators), and manages a liquidation system with a time-windowed grace period and linearly increasing bonus. All health factors, LTV ratios, and rates are in ray format where 1e27 = 100%.
Mechanics
Borrow Flow
Realize restaker interest: Any accrued restaker interest for the Operator is realized first, updating their debt balance before new principal is added.
Max borrow calculation (if
maxBorrow = true):ViewLogic.maxBorrowablecomputes remaining capacity based on the Operator's delegation coverage and current LTV.Validation:
ValidationLogic.validateBorrowchecks that the Operator is whitelisted, not paused, the asset is not paused, minimum borrow is met, and vault liquidity is sufficient.State updates:
IDelegation.setLastBorrowis called; the Operator is marked as borrowing the reserve.Asset transfer: The vault borrows the asset and sends it to
_receiver; a debt token is minted to the Operator;reserve.debtis incremented.
Repay Flow
Realize restaker interest: Accrued restaker interest is settled first.
Amount cap: Repayment is silently capped at the Operator's total debt. If the remaining debt after repayment would fall below
minBorrow, the repayment is reduced to leave exactlyminBorrowoutstanding.Repayment allocation (in priority order):
Vault interest (
interestRepaid): Any excess abovereserve.debt + unrealizedInterestgoes tointerestReceiver.Restaker interest (
restakerRepaid): Settled unrealized restaker interest is sent to the Delegation contract and distributed to Delegators.Vault principal (
vaultRepaid): Repaysreserve.debtand returns assets to the vault.
Debt token burn: The debt token is burned for the full
repaidamount.
Interest Accrual
Cap uses two separate interest streams, both accrued continuously but realized discretely:
Vault interest — paid to stcUSD holders via the FeeAuction:
Accrual is tracked by the RateOracle's utilization index (time-weighted borrow rate).
Realized by calling
realizeInterest(_asset)— permissionless.The Lender borrows the interest amount from the vault and sends it directly to
interestReceiver.
Restaker interest — paid to Delegators:
Accrues per-Operator per-asset:
debtBalance × restakerRate × elapsedTime / SECONDS_IN_YEAR.restakerRateis set per-Operator by admin via the oracle.Realized on every borrow and repay, or permissionlessly via
realizeRestakerInterest.If vault liquidity is insufficient, the shortfall becomes unrealized interest — added to the Operator's debt balance and settled during future repayments.
Liquidation Flow
Liquidation is a two-step process:
Step 1 — Open liquidation window:
Anyone calls
openLiquidation(_agent)whenhealth < 1e27(below 100%).liquidationStart[agent]is recorded. Reverts if a non-expired window is already open.
Step 2 — Liquidate:
liquidateis callable aftergraceseconds have elapsed sinceliquidationStart.The liquidator repays up to
maxLiquidatableof the Operator's debt for one asset.A bonus is awarded in collateral value: starts at 0 after grace, grows linearly to
bonusCapby the timeexpiryis reached. Emergency liquidations (health dangerously low) receive the fullbonusCapimmediately.IDelegation.slashis called to seize the bonus value from the Operator's Delegators.If health recovers to
≥ 1e27after the liquidation, the window is automatically closed.
Emergency liquidations: If totalDelegation × emergencyLiquidationThreshold / totalDebt < 1e27, the grace period is skipped and the full bonus is applied immediately.
Health Factor
health ≥ 1e27(≥ 100%): Healthy — borrowing allowed, no liquidation.health < 1e27(< 100%): Undercollateralised — liquidation window can be opened.totalDebt = 0: Health returnstype(uint256).max.
All values in USD with 8 decimals. liquidationThreshold and ltv are set per-Operator by the Delegation contract.
Architecture
Core Functions
borrow(address _asset, uint256 _amount, address _receiver)
borrow(address _asset, uint256 _amount, address _receiver)Called by a whitelisted Operator. msg.sender is the Operator. Pass _amount = 0 with maxBorrow = true (via the BorrowParams struct internally) to borrow the maximum available.
_asset
address
Asset to borrow
_amount
uint256
Amount to borrow in asset decimals
_receiver
address
Address to receive the borrowed assets
Returns: borrowed — actual amount borrowed (may be less if vault liquidity is limited).
repay(address _asset, uint256 _amount, address _agent)
repay(address _asset, uint256 _amount, address _agent)Repay debt on behalf of _agent. msg.sender provides the assets via transferFrom. Pass _amount = type(uint256).max to repay the full balance.
_asset
address
Asset to repay
_amount
uint256
Amount to repay in asset decimals
_agent
address
Operator whose debt is being repaid
Returns: repaid — actual amount repaid.
realizeInterest(address _asset)
realizeInterest(address _asset)Permissionless. Borrows accrued vault interest from the vault and sends it to interestReceiver (typically the FeeAuction). Reverts with ZeroRealization if there is nothing to realize.
realizeRestakerInterest(address _agent, address _asset)
realizeRestakerInterest(address _agent, address _asset)Permissionless. Realizes the accrued restaker interest for _agent for _asset. Borrows from the vault and sends to the Delegation contract for distribution to Delegators. If vault liquidity is insufficient, the shortfall is recorded as unrealized interest.
openLiquidation(address _agent)
openLiquidation(address _agent)Opens the liquidation window for _agent. Requires health < 1e27. Reverts if an active, non-expired window already exists.
closeLiquidation(address _agent)
closeLiquidation(address _agent)Closes the liquidation window when health ≥ 1e27. Anyone can call this once the Operator is healthy again.
liquidate(address _agent, address _asset, uint256 _amount, uint256 _minLiquidatedValue)
liquidate(address _agent, address _asset, uint256 _amount, uint256 _minLiquidatedValue)Repays up to _amount of _agent's debt in _asset and receives slashed collateral value in return. Requires an open liquidation window past the grace period.
_agent
address
Operator to liquidate
_asset
address
Asset to repay on the Operator's behalf
_amount
uint256
Amount of _asset to repay (capped at maxLiquidatable)
_minLiquidatedValue
uint256
Minimum USD value of collateral to receive (slippage protection)
Returns: liquidatedValue — USD value (8 decimals) of collateral slashed and awarded to the liquidator.
View Functions
agent(address _agent)
Returns a complete snapshot of the Operator's collateral and health position. All USD values use 8 decimals; ratios are in ray (1e27 = 100%).
maxBorrowable(address _agent, address _asset)
Returns the maximum additional amount of _asset the Operator can borrow given their current delegation and debt.
maxLiquidatable(address _agent, address _asset)
Returns the maximum amount of _asset that can be liquidated against _agent in a single call to reach targetHealth.
bonus(address _agent)
Returns the current liquidation bonus in ray format. Grows linearly from 0 (at grace) to bonusCap (at expiry). Returns bonusCap immediately for emergency liquidations.
debt(address _agent, address _asset)
Returns the Operator's total debt for _asset including accrued restaker interest not yet settled.
accruedRestakerInterest(address _agent, address _asset)
Returns the restaker interest accrued since the last realization: debt × rate × elapsed / SECONDS_IN_YEAR.
maxRealization(address _asset) / maxRestakerRealization(address _agent, address _asset)
Preview functions for how much interest can be realized right now vs. how much would become unrealized due to vault liquidity constraints.
Admin Functions
addAsset(AddAssetParams)
Add a new reserve (asset, vault, debt token, interest receiver)
removeAsset(address)
Remove a reserve with zero outstanding debt
pauseAsset(address, bool)
Pause or unpause borrowing for an asset
setInterestReceiver(address, address)
Update the vault interest recipient for an asset
setMinBorrow(address, uint256)
Set minimum borrow floor for an asset
setGrace(uint256)
Update the liquidation grace period (seconds)
setExpiry(uint256)
Update the liquidation expiry window (seconds)
setBonusCap(uint256)
Update the maximum liquidation bonus (ray)
Data Structures
LenderStorage
LenderStoragedelegation
address
Delegation contract — provides coverage, LTV, and slash functions
oracle
address
Oracle — provides asset prices and per-Operator restaker rates
reservesData
mapping
Reserve configuration and state per asset
targetHealth
uint256
Health ratio to restore after liquidation (ray, 1e27 = 100%)
grace
uint256
Seconds after openLiquidation before liquidation is allowed
expiry
uint256
Seconds after which a liquidation window expires if unused
bonusCap
uint256
Maximum liquidation bonus (ray)
emergencyLiquidationThreshold
uint256
Ratio below which emergency liquidation rules apply (ray)
ReserveData
ReserveDatavault
cUSD vault that holds the backing assets for this reserve
debtToken
Non-transferrable ERC20 tracking each Operator's outstanding principal
interestReceiver
Receives vault interest (typically the FeeAuction)
debt
Total vault principal currently borrowed across all Operators
totalUnrealizedInterest
Sum of all per-Operator unrealized restaker interest
unrealizedInterest
Per-Operator restaker interest that couldn't be realized due to illiquidity
lastRealizationTime
Timestamp of the last restaker interest realization per Operator
minBorrow
Minimum outstanding balance — partial repayments that would leave less than this are rejected
Usage Examples
1. Operator borrows an asset
2. Keeper realizes vault interest
3. Liquidator opens and executes a liquidation
Last updated