Overview
Reserves represent individual token markets within a lending market. Each reserve has its own liquidity pool, interest rate model, and risk parameters. This guide covers reserve initialization and configuration.
Initializing a Reserve
Use the init_reserve instruction to create a new reserve for a token.
Accounts Required
Key accounts from handler_init_reserve.rs:116-184:
pub struct InitReserve<'info> {
#[account(mut)]
pub signer: Signer<'info>,
pub lending_market: AccountLoader<'info, LendingMarket>,
pub lending_market_authority: AccountInfo<'info>,
#[account(zero)]
pub reserve: AccountLoader<'info, Reserve>,
pub reserve_liquidity_mint: InterfaceAccount<'info, Mint>,
pub reserve_liquidity_supply: AccountInfo<'info>, // PDA
pub fee_receiver: AccountInfo<'info>, // PDA
#[account(init)] // Created by instruction
pub reserve_collateral_mint: InterfaceAccount<'info, Mint>,
#[account(init)]
pub reserve_collateral_supply: InterfaceAccount<'info, TokenAccount>,
#[account(mut)]
pub initial_liquidity_source: InterfaceAccount<'info, TokenAccount>,
// Programs and sysvars
pub rent: Sysvar<'info, Rent>,
pub liquidity_token_program: Interface<'info, TokenInterface>,
pub collateral_token_program: Program<'info, Token>,
pub system_program: Program<'info, System>,
}
Initial Deposit Requirement
Reserves require an initial deposit to prevent price manipulation:
let min_initial_deposit_amount = market.min_initial_deposit_amount; // Default: 1000 base units
The signer must provide this initial liquidity from initial_liquidity_source. This liquidity is locked forever to maintain the exchange rate.
See handler_init_reserve.rs:72-76 and handler_init_reserve.rs:102-110.
Initial Configuration
New reserves start with minimal configuration:
ReserveConfig {
status: ReserveStatus::Hidden, // Not visible to users
..Default::default()
}
All other parameters (LTV, borrow limits, fees, etc.) default to zero and must be configured before activation.
See handler_init_reserve.rs:95-98.
Reserve Configuration Structure
The ReserveConfig struct contains all configurable parameters. From state/reserve.rs:1313-1426:
pub struct ReserveConfig {
// Status
pub status: u8, // Active, Obsolete, or Hidden
// Interest rates
pub host_fixed_interest_rate_bps: u16,
pub borrow_rate_curve: BorrowRateCurve,
// Risk parameters
pub loan_to_value_pct: u8,
pub liquidation_threshold_pct: u8,
pub min_liquidation_bonus_bps: u16,
pub max_liquidation_bonus_bps: u16,
pub bad_debt_liquidation_bonus_bps: u16,
// Fees
pub protocol_take_rate_pct: u8,
pub protocol_liquidation_fee_pct: u8,
pub fees: ReserveFees,
// Limits
pub deposit_limit: u64,
pub borrow_limit: u64,
pub borrow_factor_pct: u64,
pub borrow_limit_outside_elevation_group: u64,
pub borrow_limit_against_this_collateral_in_elevation_group: [u64; 32],
// Withdrawal caps
pub deposit_withdrawal_cap: WithdrawalCaps,
pub debt_withdrawal_cap: WithdrawalCaps,
// Token info and oracles
pub token_info: TokenInfo,
// Deleveraging
pub deleveraging_margin_call_period_secs: u64,
pub deleveraging_threshold_decrease_bps_per_day: u64,
pub deleveraging_bonus_increase_bps_per_day: u64,
pub min_deleveraging_bonus_bps: u16,
pub autodeleverage_enabled: u8,
// Elevation groups
pub elevation_groups: [u8; 20],
pub disable_usage_as_coll_outside_emode: u8,
// Advanced features
pub utilization_limit_block_borrowing_above_pct: u8,
pub block_ctoken_usage: u8,
pub proposer_authority_locked: u8,
pub protocol_order_execution_fee_pct: u8,
// Debt maturity
pub debt_maturity_timestamp: u64,
pub debt_term_seconds: u64,
}
Updating Reserve Configuration
Use the update_reserve_config instruction to modify reserve parameters.
Update Modes
The UpdateConfigMode enum defines all updatable parameters. From state/mod.rs:97-153:
pub enum UpdateConfigMode {
UpdateLoanToValuePct = 1,
UpdateMaxLiquidationBonusBps = 2,
UpdateLiquidationThresholdPct = 3,
UpdateProtocolLiquidationFee = 4,
UpdateProtocolTakeRate = 5,
UpdateFeesOriginationFee = 6,
UpdateFeesFlashLoanFee = 7,
UpdateDepositLimit = 9,
UpdateBorrowLimit = 10,
UpdateBorrowRateCurve = 24,
UpdateBorrowFactor = 33,
UpdateElevationGroup = 35,
UpdateReserveStatus = 39,
// ... and many more
}
Common Configuration Updates
Risk Parameters
Loan-to-Value (LTV) - Maximum borrow power:
await program.methods
.updateReserveConfig(
UpdateConfigMode.UpdateLoanToValuePct,
encodeU8(75), // 75%
false // don't skip validation
)
.accounts({ reserve: reservePubkey })
.rpc();
Liquidation Threshold - LTV at which liquidation becomes possible:
await program.methods
.updateReserveConfig(
UpdateConfigMode.UpdateLiquidationThresholdPct,
encodeU8(80), // 80%
false
)
.rpc();
Liquidation threshold must be greater than LTV to provide a safety buffer.
Liquidation Bonuses - Liquidator incentives:
// Min bonus (for large, liquid positions)
await program.methods
.updateReserveConfig(
UpdateConfigMode.UpdateMinLiquidationBonusBps,
encodeU16(250), // 2.5%
false
)
.rpc();
// Max bonus (for small, illiquid positions)
await program.methods
.updateReserveConfig(
UpdateConfigMode.UpdateMaxLiquidationBonusBps,
encodeU16(500), // 5%
false
)
.rpc();
// Bad debt bonus (for insolvent positions)
await program.methods
.updateReserveConfig(
UpdateConfigMode.UpdateBadDebtLiquidationBonusBps,
encodeU16(1000), // 10%
false
)
.rpc();
Borrow Rate Curve
Define the interest rate model:
const borrowRateCurve = {
points: [
{
utilization_rate_bps: 0, // 0% utilization
borrow_rate_bps: 0, // 0% APR
},
{
utilization_rate_bps: 7000, // 70% utilization
borrow_rate_bps: 500, // 5% APR
},
{
utilization_rate_bps: 9000, // 90% utilization
borrow_rate_bps: 2000, // 20% APR
},
{
utilization_rate_bps: 10000, // 100% utilization
borrow_rate_bps: 5000, // 50% APR
},
],
};
await program.methods
.updateReserveConfig(
UpdateConfigMode.UpdateBorrowRateCurve,
encodeBorrowRateCurve(borrowRateCurve),
false
)
.rpc();
The curve is linearly interpolated between points. See utils/borrow_rate_curve.rs for implementation.
Deposit and Borrow Limits
Deposit Limit - Maximum total deposits:
await program.methods
.updateReserveConfig(
UpdateConfigMode.UpdateDepositLimit,
encodeU64(1_000_000_000_000), // 1M tokens (with 6 decimals)
false
)
.rpc();
Borrow Limit - Maximum total borrows:
await program.methods
.updateReserveConfig(
UpdateConfigMode.UpdateBorrowLimit,
encodeU64(500_000_000_000), // 500K tokens
false
)
.rpc();
Setting both limits to 0 blocks all usage (see state/reserve.rs:755-757).
Borrow Factor - Multiplier applied to borrowed value for risk calculations:
await program.methods
.updateReserveConfig(
UpdateConfigMode.UpdateBorrowFactor,
encodeU64(120), // 120% - borrows count as 1.2x their value
false
)
.rpc();
Borrow factor ≥ 100%. Higher values make borrowing more expensive in terms of collateral requirements.
Fees
Origination Fee - Fee charged on borrows:
// 0.1% origination fee
const originationFee = new Fraction(1, 1000); // 0.001
await program.methods
.updateReserveConfig(
UpdateConfigMode.UpdateFeesOriginationFee,
encodeFraction(originationFee),
false
)
.rpc();
Flash Loan Fee - Fee charged on flash loans:
// 0.01% flash loan fee
const flashLoanFee = new Fraction(1, 10000);
await program.methods
.updateReserveConfig(
UpdateConfigMode.UpdateFeesFlashLoanFee,
encodeFraction(flashLoanFee),
false
)
.rpc();
Set to u64::MAX to disable flash loans.
Protocol Take Rate - Percentage of interest going to protocol:
await program.methods
.updateReserveConfig(
UpdateConfigMode.UpdateProtocolTakeRate,
encodeU8(10), // 10% of interest
false
)
.rpc();
Protocol Liquidation Fee - Protocol’s share of liquidation bonus:
await program.methods
.updateReserveConfig(
UpdateConfigMode.UpdateProtocolLiquidationFee,
encodeU8(5), // 5% of liquidation bonus
false
)
.rpc();
Withdrawal Caps
Limit withdrawal rate to prevent bank runs:
// Limit deposits/withdrawals to 100K tokens per hour
await program.methods
.updateReserveConfig(
UpdateConfigMode.UpdateDepositWithdrawalCap,
encodeWithdrawalCap(
100_000_000_000, // capacity: 100K tokens
3600 // interval: 1 hour (in seconds)
),
false
)
.rpc();
// Limit debt repayment/borrowing to 50K tokens per hour
await program.methods
.updateReserveConfig(
UpdateConfigMode.UpdateDebtWithdrawalCap,
encodeWithdrawalCap(50_000_000_000, 3600),
false
)
.rpc();
Reserve Status
Control reserve visibility and usage:
enum ReserveStatus {
Active = 0, // Fully operational
Obsolete = 1, // Deprecated, users should withdraw
Hidden = 2, // Not visible in UI, but functional
}
await program.methods
.updateReserveConfig(
UpdateConfigMode.UpdateReserveStatus,
encodeU8(ReserveStatus.Active),
false
)
.rpc();
Authorization
Reserve configuration updates require one of:
- Market owner - Full control over all parameters
- Reserve proposer authority - Limited permissions (if not locked)
- Global admin - Can update specific parameters even in immutable markets
See handler_update_reserve_config.rs:76-90 for authorization logic.
Configuration Validation
By default, all configuration updates are validated for safety (see handler_update_reserve_config.rs:48-61):
- LTV < liquidation threshold
- Liquidation bonus within reasonable bounds
- Borrow rate curve is monotonic
- Oracle configuration is valid
- Elevation group membership is consistent
Validation can be skipped for emergency situations, but only if the reserve is unused and blocked:
await program.methods
.updateReserveConfig(
mode,
value,
true // skip validation - DANGEROUS!
)
.rpc();
Skipping validation can create invalid states. Only use when absolutely necessary and the reserve is completely unused.
Activating a Reserve
After initialization and configuration:
- Configure all risk parameters (LTV, liquidation thresholds)
- Set borrow rate curve
- Configure fees and limits
- Set oracle feeds via token info
- Change status from Hidden to Active
- Optionally seed initial liquidity
Best Practices
Risk Management
- Conservative Start: Begin with low LTV (50-60%) and high liquidation bonuses
- Gradual Increases: Increase LTV and limits slowly based on market performance
- Safety Buffers: Maintain 5-10% gap between LTV and liquidation threshold
- Stress Test: Test extreme market conditions before going live
Interest Rates
- Target Utilization: Design curves for 70-80% target utilization
- Steep at High Usage: Rapidly increase rates above 90% utilization
- Market-Based: Research competitor rates and DeFi lending norms
- Monitor: Adjust based on actual utilization patterns
Limits and Caps
- Initial Conservatism: Start with lower limits for new/volatile tokens
- Liquidity-Based: Scale limits to token’s on-chain liquidity
- Withdrawal Caps: Use for volatile tokens to prevent rapid exits
- Regular Review: Update limits as market conditions change
Oracle Configuration
- Multiple Sources: Use multiple oracle feeds when possible
- Staleness Checks: Set appropriate max age for price updates
- TWAP: Enable TWAP for manipulation resistance
- Test Thoroughly: Verify price feeds before activation
Elevation Groups
- Isolated Risk: Use for high-risk assets or leverage strategies
- Clear Debt Asset: Ensure debt asset is stable and liquid
- Conservative Params: Lower LTVs for elevation group assets
- Collateral Limits: Restrict max collateral types to manage complexity
Monitoring Configuration
Regularly review:
- Utilization rates vs target
- Liquidation frequency and bonuses
- Protocol fee collection
- Limit hits (deposit/borrow caps)
- Oracle price staleness
- Withdrawal cap usage
See Monitoring for detailed metrics.