Borrow
Whitelisted Operators in Cap can borrow and repay the underlying Vault assets, provided they have secured sufficient collateral from a Delegator.
Mechanics
Borrow
The borrow process flow is as follows:
Operator calls the borrow function on the Lender contract
Restaker interest is realized first to prevent charging for compounded interest
If eligible to borrow*, assets are lent out from the Vault to the Opeartor
Debt tokens are minted to track the loan
Key Validations*:
Operator must have sufficient collateral and health factor
Operator must be whitelisted and not paused
Asset being borrowed should not be paused
Borrow amount must meet minimum requirements, and may not exceed maximum borrows
Minimum: set by Admin via the setMinBorrow function
Maximum: The smaller value of the Operator's remaining borrowable capacity, and the remaining available amount to be borrowed from the Vault
Repay
The repay process flow is as follows:
Operator calls the repay function on the Lender contract
Restaker interest is realized first to ensure all interest is accounted for
The repayment is processed in the following order:
Unrealized restaker interest (if any)
Vault principal debt
Vault interest
Debt tokens corresponding to the total amount repaid are burned
Interest Distribution
The repaid assets are distributed back to the shareholders:
Vault Principal: Sent to Vault contract's reserves
Vault Interest: Sent to
interestReceiver
(set to FeeAuction). Any excess interest will go to the interest receiverRestaker Interest: Sent to Delegation contract for distribution to Delegators
Unrealized Interest: Added to agent's debt balance for future repayment
Realizing Interest
Interest in Cap is accrued continuously but realized discretely:
Accrued Interest: Calculated in real-time based on elapsed time and rates
Realized Interest: Actually borrowed from vault and distributed to recipients
Unrealized Interest: Accrued but not yet realized due to vault liquidity constraints
As can be seen, interest can be realized prior to repayment in Cap. Both stcUSD holders and Delegators may permissionlessly realize interest by borrowing from the vault and distributing to interest receivers.
There are two functions to realize interest: realizeInterest and realizeRestakerInterest
RealizeInterest:
Realizes vault interest (interest paid to the stcUSD holders)
Interest is borrowed from the Vault and paid to the interest receiver
RealizeRestakerInterest:
Realizes restaker interest (interest paid to Delegators)
Interest is borrowed from the Vault and paid to the Delegation Contract
For both functions, the process flow is as follows:
Determine available interest to realize
Increase reserve debt
Borrow interest amount from Vault
Transfer assets to recipient
Architecture
Borrow Logic
The BorrowLogic library is the core component responsible for managing borrowing and repayment operations in the CAP lending system.
Key Dependencies:
IDebtToken: Manages debt token minting/burning
IDelegation: Handles restaker interest distribution
IVault: Manages asset storage and transfers
ValidationLogic: Validates borrowing operations
ViewLogic: Calculates borrowing capacity and health metrics
AgentConfiguration: Tracks agent borrowing status per reserve
Debt Token
Debt tokens are core to accounting an Operator's loan. Debt tokens are non-transferrable ERC20 tokens minted when an Operator borrows, and burned when the debt is repayed.
interface IDebtToken {
function mint(address to, uint256 amount) external;
function burn(address from, uint256 amount) external;
function balanceOf(address account) external view returns (uint256);
}
Vault
Assets are transferred from/to the Vault once validation checks and interest calculations are done in the Lender.
interface IVault {
function borrow(address _asset, uint256 _amount, address _receiver) external;
function repay(address _asset, uint256 _amount) external;
function availableBalance(address _asset) external view returns (uint256);
}
Key Functions
borrow
: Borrow an asset
function borrow(ILender.LenderStorage storage $, ILender.BorrowParams memory params)
external
returns (uint256 borrowed)
where the borrow params are:
struct BorrowParams {
address agent; // Borrower address
address asset; // Asset to borrow
uint256 amount; // Amount to borrow
address receiver; // Receiver of borrowed assets
bool maxBorrow; // Whether to borrow maximum available
}
Process Flow:
Interest Realization:
realizeRestakerInterest($, params.agent, params.asset);
Amount Calculation:
if (params.maxBorrow) { params.amount = ViewLogic.maxBorrowable($, params.agent, params.asset); }
Validation:
ValidationLogic.validateBorrow($, params);
State Updates:
IDelegation($.delegation).setLastBorrow(params.agent); $.agentConfig[params.agent].setBorrowing(reserve.id, true);
Asset Transfer:
IVault(reserve.vault).borrow(params.asset, borrowed, params.receiver); IDebtToken(reserve.debtToken).mint(params.agent, borrowed); reserve.debt += borrowed;
repay
: Repay an asset
function repay(ILender.LenderStorage storage $, ILender.RepayParams memory params)
external
returns (uint256 repaid)
where the repay params are:
struct RepayParams {
address agent; // Agent to repay for
address asset; // Asset to repay
uint256 amount; // Amount to repay
address caller; // Address making the repayment
}
Process Flow:
Interest Realization:
realizeRestakerInterest($, params.agent, params.asset);
Amount Calculation: prevents repaying more than owned
uint256 agentDebt = IERC20(reserve.debtToken).balanceOf(params.agent); repaid = Math.min(params.amount, agentDebt);
Minimum Debt Protection:
if (remainingDebt > 0 && remainingDebt < reserve.minBorrow) { repaid = agentDebt - reserve.minBorrow; }
Repayment Allocation:
// Calculate interest repayment if (repaid > reserve.unrealizedInterest[params.agent] + reserve.debt) { interestRepaid = repaid - (reserve.debt + reserve.unrealizedInterest[params.agent]); } // Calculate restaker repayment if (remaining > reserve.unrealizedInterest[params.agent]) { restakerRepaid = reserve.unrealizedInterest[params.agent]; } // Calculate vault repayment uint256 vaultRepaid = Math.min(remaining, reserve.debt);
Asset Distribution:
// Send to restakers IERC20(params.asset).safeTransfer($.delegation, restakerRepaid); IDelegation($.delegation).distributeRewards(params.agent, params.asset); // Send to vault IVault(reserve.vault).repay(params.asset, vaultRepaid); // Send to interest receiver IERC20(params.asset).safeTransfer(reserve.interestReceiver, interestRepaid);
Debt Token Burning:
IDebtToken(reserve.debtToken).burn(params.agent, repaid);
realizeInterest
: Realize interest for an asset
function realizeInterest(ILender.LenderStorage storage $, address _asset)
external
returns (uint256 realizedInterest)
realizeRestakerInterest
: Realize interest for restaker debt of an agent for an asset
function realizeRestakerInterest(ILender.LenderStorage storage $, address _agent, address _asset)
public
returns (uint256 realizedInterest)
Unrealized interest support: Can track interest that couldn't be realized due to liquidity constraintss
Admin Functions
setInterestReceiver
: Set Interest Receiver for an asset
function setInterestReceiver(address _asset, address _interestReceiver) external checkAccess(this.setInterestReceiver.selector)
setMinBorrow
: Set minimum borrow amount for an asset
function setMinBorrow(address _asset, uint256 _minBorrow) external checkAccess(this.setMinBorrow.selector)
View Functions
accruedRestakerInterest
: Accrued Restaker Interest
function accruedRestakerInterest(address _agent, address _asset) external view returns (uint256 accruedInterest)
unrealizedInterest
: Unrealized Restaker Interest
function unrealizedInterest(address _agent, address _asset) external view returns (uint256 _unrealizedInterest)
maxRealization
: Calculates the maximum vault interest that can be realized for an asset
function maxRealization(address _asset) external view returns (uint256 _maxRealization)
maxRestakerRealization
: Calculates the maximum restaker interest that can be realized for an agent
function maxRestakerRealization(address _agent, address _asset)
external
view
returns (uint256 newRealizedInterest, uint256 newUnrealizedInterest)
newRealizedInterest
: Maximum realizable interestnewUnrealizedInterest
: Interest that will become unrealized
Last updated