Skip to content

Functions

Note

Some of the parameters require adapter and data to be set.
- If the adapter is an ERC20, the data parameter is the amount.
- For ERC721, the data parameter is the token ID.

Liquidity Provision

deposit

deposit(uint256 amount)

Summary
Deposits the reserve asset (crvUSD) into the pool and mints RToken 1:1 for the caller, minus any deposit fee.

Guarded Method
Callable by any address (EOA or contract). Subject to pause.

Parameters

Name Type Description
amount uint256 Deposit amount in crvUSD (18 decimals).

Emits

  • Deposit(address indexed user, uint256 amount, uint256 rTokenMinted)

Reverts

  • SupplyCapExceeded() — if LendingPoolSupplyCap would be exceeded
  • AmountZero() — if amount == 0

Requirements

  • Deposit fee (LendingPoolDepositFee) goes to FeeCollector before minting RToken.
  • Withdrawals may be paused but deposits remain open unless explicitly disabled.
TS
const tx = await lendingPool.deposit(parseUnits("1000", 18));
await tx.wait();
Source code
function deposit(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) notBlacklisted(msg.sender) {        
    ReserveLibrary.updateReserveState(reserve, rateData);

    uint256 depositFeeAmount = (feeCollector == address(0) || msg.sender == stabilityPool) ? 0 : amount.percentMul(parameters.depositFee);
    uint256 depositAmount = amount - depositFeeAmount;
    _validateDepositSupplyCap(depositAmount);

    uint256 mintedAmount = ReserveLibrary.deposit(reserve, rateData, depositAmount, msg.sender);

    _rebalanceLiquidity();

    if(depositFeeAmount > 0) _collectFee(depositFeeAmount, keccak256("LENDING_FEE"));

    depositBlock[msg.sender] = block.number;

    emit Deposit(msg.sender, depositAmount, mintedAmount);
}

withdraw

withdraw(uint256 amount)

Summary
Burns the caller’s RToken balance to redeem reserve assets (crvUSD).
Updates reserve state, ensures liquidity, and rebalances after the withdrawal.

Guarded Method
- Callable by any user with RToken balance.
- Restricted by nonReentrant, whenNotPaused, and blacklist checks.
- Withdrawals can be globally paused via governance parameters, except for StabilityPool.

Full Withdrawal Tip

Pass uint256.max as the amount parameter to withdraw your entire RToken balance.
This ensures that no residual "dust" remains due to rounding or index scaling.

Parameters

Name Type Description
amount uint256 Amount of reserve assets to withdraw. Use uint256.max to withdraw full balance.

Emits - Withdraw(address indexed user, uint256 amountWithdrawn)

Reverts

  • WithdrawalsArePaused() — if governance has disabled withdrawals (non-StabilityPool users).
  • InsufficientBalance() — if caller’s RToken balance < requested amount.
  • LiquidityShortfall() — if pool liquidity is insufficient to honor the withdrawal.

Requirements

  • Caller must hold at least amount RToken.
  • Pool liquidity must be sufficient after rebalancing.
  • Reserve state updated via ReserveLibrary.updateReserveState before execution.
Example (TypeScript / ethers)
// Withdraw specific amount (1000 crvUSD)
const tx = await lendingPool.withdraw(parseUnits("1000", 18));
await tx.wait();

// Withdraw full balance without leaving dust
const txFull = await lendingPool.withdraw(constants.MaxUint256);
await txFull.wait();
Source code
function withdraw(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) notBlacklisted(msg.sender) {
    if (msg.sender != stabilityPool && parameters.withdrawalsPaused) revert WithdrawalsArePaused();

    ReserveLibrary.updateReserveState(reserve, rateData);

    uint256 scaledAmount = (amount == type(uint256).max) 
        ? IERC20(reserve.reserveRTokenAddress).balanceOf(msg.sender) 
        : amount;

    if (scaledAmount > IERC20(reserve.reserveRTokenAddress).balanceOf(msg.sender)) revert InsufficientBalance();

    _ensureLiquidity(scaledAmount);

    (uint256 amountWithdrawn,,) = ReserveLibrary.withdraw(
        reserve,
        rateData,
        scaledAmount,
        msg.sender
    );

    _rebalanceLiquidity();

    emit Withdraw(msg.sender, amountWithdrawn);
}

Collateral management

openVaultPosition

openVaultPosition()

Summary
Initializes a user’s vault for collateral management.
Must be called once before using depositAsset or borrow.
Collects a one-time fee if configured.

Guarded Method

  • Callable by any user not blacklisted.
  • No nonReentrant guard since it is a simple state change.

Parameters - None

Emits - VaultOpeningFeePaid(address indexed user, uint256 fee)

Reverts - VaultPositionAlreadyOpened() — if vault already initialized.

Requirements

  • One-time action per user.
  • If vaultOpeningFee > 0, fee is transferred to FeeCollector.
Example (TypeScript / ethers)
const tx = await lendingPool.openVaultPosition();
await tx.wait();
Source code
function openVaultPosition() external notBlacklisted(msg.sender) {
    if (vaultOpened[msg.sender]) revert VaultPositionAlreadyOpened();
    vaultOpened[msg.sender] = true;
    if (parameters.vaultOpeningFee > 0) _collectFee(parameters.vaultOpeningFee, keccak256("LENDING_FEE"));
    emit VaultOpeningFeePaid(msg.sender, parameters.vaultOpeningFee);
}

depositAsset

depositAsset(address adapter, bytes calldata data)

Summary
Supplies collateral into the pool through a registered adapter.
Updates reserve state, verifies that the vault is open, and delegates the deposit logic to the adapter.

Guarded Method

  • Callable by any user with an open vault.
  • Protected by nonReentrant, whenNotPaused, onlySupportedAdapter, and blacklist checks.

Parameters

Name Type Description
adapter address Registered IAssetAdapter to route deposit
data bytes Adapter-specific calldata

Emits - May emit adapter-specific events via IAssetAdapter.deposit.

Reverts

  • CannotDepositWhenUnderLiquidation() — if the position is flagged under liquidation.
  • VaultPositionNotOpened() — if openVaultPosition() has not been called.
  • UnsupportedAdapter() — if adapter not registered.

Requirements

  • User must have paid the VaultOpeningFee.
  • Collateral type must be whitelisted.
  • Reserve state updated via ReserveLibrary.updateReserveState.
Example (TypeScript / ethers)
// Approve adapter to spend the asset
await assetContract.approve(adapterAddr, amount);

const tx = await lendingPool.depositAsset(adapterAddr, adapterData);
await tx.wait();
Source code
function depositAsset(address adapter, bytes calldata data) external nonReentrant whenNotPaused onlySupportedAdapter(adapter) notBlacklisted(msg.sender) {
    CollateralPosition memory position = getPosition(adapter, msg.sender, data);
    if (position.isUnderLiquidation) revert CannotDepositWhenUnderLiquidation();
    if (!vaultOpened[msg.sender]) revert VaultPositionNotOpened();
    ReserveLibrary.updateReserveState(reserve, rateData);
    IAssetAdapter(adapter).deposit(msg.sender, data);
}

withdrawAsset

withdrawAsset(address adapter, bytes calldata data)

Summary
Withdraws collateral from the pool via a registered adapter.
Ensures that health factor remains above liquidation threshold after withdrawal.

Guarded Method
- Callable by any user with sufficient collateral.
- Protected by nonReentrant, whenNotPaused, onlySupportedAdapter, and blacklist checks.

Parameters

Name Type Description
adapter address Registered IAssetAdapter for the collateral
data bytes Adapter-specific calldata

Emits - May emit adapter-specific events via IAssetAdapter.withdraw.

Reverts - CannotWithdrawUnderLiquidation() — if position is flagged under liquidation.
- WithdrawalWouldLeaveUserUnderCollateralized() — if withdrawal reduces health factor below HEALTH_FACTOR_LIQUIDATION_THRESHOLD.

Requirements - Health factor after withdrawal must remain ≥ threshold.
- Reserve state updated via ReserveLibrary.updateReserveState.
- Collateral must be whitelisted.

Example (TypeScript / ethers)
const tx = await lendingPool.withdrawAsset(adapterAddr, adapterData);
await tx.wait();
Source code
function withdrawAsset(address adapter, bytes calldata data) external nonReentrant whenNotPaused onlySupportedAdapter(adapter) notBlacklisted(msg.sender) {
    CollateralPosition memory position = getPosition(adapter, msg.sender, data);
    if (position.isUnderLiquidation) revert CannotWithdrawUnderLiquidation();

    ReserveLibrary.updateReserveState(reserve, rateData);

    uint256 positionDebt = getPositionScaledDebt(adapter, msg.sender, data);
    if (positionDebt > 0) {
        uint256 depositedValue = IAssetAdapter(adapter).getAssetValue(msg.sender, data);
        uint256 withdrawalValue = IAssetAdapter(adapter).getWithdrawValue(msg.sender, data);
        uint256 newCollateralValue = depositedValue - withdrawalValue;
        uint256 newHealthFactor;
        if (positionDebt < 1) newHealthFactor = WadRayMath.RAY;
        else {
            uint256 collateralThreshold = newCollateralValue.percentMul(parameters.borrowThreshold);
            newHealthFactor = (collateralThreshold * 1e18) / positionDebt;
        }
        if (newHealthFactor < HEALTH_FACTOR_LIQUIDATION_THRESHOLD) {
            revert WithdrawalWouldLeaveUserUnderCollateralized();
        }
    }

    IAssetAdapter(adapter).withdraw(msg.sender, data);
}

Borrow and repayments

borrow

borrow(address adapter, bytes data, uint256 amount)

Summary
Borrows the reserve asset (crvUSD) against the caller’s collateral managed by adapter.
Internally validates collateral, ensures pool liquidity (vault pull if needed), mints scaled DebtToken, transfers the borrowed amount to the user, updates indices/rates, and rebalances liquidity.
If the position had insurance, it is cleared after a successful borrow.

Parameters

Name Type Description
adapter address Registered IAssetAdapter for the collateral position
data bytes Adapter-specific calldata identifying the position
amount uint256 Amount of reserve asset to borrow (18 decimals)

Returns
- None

Emits
- Borrow(address indexed user, uint256 amount)

Reverts

  • CannotBorrowUnderLiquidation() — if the position is flagged under liquidation.
  • Any error from _validateBorrow — insufficient collateral vs. BorrowThreshold / caps.
  • Liquidity errors from _ensureLiquidity — if the pool cannot source funds (after vault pull).
  • Adapter/role/amount guards listed above.

Side Effects

  • Clears insurance state on the position after a successful borrow:
  • position.isInsured = false
  • position.rawInsuredBalance = 0

Requirements

  • Adapter must be registered and support the asset.
  • Post-borrow health factor must remain ≥ liquidation threshold (enforced by _validateBorrow).
  • Sufficient pool liquidity must be obtainable (_ensureLiquidity).
  • Indices updated before state changes to preserve accrual correctness.

Notes

  • Insurance is intentionally cleared on borrow. To remain insured after borrowing again, the user must re-purchase insurance according to the protocol rules.
  • Debt accounting uses scaled amounts against reserve.usageIndex.
Example (TypeScript / ethers)
import { parseUnits } from "ethers";

// Adapter + data identify the collateral position (e.g., NFT vault, ERC20 vault)
const adapter = "0xAdapter...";
const data = adapterSpecificCalldata; // bytes encoded off-chain per adapter ABI
const amount = parseUnits("5000", 18); // 5,000 crvUSD

const tx = await lendingPool.borrow(adapter, data, amount);
await tx.wait();
Source code (public wrapper and internal implementation)
function borrow(address adapter, bytes calldata data, uint256 amount)
    external
    nonReentrant
    whenNotPaused
    onlyValidAmount(amount)
    onlySupportedAdapter(adapter)
    notBlacklisted(msg.sender)
{
    _borrow(adapter, data, amount);
    CollateralPosition storage position = positions[msg.sender][IAssetAdapter(adapter).getPositionKey(data)];
    if (position.isInsured) {
        position.isInsured = false;
        position.rawInsuredBalance = 0;
    }
}

function _borrow(address adapter, bytes calldata data, uint256 amount) internal {
    CollateralPosition storage position = positions[msg.sender][IAssetAdapter(adapter).getPositionKey(data)];
    if (position.isUnderLiquidation) revert CannotBorrowUnderLiquidation();

    ReserveLibrary.updateReserveState(reserve, rateData);
    _validateBorrow(adapter, data, amount);
    _ensureLiquidity(amount);

    (, uint256 underlyingAmount, uint256 userIncrease, ) =
        IDebtToken(reserve.reserveDebtTokenAddress).mint(
            msg.sender,
            msg.sender,
            amount,
            reserve.usageIndex,
            abi.encode(adapter, data)
        );

    position.positionIndex = reserve.usageIndex;
    IRToken(reserve.reserveRTokenAddress).transferAsset(msg.sender, amount);
    position.rawDebtBalance += underlyingAmount + userIncrease;

    ReserveLibrary.updateInterestRatesAndLiquidity(reserve, rateData, 0, amount);
    _rebalanceLiquidity();

    emit Borrow(msg.sender, amount);
}

borrowWithInsurance

borrowWithInsurance(address adapter, bytes data, uint256 amount)

Summary
Executes a borrow and simultaneously insures the resulting debt.
The insurance fee is calculated and collected upfront before the state changes.
Post-borrow, the user’s position is marked insured for both the newly borrowed amount and any previously uninsured balance.

Differences from borrow
- Requires the user to have approved and hold sufficient reserve asset to cover the insuranceFeeAmount.
- Calls _collectFee(..., INSURANCE_FEE) immediately after the borrow.
- Sets position.isInsured = true and increments rawInsuredBalance by uninsuredBalance + amount.
- Emits InsuredAsset(...) in addition to the normal Borrow event.

Extra Reverts
- InsufficientAllowanceForFees() — if ERC20 allowance < insuranceFeeAmount.
- InsufficientBalance() — if ERC20 balance < insuranceFeeAmount.

Notes
- Borrowing again without this function clears existing insurance.
- To stay insured across multiple borrows, always use borrowWithInsurance.

Example (TypeScript / ethers)
const adapter = "0xAdapter...";
const data = adapterSpecificCalldata;
const amount = parseUnits("3000", 18);

// Approve insurance fee beforehand
await reserveAsset.approve(lendingPool.target, expectedFee);

const tx = await lendingPool.borrowWithInsurance(adapter, data, amount);
await tx.wait();
Source code
function borrowWithInsurance(address adapter, bytes calldata data, uint256 amount)
    external
    nonReentrant
    whenNotPaused
    onlyValidAmount(amount)
    onlySupportedAdapter(adapter)
    notBlacklisted(msg.sender)
{
    CollateralPosition storage position =
        positions[msg.sender][IAssetAdapter(adapter).getPositionKey(data)];

    uint256 uninsuredBalance = getUninsuredBalance(adapter, msg.sender, data);
    uint256 insuranceFeeAmount = calculateInsuranceFee(adapter, msg.sender, data, amount);

    if (IERC20(reserve.reserveAssetAddress).allowance(msg.sender, address(this)) < insuranceFeeAmount)
        revert InsufficientAllowanceForFees();
    if (IERC20(reserve.reserveAssetAddress).balanceOf(msg.sender) < insuranceFeeAmount)
        revert InsufficientBalance();

    _borrow(adapter, data, amount);

    _collectFee(insuranceFeeAmount, keccak256("INSURANCE_FEE"));
    position.isInsured = true;
    position.rawInsuredBalance += uninsuredBalance + amount;

    emit InsuredAsset(IAssetAdapter(adapter).getAssetToken(), msg.sender, data, uninsuredBalance);
}

getUninsuredBalance

getUninsuredBalance(address adapter, address user, bytes data) → uint256

Summary
Returns the portion of a user’s debt that is not covered by insurance.
Computed as rawDebtBalance - rawInsuredBalance for the given collateral position.

Parameters

Name Type Description
adapter address Registered IAssetAdapter for the collateral
user address Address of the borrower
data bytes Adapter-specific calldata identifying the position

Returns
| Type | Description | |----------|-----------------------------------| | uint256 | Uninsured portion of the debt |

Example (TypeScript / ethers)
const uninsured = await lendingPool.getUninsuredBalance(adapterAddr, userAddr, adapterData);
console.log("Uninsured balance:", uninsured.toString());
Source code
function getUninsuredBalance(address adapter, address user, bytes calldata data)
    public
    view
    returns (uint256)
{
    CollateralPosition memory position = getPosition(adapter, user, data);
    return position.rawDebtBalance - position.rawInsuredBalance;
}

calculateInsuranceFee

calculateInsuranceFee(address adapter, address user, bytes data, uint256 amount) → uint256

Summary
Computes the insurance fee owed for a user’s position if they borrow with insurance.
The fee covers both the new borrow amount and any previously uninsured balance.

Parameters

Name Type Description
adapter address Registered IAssetAdapter for the collateral
user address Address of the borrower
data bytes Adapter-specific calldata identifying the position
amount uint256 Amount of reserve asset intended to be borrowed

Returns

Type Description
uint256 Insurance fee in crvUSD

Formula

insuranceFee = (uninsuredBalance + amount) * insuranceFeeRate

  • uninsuredBalance = from getUninsuredBalance
  • insuranceFeeRate = parameters.insuranceFee (basis points)

Notes

  • If the user borrows without insurance initially, then later decides to insure, they must pay on their entire outstanding debt, not just the new amount.
  • Pure view function: no state changes, no events.
Example (TypeScript / ethers)
const fee = await lendingPool.calculateInsuranceFee(adapterAddr, userAddr, adapterData, parseUnits("2000", 18));
console.log("Insurance fee:", fee.toString());
Source code
function calculateInsuranceFee(address adapter, address user, bytes calldata data, uint256 amount)
    public
    view
    returns (uint256)
{
    uint256 uninsuredBalance = getUninsuredBalance(adapter, user, data);
    return (uninsuredBalance + amount).percentMul(parameters.insuranceFee);
}

repay

repay(address adapter, bytes data, uint256 amount)

Summary
Repays the caller’s own debt for a specific collateral position and updates indices, debt balances, insurance status, rates, and liquidity.

Parameters

Name Type Description
adapter address Registered IAssetAdapter for the collateral
data bytes Adapter-specific calldata identifying the position
amount uint256 Repayment amount (pass type(uint256).max to repay full scaled debt)

Emits
Repay(address payer, address onBehalfOf, uint256 actualRepayAmount)

Reverts (selected)

  • InvalidAmount()amount == 0
  • CannotRepayUnderLiquidationWithoutInsurance() — under liquidation and not insured
  • GracePeriodExpired() — liquidation grace window elapsed
  • ResidualCannotBeRepaid() — remaining scaled debt < MIN_BORROW_AMOUNT but still > 0
  • ERC20 allowance/balance failures on transfer
  • Adapter validation failures

Notes

  • To repay the entire outstanding scaled debt and avoid dust, pass type(uint256).max as amount.
  • After successful repayment, withdrawAsset can be called for an ERC-721 adapter to retrieve the NFT.
  • For ERC-20 adapters, you can continue repaying until your position approaches the borrow threshold; ensure collateralization remains sufficient.
  • If a small residual remains below MIN_BORROW_AMOUNT, the system reverts; repay in full to avoid this.
Example (TypeScript / ethers)
// Repay a fixed amount
await reserveAsset.approve(lendingPool.target, repayAmt);
await (await lendingPool.repay(adapter, data, repayAmt)).wait();

// Repay in full (no dust)
await reserveAsset.approve(lendingPool.target, maxExpected);
await (await lendingPool.repay(adapter, data, ethers.constants.MaxUint256)).wait();
Source code (public + internal)
function repay(address adapter, bytes calldata data,  uint256 amount)
    external
    nonReentrant
    whenNotPaused
    onlyValidAmount(amount)
    onlySupportedAdapter(adapter)
    notBlacklisted(msg.sender)
{
    _repay(adapter, data, amount, msg.sender);
}

function _repay(address adapter, bytes calldata data, uint256 amount, address onBehalfOf) internal {
    if (amount == 0) revert InvalidAmount();
    if (onBehalfOf == address(0)) revert AddressCannotBeZero();

    IAssetAdapter(adapter).validate(onBehalfOf, data);

    // Update reserve state before repayment
    ReserveLibrary.updateReserveState(reserve, rateData);

    CollateralPosition storage position = positions[onBehalfOf][IAssetAdapter(adapter).getPositionKey(data)];

    // If the user is under liquidation and position is not insured, they cannot repay.
    if (position.isUnderLiquidation && !position.isInsured) revert CannotRepayUnderLiquidationWithoutInsurance();

    // If the position is under liquidation and the grace period has expired, the user cannot repay.
    if (position.isUnderLiquidation && block.timestamp > position.liquidationStartTime + parameters.liquidationGracePeriod) revert GracePeriodExpired();

    // Calculate the position's debt including any accrued interest
    uint256 positionScaledDebt = getPositionScaledDebt(adapter, onBehalfOf, data);
    // If amount is greater than positionDebt, cap it at positionDebt
    uint256 actualRepayAmount = (amount == type(uint256).max) ? positionScaledDebt : amount;

    // Burn DebtTokens from the user whose debt is being repaid (onBehalfOf)
    (, , uint256 normalizedAmountBurned, uint256 balanceIncrease) =
        IDebtToken(reserve.reserveDebtTokenAddress).burn(
            onBehalfOf,
            actualRepayAmount,
            this.getNormalizedDebt(),
            abi.encode(adapter, data)
        );

    // Update the position index of the user
    position.positionIndex = reserve.usageIndex;

    // Transfer reserve assets from the caller (msg.sender) to the reserve
    IERC20(reserve.reserveAssetAddress).safeTransferFrom(msg.sender, reserve.reserveRTokenAddress, actualRepayAmount);

    // Update position's debt balance
    if (balanceIncrease > 0) {
        position.rawDebtBalance += balanceIncrease;
    }
    position.rawDebtBalance = position.rawDebtBalance - normalizedAmountBurned;

    // Update the insurance
    if (position.rawInsuredBalance > position.rawDebtBalance) {
        position.rawInsuredBalance = position.rawDebtBalance;
    }
    if (position.rawDebtBalance == 0) {
        position.rawInsuredBalance = 0;
        position.isInsured = false;
    }

    // Update liquidity and interest rates
    ReserveLibrary.updateInterestRatesAndLiquidity(reserve, rateData, actualRepayAmount, 0);

    // If a residual remains below MIN_BORROW_AMOUNT, revert
    if (position.rawDebtBalance > 0 && getPositionScaledDebt(adapter, onBehalfOf, data) < MIN_BORROW_AMOUNT) {
        revert ResidualCannotBeRepaid();
    }

    _rebalanceLiquidity();

    emit Repay(msg.sender, onBehalfOf, actualRepayAmount);
}

repayOnBehalf

repayOnBehalf(address adapter, bytes data, uint256 amount, address onBehalfOf)

Summary
Third-party repayment of a borrower’s debt for a specific collateral position. Forwards to the internal _repay flow after performing feature-flag and minimum-amount checks.

Minimum Repayment Requirement

When repaying on behalf of another user, at least 1% of the borrower's scaled debt must be repaid in a single transaction.
Attempting to repay less than 1% will revert with RepaymentBelowMinimumThreshold().
To repay the full debt (and avoid dust), pass type(uint256).max as the amount parameter.

Parameters

Name Type Description
adapter address Registered IAssetAdapter for the collateral position
data bytes Adapter-specific calldata identifying the position
amount uint256 Repayment amount (use type(uint256).max to repay full scaled debt; see Notes)
onBehalfOf address Borrower whose debt is being repaid

Emits
Repay(address payer, address onBehalfOf, uint256 actualRepayAmount) (emitted inside _repay)

Reverts (selected)

  • PaybackDebtDisabled() — feature flag disabled
  • AddressCannotBeZero()onBehalfOf is zero
  • RepaymentBelowMinimumThreshold() — when msg.sender != onBehalfOf and amount < 1% of scaled debt
  • Plus all _repay errors (e.g., liquidation/grace-period rules, allowance/balance, residual below MIN_BORROW_AMOUNT)

Notes

  • To repay the entire outstanding scaled debt and avoid dust, pass type(uint256).max as amount.
  • When the payer is not the borrower, the repayment must be ≥ 1% of the borrower’s scaled debt (anti-griefing).
  • If a small residual remains below MIN_BORROW_AMOUNT, the call will revert; repay in full to avoid this.
  • Ensure the payer has approved and holds enough reserve asset for the transfer.
Example (TypeScript / ethers)
// Third-party repay >= 1% of scaled debt
const scaled = await lendingPool.getPositionScaledDebt(adapter, borrower, data);
const onePercent = scaled.mul(1).div(100);
await reserveAsset.approve(lendingPool.target, onePercent);
await (await lendingPool.repayOnBehalf(adapter, data, onePercent, borrower)).wait();

// Full payoff (no dust)
await reserveAsset.approve(lendingPool.target, maxExpected);
await (await lendingPool.repayOnBehalf(
  adapter, data, ethers.constants.MaxUint256, borrower
)).wait();
Source code
function repayOnBehalf(address adapter, bytes calldata data, uint256 amount, address onBehalfOf)
    external
    nonReentrant
    whenNotPaused
    onlyValidAmount(amount)
    onlySupportedAdapter(adapter)
    notBlacklisted(msg.sender)
    notBlacklisted(onBehalfOf)
{
    if (!parameters.canPaybackDebt) revert PaybackDebtDisabled();
    if (onBehalfOf == address(0)) revert AddressCannotBeZero();

    uint256 positionScaledDebt = getPositionScaledDebt(adapter, onBehalfOf, data);
    if (positionScaledDebt != 0 && msg.sender != onBehalfOf) {
        uint256 minRepaymentAmount = positionScaledDebt.percentMul(100); // 1%
        if (amount < minRepaymentAmount) revert RepaymentBelowMinimumThreshold();
    }

    _repay(adapter, data, amount, onBehalfOf);
}

Liquidation Management

initiateLiquidation

initiateLiquidation(address adapter, address user, bytes data)

Summary
Starts liquidation for a position whose health factor is below the liquidation threshold.
Validates the position, accrues indices, checks HF, then flags the position under liquidation and records the start time.

Note

The contract implementing this function is a proxy and resides in LiquidationProxy.sol.

Parameters

Name Type Description
adapter address Registered IAssetAdapter for the collateral
user address Position owner
data bytes Adapter-specific calldata identifying the position

Emits
- LiquidationInitiated(address indexed sender, address indexed user, address adapter, bytes data)

Reverts (selected)

  • UserAlreadyUnderLiquidation() — position already flagged
  • HealthFactorTooHigh() — health factor ≥ HEALTH_FACTOR_LIQUIDATION_THRESHOLD
  • Adapter validation failures

Notes

  • Health factor is computed via calculateHealthFactorProxy (delegates to LendingPool’s calculateHealthFactor).
  • Index accrual occurs before checks via ReserveLibrary.updateReserveState.
Example (TypeScript / ethers)
const adapterAddr = "0xAdapter...";
const borrower = "0xBorrower...";
const adapterData = ethers.AbiCoder.defaultAbiCoder().encode(
  ["uint256"],
  [tokenId] // e.g. for an ERC721 collateral adapter
);

const tx = await lendingPool.initiateLiquidation(adapterAddr, borrower, adapterData);
await tx.wait();
Source code
function initiateLiquidation(address adapter, address user, bytes calldata data) external onlyProxy {
    bytes32 positionKey = IAssetAdapter(adapter).getPositionKey(data);
    CollateralPosition storage position = positions[user][positionKey];
    if (position.isUnderLiquidation) revert UserAlreadyUnderLiquidation();

    IAssetAdapter(adapter).validate(user, data);

    // update state
    ReserveLibrary.updateReserveState(reserve, rateData);

    uint256 healthFactor = calculateHealthFactorProxy(adapter, user, data);

    if (healthFactor >= HEALTH_FACTOR_LIQUIDATION_THRESHOLD) revert HealthFactorTooHigh();

    position.isUnderLiquidation = true;
    position.liquidationStartTime = block.timestamp;

    emit LiquidationInitiated(msg.sender, user, adapter, data);
}

closeLiquidation

closeLiquidation(address adapter, bytes data)

Summary
Closes an active liquidation for the caller’s position.
Validates the position, enforces grace-period and insurance rules, accrues indices, confirms debt is fully repaid, then clears the liquidation flags and insurance state.

Parameters

Name Type Description
adapter address Registered IAssetAdapter for the collateral
data bytes Adapter-specific calldata identifying the position

Emits
- LiquidationClosed(address indexed user, address adapter, bytes data)

Reverts (selected)

  • NotUnderLiquidation() — position isn’t flagged under liquidation
  • GracePeriodExpired() — grace period elapsed and debt is still > 0
  • NotInsured() — attempting to close with nonzero debt and no insurance
  • DebtNotZero() — after accrual, outstanding debt is not fully repaid
  • Adapter validation failures

Notes

  • If insured, the borrower must fully repay within liquidationGracePeriod to close.
  • The proxy permits closing after the grace period only when debt is already zero.
  • On success, liquidation flags are cleared and insurance is reset.
Example (TypeScript / ethers)

const adapterAddr = "0xAdapter..."; const data = ethers.AbiCoder.defaultAbiCoder().encode(["uint256"], [tokenId]);

const tx = await lendingPool.closeLiquidation(adapterAddr, data); await tx.wait(); ```

Source code
function closeLiquidation(address adapter, bytes calldata data) external onlyProxy {
    address userAddress = msg.sender;
    bytes32 positionKey = IAssetAdapter(adapter).getPositionKey(data);
    CollateralPosition storage position = positions[userAddress][positionKey];

    if (!position.isUnderLiquidation) revert NotUnderLiquidation();

    IAssetAdapter(adapter).validate(msg.sender, data);

    uint256 positionDebt = getPositionScaledDebtProxy(adapter, userAddress, data);
    if (block.timestamp > position.liquidationStartTime + parameters.liquidationGracePeriod && positionDebt > 0) {
        revert GracePeriodExpired();
    }

    // Closing is only allowed with insurance if debt is nonzero
    if (!position.isInsured && positionDebt > 0) revert NotInsured();

    // Accrue indices; after accrual, debt must be zero
    ReserveLibrary.updateReserveState(reserve, rateData);
    if (positionDebt > 0) revert DebtNotZero();

    position.isUnderLiquidation = false;
    position.liquidationStartTime = 0;
    position.rawInsuredBalance = 0;
    position.isInsured = false;

    emit LiquidationClosed(userAddress, adapter, data);
}

finalizeLiquidation

finalizeLiquidation(address adapter, address user, bytes data)

Summary
Completes the liquidation process by transferring collateral to the Stability Pool and burning the user’s debt.
Enforces that the grace period has expired (for insured positions) and ensures there is still outstanding debt.

Restricted Access

This method is only callable by the Stability Pool through the proxy when a position must be liquidated.
If the debt is already zero, the liquidation cannot be finalized here. In that case, the borrower (owner) should close the liquidation instead.

Parameters

Name Type Description
adapter address Registered IAssetAdapter for the collateral
user address The borrower whose position is being liquidated
data bytes Adapter-specific calldata identifying the position

Emits
- LiquidationFinalized(address stabilityPool, address user, address adapter, bytes data, uint256 debtCleared, uint256 collateralValue)

Reverts (selected)

  • NotUnderLiquidation() — position isn’t marked for liquidation
  • GracePeriodNotExpired() — insured position still within grace period
  • Adapter validation failures
  • Stability Pool checks debt > 0 before execution

Notes

  • Debt must exist for liquidation to be finalized.
  • After execution, the user’s collateral is transferred to the Stability Pool and the debt is fully cleared.
  • Insurance flags and balances are reset to zero.
Example (TypeScript / ethers)
const adapterAddr = "0xAdapter...";
const userAddr = "0xBorrower...";
const data = ethers.AbiCoder.defaultAbiCoder().encode(["uint256"], [tokenId]);
const tx = await lendingPool.finalizeLiquidation(adapterAddr, userAddr, data);
await tx.wait();
Source code
function finalizeLiquidation(address adapter, address user, bytes calldata data) external onlyProxy {
    bytes32 positionKey = IAssetAdapter(adapter).getPositionKey(data);
    CollateralPosition storage position = positions[user][positionKey];
    if (!position.isUnderLiquidation) revert NotUnderLiquidation();

    IAssetAdapter(adapter).validate(user, data);

    // update state
    ReserveLibrary.updateReserveState(reserve, rateData);

    if (position.isInsured && block.timestamp <= position.liquidationStartTime + parameters.liquidationGracePeriod) {
        revert GracePeriodNotExpired();
    }

    uint256 positionDebt = getPositionScaledDebtProxy(adapter, user, data);

    position.isUnderLiquidation = false;
    position.liquidationStartTime = 0;
    position.isInsured = false;

    IAssetAdapter(adapter).transferAsset(user, data, stabilityPool);

    IDebtToken(reserve.reserveDebtTokenAddress).burn(
        user,
        positionDebt,
        reserve.usageIndex,
        abi.encode(adapter, data)
    );

    position.positionIndex = reserve.usageIndex;
    position.rawDebtBalance = 0;
    position.rawInsuredBalance = 0;

    ReserveLibrary.updateInterestRatesAndLiquidity(reserve, rateData, positionDebt, 0);

    emit LiquidationFinalized(
        stabilityPool,
        user,
        adapter,
        data,
        positionDebt,
        IAssetAdapter(adapter).getAssetValue(user, data)
    );
}

Positions

calculateHealthFactor

calculateHealthFactor(address adapter, address user, bytes data) → uint256

Summary
Returns the position’s health factor (RAY). Uses current collateral value and scaled debt with the protocol’s liquidationThreshold.

Parameters

Name Type Description
adapter address Registered IAssetAdapter
user address Position owner
data bytes Adapter-specific identifier

Returns

uint256 — Health factor (RAY, 1e27). If scaled debt is negligible, returns WadRayMath.RAY.

Notes

  • HF ≥ 1e18 implies safe; HF < 1e18 implies liquidatable per thresholding logic (exact threshold governed by parameters).
  • Collateral value from IAssetAdapter.getAssetValue(user, data).
Example (TypeScript / ethers)
const hf = await lendingPool.calculateHealthFactor(adapter, user, data);
console.log("HF:", hf.toString());

getUserCollateralValue

getUserCollateralValue(address user) → uint256

Summary
Sums the user’s total collateral value across all registered adapters.

Parameters

Name Type Description
user address User address

Returns

uint256 — Total collateral value.

Notes

  • Iterates adapters[] and calls IAssetAdapter(adapter).getTotalAssetValue(user) per adapter.
Example (TypeScript / ethers)
const total = await lendingPool.getUserCollateralValue(user);
console.log("Total collateral value:", total.toString());

getPositionDebt

getPositionDebt(address adapter, address user, bytes data) → uint256

Summary
Returns raw debt for a position without interest accrual (stored balance).

Parameters

Name Type Description
adapter address Registered IAssetAdapter
user address Position owner
data bytes Position identifier

Returns

uint256 — Raw debt balance.

Notes

  • For interest-inclusive debt, use getPositionScaledDebt.
Example (TypeScript / ethers)
const raw = await lendingPool.getPositionDebt(adapter, user, data);
console.log("Raw debt:", raw.toString());

getPositionScaledDebt

getPositionScaledDebt(address adapter, address user, bytes data) → uint256

Summary
Returns scaled debt (includes interest via index) for a specific position.

Parameters

Name Type Description
adapter address Registered IAssetAdapter
user address Position owner
data bytes Position identifier

Returns

uint256 — Scaled debt (interest-aware).

Notes

  • Internally derives from _positionScaledDebt(position).
Example (TypeScript / ethers)
const scaled = await lendingPool.getPositionScaledDebt(adapter, user, data);
console.log("Scaled debt:", scaled.toString());

getPositionsScaledDebt

getPositionsScaledDebt(address user) → uint256

Summary
Aggregates the user’s total scaled debt across all positions on all adapters.

Parameters

Name Type Description
user address User address

Returns

uint256 — Total scaled debt.

Notes

  • Iterates adapters[], fetches getPositionKeys(user), sums _positionScaledDebt for each key.
Example (TypeScript / ethers)
const totalScaled = await lendingPool.getPositionsScaledDebt(user);
console.log("Total scaled debt:", totalScaled.toString());

getPosition

getPosition(address adapter, address user, bytes data) → CollateralPosition

Summary
Returns the stored CollateralPosition struct for the specified position.

Parameters

Name Type Description
adapter address Registered IAssetAdapter
user address Position owner
data bytes Position identifier

Returns

CollateralPosition — Current stored fields for the position.

Notes

  • Keyed by positions[user][IAssetAdapter(adapter).getPositionKey(data)].
Example (TypeScript / ethers)
const pos = await lendingPool.getPosition(adapter, user, data);
console.log("Position:", pos);

getPositionData

getPositionData(address adapter, address user, bytes data) → (uint256 positionIndex, uint256 rawDebtBalance)

Summary
Returns a compact view of two key fields of a position.

Parameters

Name Type Description
adapter address Registered IAssetAdapter
user address Position owner
data bytes Position identifier

Returns

  • uint256 positionIndex
  • uint256 rawDebtBalance

Notes

  • For a fuller struct, use getPosition or getPositionView.
Example (TypeScript / ethers)
const [positionIndex, rawDebt] = await lendingPool.getPositionData(adapter, user, data);
console.log({ positionIndex: positionIndex.toString(), rawDebt: rawDebt.toString() });

getPositionView

getPositionView(address adapter, address user, bytes data) → CollateralPositionView

Summary
Returns a synthesized view struct for a position, including flags, balances, indices, scaled debt, health factor, adapter and asset addresses.

Parameters

Name Type Description
adapter address Registered IAssetAdapter
user address Position owner
data bytes Position identifier

Returns
CollateralPositionView with:

  • isUnderLiquidation, liquidationStartTime, rawDebtBalance, isInsured
  • scaledDebtBalance (via getPositionScaledDebt)
  • healthFactor (via calculateHealthFactor)
  • positionIndex, adapter, asset

Notes

  • Useful for UIs needing a single call to render the full state.
Example (TypeScript / ethers)
const view = await lendingPool.getPositionView(adapter, user, data);
console.log("Position view:", view);

Indexes

getNormalizedIncome

getNormalizedIncome() → uint256

Summary
Returns the reserve’s normalized income (liquidity index).

Returns

uint256 — Liquidity index.

Notes

  • Read-only wrapper over ReserveLibrary.getLiquidityIndex(reserve, rateData).
Example (TypeScript / ethers)
const income = await lendingPool.getNormalizedIncome();
console.log("Liquidity index:", income.toString());

getNormalizedDebt

getNormalizedDebt() → uint256

Summary
Returns the reserve’s normalized debt (usage index).

Returns

uint256 — Usage index.

Notes

  • Read-only wrapper over ReserveLibrary.getUsageIndex(reserve, rateData).
Example (TypeScript / ethers)
const usage = await lendingPool.getNormalizedDebt();
console.log("Usage index:", usage.toString());

getReserveState

getReserveState() → ReserveData

Summary
Returns the reserve’s data that includes the indexes and usages.

Returns
ReserveData with:

  • reserveRTokenAddress, reserveAssetAddress, reserveDebtTokenAddress
  • totalLiquidity, totalUsage,
  • liquidityIndex, usageIndex
  • lastUpdateTimestamp, pendingProtocolFeeAmount

Important: Correct Source for Total Usage (Total Debt)

To have an up-to-date value for total usage (i.e., the total debt in the system including accrued interest), do not use reserve.totalUsage.
Instead, use DebtToken.totalSupply.
The reserve.totalUsage field only reflects the total supply of the debt token in circulation at the last update and does not include accrued interest since then.

Example (TypeScript / ethers)
const state = await lendingPool.getReserveState();