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

  1. Realize restaker interest: Any accrued restaker interest for the Operator is realized first, updating their debt balance before new principal is added.

  2. Max borrow calculation (if maxBorrow = true): ViewLogic.maxBorrowable computes remaining capacity based on the Operator's delegation coverage and current LTV.

  3. Validation: ValidationLogic.validateBorrow checks that the Operator is whitelisted, not paused, the asset is not paused, minimum borrow is met, and vault liquidity is sufficient.

  4. State updates: IDelegation.setLastBorrow is called; the Operator is marked as borrowing the reserve.

  5. Asset transfer: The vault borrows the asset and sends it to _receiver; a debt token is minted to the Operator; reserve.debt is incremented.

Repay Flow

  1. Realize restaker interest: Accrued restaker interest is settled first.

  2. 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 exactly minBorrow outstanding.

  3. Repayment allocation (in priority order):

    • Vault interest (interestRepaid): Any excess above reserve.debt + unrealizedInterest goes to interestReceiver.

    • Restaker interest (restakerRepaid): Settled unrealized restaker interest is sent to the Delegation contract and distributed to Delegators.

    • Vault principal (vaultRepaid): Repays reserve.debt and returns assets to the vault.

  4. Debt token burn: The debt token is burned for the full repaid amount.

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.

  • restakerRate is 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:

  1. Anyone calls openLiquidation(_agent) when health < 1e27 (below 100%).

  2. liquidationStart[agent] is recorded. Reverts if a non-expired window is already open.

Step 2 — Liquidate:

  1. liquidate is callable after grace seconds have elapsed since liquidationStart.

  2. The liquidator repays up to maxLiquidatable of the Operator's debt for one asset.

  3. A bonus is awarded in collateral value: starts at 0 after grace, grows linearly to bonusCap by the time expiry is reached. Emergency liquidations (health dangerously low) receive the full bonusCap immediately.

  4. IDelegation.slash is called to seize the bonus value from the Operator's Delegators.

  5. If health recovers to ≥ 1e27 after 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 returns type(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)

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.

Parameter
Type
Description

_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 debt on behalf of _agent. msg.sender provides the assets via transferFrom. Pass _amount = type(uint256).max to repay the full balance.

Parameter
Type
Description

_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)

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)

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)

Opens the liquidation window for _agent. Requires health < 1e27. Reverts if an active, non-expired window already exists.


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)

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.

Parameter
Type
Description

_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

Function
Description

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

Field
Type
Description

delegation

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

Field
Description

vault

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