Expand description
§klend-interface
Instruction builders and zero-copy account deserialization for
Kamino Lending (Klend). No anchor-lang dependency;
targets solana-sdk v2.x.
§Quick start
Add to your Cargo.toml:
[dependencies]
klend-interface = { git = "https://github.com/Kamino-Finance/klend" }
solana-pubkey = "2.1"
solana-instruction = "2.1"
solana-sdk = "~2.3"
solana-client = "2.1"
spl-associated-token-account = "6"§Two-level API
The crate provides two levels of instruction building:
-
Low-level (
instructions): one function per Klend instruction, returning a singlesolana_instruction::Instruction. You supply every account address manually. -
High-level (
helpers): workflow builders that returnVec<Instruction>with requiredrefresh_reserve/refresh_obligationinstructions prepended automatically. UsesReserveInfoandObligationContextto auto-derive PDAs.
§Core types
| Type | Purpose |
|---|---|
ReserveInfo | On-chain reserve metadata; built via ReserveInfo::from_account_data. |
ObligationContext | Bundles an obligation with all its reserves; provides .deposit(), .borrow(), .repay(), .withdraw(). |
state::Reserve | Zero-copy deserialization of the on-chain Reserve account (8616 bytes). |
state::Obligation | Zero-copy deserialization of the on-chain Obligation account (3336 bytes). |
state::LendingMarket | Zero-copy deserialization of the on-chain LendingMarket account (4656 bytes). |
§Typical flow with ObligationContext
use klend_interface::{ObligationContext, pda, KLEND_PROGRAM_ID};
use solana_pubkey::Pubkey;
let lending_market: Pubkey = "7u3HeHxYDLhnCoErrtycNokbQYbWGzLs6JSDqGAv5PfF".parse()?;
let reserve: Pubkey = "D6q6wuQSrifJKZYpR1M8R4YawnLDtDsMmWM1NbBmgJ59".parse()?;
// 1. Derive the obligation PDA
let (obligation_pubkey, _) = pda::obligation(
&KLEND_PROGRAM_ID, 0, 0, &owner, &lending_market,
&Pubkey::default(), &Pubkey::default(),
);
// 2. Fetch the obligation, discover its reserves, fetch them
let obligation_data = rpc.get_account(&obligation_pubkey)?;
let reserve_addrs = ObligationContext::reserve_addresses_for_obligation(&obligation_data.data)?;
let reserve_accounts = rpc.get_multiple_accounts(&reserve_addrs)?;
// 3. Build the context
let reserves: Vec<(Pubkey, &[u8])> = reserve_addrs.iter()
.zip(reserve_accounts.iter())
.filter_map(|(addr, acc)| acc.as_ref().map(|a| (*addr, a.data.as_slice())))
.collect();
let ctx = ObligationContext::from_account_data(obligation_pubkey, &obligation_data.data, &reserves)?;
// 4. Build instructions — refreshes are prepended automatically
let user_ata = spl_associated_token_account::get_associated_token_address(&owner, &"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".parse()?);
let ixs = ctx.deposit(owner, &reserve, user_ata, 1_000_000)?;§Scaled fraction fields (_sf)
All on-chain fields ending in _sf (e.g. market_price_sf, borrowed_amount_sf) are
128-bit fixed-point numbers stored as raw u128 bits. Use Fraction (U68F60 from the
fixed crate) to interpret them:
use klend_interface::Fraction;
let raw_sf: u128 = 1_152_921_504_606_846_976; // 1.0 encoded
let value = Fraction::from_bits(raw_sf);
let float_value: f64 = value.to_num();§Examples
The examples/ directory contains runnable examples matching the
Kamino developer documentation:
| Example | Description |
|---|---|
deposit_lending | Deposit liquidity and receive cTokens directly (no obligation). |
deposit_borrowing | Deposit as collateral into an obligation using ObligationContext. |
borrow | Borrow liquidity against an obligation. |
repay | Repay borrowed liquidity. |
withdraw_obligation | Withdraw collateral from an obligation and redeem for liquidity. |
redeem_ctokens | Redeem cTokens for the underlying liquidity (no obligation). |
flash_loan | Flash-borrow and repay within a single transaction. |
market_data | Fetch and inspect lending market and reserve data. |
user_position | Read an obligation and display deposit/borrow positions. |
cpi_deposit_and_borrow | CPI from an Anchor program: deposit collateral and borrow (reference only, not runnable). |
Full source for each example is included in the crate documentation or in the public GitHub repository.
§License
Licensed under Business Source License 1.1 with an Additional Use Grant permitting unmodified use as a dependency. See the LICENSE file for full terms.
§Example source code
deposit_lending — Deposit liquidity and receive cTokens directly
//! Deposit liquidity into a reserve and receive cTokens directly (no obligation).
//!
//! This is the simplest deposit flow — you supply tokens to a reserve and receive
//! collateral tokens (cTokens) in return. No obligation is involved, so the
//! deposit cannot be used as collateral for borrowing.
//!
//! ```text
//! cargo run --example deposit_lending
//! ```
use std::str::FromStr;
use klend_interface::{helpers, pda, ReserveInfo, KLEND_PROGRAM_ID};
use solana_client::rpc_client::RpcClient;
use solana_pubkey::Pubkey;
use solana_sdk::signer::{keypair::read_keypair_file, Signer};
use spl_associated_token_account::get_associated_token_address;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let rpc_client = RpcClient::new("https://api.mainnet-beta.solana.com");
let signer = read_keypair_file("/path/to/your/keypair.json")?;
let owner = signer.pubkey();
// --- 1. Fetch reserve data -----------------------------------------------------------
let reserve_pubkey = Pubkey::from_str("D6q6wuQSrifJKZYpR1M8R4YawnLDtDsMmWM1NbBmgJ59")?;
let reserve_data = rpc_client.get_account(&reserve_pubkey)?;
let reserve = ReserveInfo::from_account_data(reserve_pubkey, &reserve_data.data)?;
// --- 2. Derive token accounts --------------------------------------------------------
// User's ATA for the deposit token (e.g. USDC)
let user_source_liquidity = get_associated_token_address(&owner, &reserve.liquidity_mint);
// Derive the collateral mint PDA, then the user's ATA for it
let (collateral_mint, _) = pda::reserve_collateral_mint(&KLEND_PROGRAM_ID, &reserve_pubkey);
let user_destination_collateral = get_associated_token_address(&owner, &collateral_mint);
// --- 3. Build instructions -----------------------------------------------------------
let instructions = helpers::deposit::deposit(
owner,
&reserve,
user_source_liquidity, // user's USDC token account
user_destination_collateral, // user's cToken account
1_000_000, // 1 USDC (6 decimals)
);
// Returns: [refresh_reserve, deposit_reserve_liquidity]
// --- 4. Send transaction -------------------------------------------------------------
let message = solana_sdk::message::Message::new(&instructions, Some(&owner));
let recent_blockhash = rpc_client.get_latest_blockhash()?;
let tx = solana_sdk::transaction::Transaction::new(&[&signer], message, recent_blockhash);
let signature = rpc_client.send_and_confirm_transaction(&tx)?;
println!("Deposit successful! Signature: {signature}");
Ok(())
}deposit_borrowing — Deposit as collateral into an obligation
//! Deposit liquidity as collateral into an obligation using `ObligationContext`.
//!
//! Unlike the simple `deposit_lending` example, this deposits tokens into a
//! reserve **and** credits the resulting cTokens as collateral on an obligation,
//! enabling the user to borrow against them.
//!
//! ```text
//! cargo run --example deposit_borrowing
//! ```
use std::str::FromStr;
use klend_interface::{pda, ObligationContext, KLEND_PROGRAM_ID};
use solana_client::rpc_client::RpcClient;
use solana_pubkey::Pubkey;
use solana_sdk::signer::{keypair::read_keypair_file, Signer};
use spl_associated_token_account::get_associated_token_address;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let rpc_client = RpcClient::new("https://api.mainnet-beta.solana.com");
let signer = read_keypair_file("/path/to/your/keypair.json")?;
let owner = signer.pubkey();
// --- 1. Build ObligationContext ------------------------------------------------------
let lending_market = Pubkey::from_str("7u3HeHxYDLhnCoErrtycNokbQYbWGzLs6JSDqGAv5PfF")?;
let reserve_pubkey = Pubkey::from_str("D6q6wuQSrifJKZYpR1M8R4YawnLDtDsMmWM1NbBmgJ59")?;
// Derive the obligation PDA
let (obligation_pubkey, _) = pda::obligation(
&KLEND_PROGRAM_ID,
0,
0,
&owner,
&lending_market,
&Pubkey::default(),
&Pubkey::default(),
);
// Fetch the obligation account
let obligation_data = rpc_client.get_account(&obligation_pubkey)?;
// Discover which reserves the obligation references
let reserve_addrs = ObligationContext::reserve_addresses_for_obligation(&obligation_data.data)?;
// Fetch all reserve accounts in one RPC call
let reserve_accounts = rpc_client.get_multiple_accounts(&reserve_addrs)?;
// Build the context
let reserves: Vec<(Pubkey, &[u8])> = reserve_addrs
.iter()
.zip(reserve_accounts.iter())
.filter_map(|(addr, acc)| acc.as_ref().map(|a| (*addr, a.data.as_slice())))
.collect();
let ctx =
ObligationContext::from_account_data(obligation_pubkey, &obligation_data.data, &reserves)?;
// --- 2. Build deposit instructions ---------------------------------------------------
let liquidity_mint = Pubkey::from_str("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")?;
let user_source_liquidity = get_associated_token_address(&owner, &liquidity_mint);
let instructions = ctx.deposit(
owner,
&reserve_pubkey,
user_source_liquidity,
3_000_000, // 3 USDC (6 decimals)
)?;
// Returns: [refresh_reserves..., refresh_obligation, deposit_and_collateral_v2]
// --- 3. Send transaction -------------------------------------------------------------
let message = solana_sdk::message::Message::new(&instructions, Some(&owner));
let recent_blockhash = rpc_client.get_latest_blockhash()?;
let tx = solana_sdk::transaction::Transaction::new(&[&signer], message, recent_blockhash);
let signature = rpc_client.send_and_confirm_transaction(&tx)?;
println!("Deposit successful! Signature: {signature}");
Ok(())
}borrow — Borrow liquidity against an obligation
//! Borrow liquidity from a reserve against an obligation.
//!
//! The obligation must already have deposited collateral. The borrow instruction
//! is built via `ObligationContext`, which handles refresh instructions automatically.
//!
//! ```text
//! cargo run --example borrow
//! ```
use std::str::FromStr;
use klend_interface::{pda, ObligationContext, KLEND_PROGRAM_ID};
use solana_client::rpc_client::RpcClient;
use solana_pubkey::Pubkey;
use solana_sdk::signer::{keypair::read_keypair_file, Signer};
use spl_associated_token_account::get_associated_token_address;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let rpc_client = RpcClient::new("https://api.mainnet-beta.solana.com");
let signer = read_keypair_file("/path/to/your/keypair.json")?;
let owner = signer.pubkey();
// --- 1. Build ObligationContext ------------------------------------------------------
let lending_market = Pubkey::from_str("7u3HeHxYDLhnCoErrtycNokbQYbWGzLs6JSDqGAv5PfF")?;
let borrow_reserve_pubkey = Pubkey::from_str("FBSyPnxtHKLBZ4UeeUyAnbtFuAmTHLtso9YtsqRDRWpM")?;
let (obligation_pubkey, _) = pda::obligation(
&KLEND_PROGRAM_ID,
0,
0,
&owner,
&lending_market,
&Pubkey::default(),
&Pubkey::default(),
);
let obligation_data = rpc_client.get_account(&obligation_pubkey)?;
let reserve_addrs = ObligationContext::reserve_addresses_for_obligation(&obligation_data.data)?;
let reserve_accounts = rpc_client.get_multiple_accounts(&reserve_addrs)?;
let reserves: Vec<(Pubkey, &[u8])> = reserve_addrs
.iter()
.zip(reserve_accounts.iter())
.filter_map(|(addr, acc)| acc.as_ref().map(|a| (*addr, a.data.as_slice())))
.collect();
let ctx =
ObligationContext::from_account_data(obligation_pubkey, &obligation_data.data, &reserves)?;
// --- 2. Build borrow instructions ----------------------------------------------------
let borrow_liquidity_mint = Pubkey::from_str("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB")?;
let user_dest_liquidity = get_associated_token_address(&owner, &borrow_liquidity_mint);
let instructions = ctx.borrow(
owner,
&borrow_reserve_pubkey,
user_dest_liquidity,
1_000_000, // 1 USDT (6 decimals)
)?;
// Returns: [refresh_reserves..., refresh_obligation, borrow_obligation_liquidity_v2]
// --- 3. Send transaction -------------------------------------------------------------
let message = solana_sdk::message::Message::new(&instructions, Some(&owner));
let recent_blockhash = rpc_client.get_latest_blockhash()?;
let tx = solana_sdk::transaction::Transaction::new(&[&signer], message, recent_blockhash);
let signature = rpc_client.send_and_confirm_transaction(&tx)?;
println!("Borrow successful! Signature: {signature}");
Ok(())
}repay — Repay borrowed liquidity
//! Repay borrowed liquidity on an obligation.
//!
//! Pass [`klend_interface::MAX_AMOUNT`] as the amount to repay the full debt.
//!
//! ```text
//! cargo run --example repay
//! ```
use std::str::FromStr;
use klend_interface::{pda, ObligationContext, KLEND_PROGRAM_ID};
use solana_client::rpc_client::RpcClient;
use solana_pubkey::Pubkey;
use solana_sdk::signer::{keypair::read_keypair_file, Signer};
use spl_associated_token_account::get_associated_token_address;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let rpc_client = RpcClient::new("https://api.mainnet-beta.solana.com");
let signer = read_keypair_file("/path/to/your/keypair.json")?;
let owner = signer.pubkey();
// --- 1. Build ObligationContext ------------------------------------------------------
let lending_market = Pubkey::from_str("7u3HeHxYDLhnCoErrtycNokbQYbWGzLs6JSDqGAv5PfF")?;
let repay_reserve_pubkey = Pubkey::from_str("FBSyPnxtHKLBZ4UeeUyAnbtFuAmTHLtso9YtsqRDRWpM")?;
let (obligation_pubkey, _) = pda::obligation(
&KLEND_PROGRAM_ID,
0,
0,
&owner,
&lending_market,
&Pubkey::default(),
&Pubkey::default(),
);
let obligation_data = rpc_client.get_account(&obligation_pubkey)?;
let reserve_addrs = ObligationContext::reserve_addresses_for_obligation(&obligation_data.data)?;
let reserve_accounts = rpc_client.get_multiple_accounts(&reserve_addrs)?;
let reserves: Vec<(Pubkey, &[u8])> = reserve_addrs
.iter()
.zip(reserve_accounts.iter())
.filter_map(|(addr, acc)| acc.as_ref().map(|a| (*addr, a.data.as_slice())))
.collect();
let ctx =
ObligationContext::from_account_data(obligation_pubkey, &obligation_data.data, &reserves)?;
// --- 2. Build repay instructions -----------------------------------------------------
let repay_liquidity_mint = Pubkey::from_str("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB")?;
let user_source_liquidity = get_associated_token_address(&owner, &repay_liquidity_mint);
let instructions = ctx.repay(
owner,
&repay_reserve_pubkey,
user_source_liquidity,
1_000_000, // 1 USDT (6 decimals)
)?;
// Returns: [refresh_reserves..., refresh_obligation, repay_obligation_liquidity_v2]
// --- 3. Send transaction -------------------------------------------------------------
let message = solana_sdk::message::Message::new(&instructions, Some(&owner));
let recent_blockhash = rpc_client.get_latest_blockhash()?;
let tx = solana_sdk::transaction::Transaction::new(&[&signer], message, recent_blockhash);
let signature = rpc_client.send_and_confirm_transaction(&tx)?;
println!("Repay successful! Signature: {signature}");
Ok(())
}withdraw_obligation — Withdraw collateral from an obligation and redeem for liquidity
//! Withdraw collateral from an obligation and redeem it for the underlying liquidity.
//!
//! This is the standard withdraw flow for borrowers — it removes collateral from
//! the obligation and redeems the cTokens for the underlying tokens in one step.
//! Your position must remain healthy (within LTV limits) after the withdrawal.
//!
//! Pass [`klend_interface::MAX_AMOUNT`] as the amount to withdraw everything.
//!
//! ```text
//! cargo run --example withdraw_obligation
//! ```
use std::str::FromStr;
use klend_interface::{pda, ObligationContext, KLEND_PROGRAM_ID};
use solana_client::rpc_client::RpcClient;
use solana_pubkey::Pubkey;
use solana_sdk::signer::{keypair::read_keypair_file, Signer};
use spl_associated_token_account::get_associated_token_address;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let rpc_client = RpcClient::new("https://api.mainnet-beta.solana.com");
let signer = read_keypair_file("/path/to/your/keypair.json")?;
let owner = signer.pubkey();
// --- 1. Build ObligationContext ------------------------------------------------------
let lending_market = Pubkey::from_str("7u3HeHxYDLhnCoErrtycNokbQYbWGzLs6JSDqGAv5PfF")?;
let reserve_pubkey = Pubkey::from_str("D6q6wuQSrifJKZYpR1M8R4YawnLDtDsMmWM1NbBmgJ59")?;
let (obligation_pubkey, _) = pda::obligation(
&KLEND_PROGRAM_ID,
0,
0,
&owner,
&lending_market,
&Pubkey::default(),
&Pubkey::default(),
);
let obligation_data = rpc_client.get_account(&obligation_pubkey)?;
let reserve_addrs = ObligationContext::reserve_addresses_for_obligation(&obligation_data.data)?;
let reserve_accounts = rpc_client.get_multiple_accounts(&reserve_addrs)?;
let reserves: Vec<(Pubkey, &[u8])> = reserve_addrs
.iter()
.zip(reserve_accounts.iter())
.filter_map(|(addr, acc)| acc.as_ref().map(|a| (*addr, a.data.as_slice())))
.collect();
let ctx =
ObligationContext::from_account_data(obligation_pubkey, &obligation_data.data, &reserves)?;
// --- 2. Build withdraw instructions --------------------------------------------------
let liquidity_mint = Pubkey::from_str("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")?;
let user_dest_liquidity = get_associated_token_address(&owner, &liquidity_mint);
let instructions = ctx.withdraw(
owner,
&reserve_pubkey,
user_dest_liquidity,
1_000_000, // 1 USDC (6 decimals)
)?;
// Returns: [refresh_reserves..., refresh_obligation, withdraw_and_redeem]
// --- 3. Send transaction -------------------------------------------------------------
let message = solana_sdk::message::Message::new(&instructions, Some(&owner));
let recent_blockhash = rpc_client.get_latest_blockhash()?;
let tx = solana_sdk::transaction::Transaction::new(&[&signer], message, recent_blockhash);
let signature = rpc_client.send_and_confirm_transaction(&tx)?;
println!("Withdrawal successful! Signature: {signature}");
Ok(())
}redeem_ctokens — Redeem cTokens for the underlying liquidity (no obligation)
//! Redeem cTokens for the underlying liquidity (no obligation).
//!
//! When you deposit liquidity into a reserve (see `deposit_lending`), you receive
//! cTokens representing your share of the pool. This example redeems those cTokens
//! back for the underlying tokens plus any accrued interest.
//!
//! No obligation is involved — this is the reverse of a simple lending deposit.
//!
//! ```text
//! cargo run --example redeem_ctokens
//! ```
use std::str::FromStr;
use klend_interface::{helpers, pda, ReserveInfo, KLEND_PROGRAM_ID};
use solana_client::rpc_client::RpcClient;
use solana_pubkey::Pubkey;
use solana_sdk::signer::{keypair::read_keypair_file, Signer};
use spl_associated_token_account::get_associated_token_address;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let rpc_client = RpcClient::new("https://api.mainnet-beta.solana.com");
let signer = read_keypair_file("/path/to/your/keypair.json")?;
let owner = signer.pubkey();
// --- 1. Fetch reserve data -----------------------------------------------------------
let reserve_pubkey = Pubkey::from_str("D6q6wuQSrifJKZYpR1M8R4YawnLDtDsMmWM1NbBmgJ59")?;
let reserve_data = rpc_client.get_account(&reserve_pubkey)?;
let reserve = ReserveInfo::from_account_data(reserve_pubkey, &reserve_data.data)?;
// --- 2. Derive token accounts --------------------------------------------------------
// User's ATA for the underlying token (e.g. USDC)
let user_destination_liquidity = get_associated_token_address(&owner, &reserve.liquidity_mint);
// User's ATA for the cToken (collateral token received when depositing)
let (collateral_mint, _) = pda::reserve_collateral_mint(&KLEND_PROGRAM_ID, &reserve_pubkey);
let user_source_collateral = get_associated_token_address(&owner, &collateral_mint);
// --- 3. Build redeem instructions ----------------------------------------------------
let collateral_amount = 1_000_000; // amount of cTokens to redeem
let instructions = helpers::withdraw::redeem(
owner,
&reserve,
user_source_collateral, // cToken account (burned)
user_destination_liquidity, // receives underlying tokens + interest
collateral_amount,
);
// Returns: [refresh_reserve, redeem_reserve_collateral]
// --- 4. Send transaction -------------------------------------------------------------
let message = solana_sdk::message::Message::new(&instructions, Some(&owner));
let recent_blockhash = rpc_client.get_latest_blockhash()?;
let tx = solana_sdk::transaction::Transaction::new(&[&signer], message, recent_blockhash);
let signature = rpc_client.send_and_confirm_transaction(&tx)?;
println!("Redeem successful! Signature: {signature}");
Ok(())
}flash_loan — Flash-borrow and repay in a single transaction
//! Flash-borrow and repay within a single transaction.
//!
//! A flash loan borrows tokens at the start of the transaction and repays them
//! (plus a fee) at the end. You can insert arbitrary instructions between the
//! borrow and repay — e.g. arbitrage, liquidation, or collateral swap.
//!
//! ```text
//! cargo run --example flash_loan
//! ```
use std::str::FromStr;
use klend_interface::{helpers, ReserveInfo};
use solana_client::rpc_client::RpcClient;
use solana_pubkey::Pubkey;
use solana_sdk::signer::{keypair::read_keypair_file, Signer};
use spl_associated_token_account::get_associated_token_address;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let rpc_client = RpcClient::new("https://api.mainnet-beta.solana.com");
let signer = read_keypair_file("/path/to/your/keypair.json")?;
let owner = signer.pubkey();
// --- 1. Fetch reserve data -----------------------------------------------------------
let reserve_pubkey = Pubkey::from_str("D6q6wuQSrifJKZYpR1M8R4YawnLDtDsMmWM1NbBmgJ59")?;
let reserve_data = rpc_client.get_account(&reserve_pubkey)?;
let reserve = ReserveInfo::from_account_data(reserve_pubkey, &reserve_data.data)?;
// --- 2. Derive token account ---------------------------------------------------------
let user_token_account = get_associated_token_address(&owner, &reserve.liquidity_mint);
// --- 3. Build flash loan instructions ------------------------------------------------
// `borrow_instruction_index` is the position of the borrow instruction in
// the final transaction. It starts at 0 here because we place it first.
let (borrow_ix, repay_ix) = helpers::flash::flash_loan(
owner,
&reserve,
user_token_account, // source for repayment
user_token_account, // destination for borrowed funds
1_000_000, // 1 USDC (6 decimals)
0, // borrow instruction index in the transaction
None, // no referrer
);
// --- 4. Compose the full transaction -------------------------------------------------
// Insert your custom instructions between borrow and repay
let your_custom_instructions: Vec<solana_instruction::Instruction> =
vec![/* your arbitrage / swap / liquidation logic here */];
let mut all_instructions = vec![borrow_ix];
all_instructions.extend(your_custom_instructions);
all_instructions.push(repay_ix);
let message = solana_sdk::message::Message::new(&all_instructions, Some(&owner));
let recent_blockhash = rpc_client.get_latest_blockhash()?;
let tx = solana_sdk::transaction::Transaction::new(&[&signer], message, recent_blockhash);
let signature = rpc_client.send_and_confirm_transaction(&tx)?;
println!("Flash loan successful! Signature: {signature}");
Ok(())
}market_data — Fetch and inspect lending market and reserve data
//! Fetch and inspect lending market and reserve data.
//!
//! Demonstrates how to:
//! - Parse a `LendingMarket` account
//! - Discover all reserves for a market using `getProgramAccounts` with memcmp
//! - Read reserve metrics (available liquidity, borrow amount, price, config)
//!
//! Fields suffixed with `_sf` are fixed-point fractions — use
//! [`klend_interface::Fraction::from_bits`] to interpret them.
//!
//! ```text
//! cargo run --example market_data
//! ```
use std::str::FromStr;
use klend_interface::{
state::{LendingMarket, Reserve},
Fraction, KLEND_PROGRAM_ID,
};
use solana_account::ReadableAccount;
use solana_client::{
rpc_client::RpcClient,
rpc_config::RpcProgramAccountsConfig,
rpc_filter::{Memcmp, RpcFilterType},
};
use solana_pubkey::Pubkey;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let rpc_client = RpcClient::new("https://api.mainnet-beta.solana.com");
let market_pubkey = Pubkey::from_str("7u3HeHxYDLhnCoErrtycNokbQYbWGzLs6JSDqGAv5PfF")?;
// --- 1. Fetch and parse the lending market -------------------------------------------
let market_account = rpc_client.get_account(&market_pubkey)?;
let market = klend_interface::from_account_data::<LendingMarket>(&market_account.data)?;
println!("Market authority: {}", market.lending_market_owner);
// --- 2. Fetch all reserves for this market -------------------------------------------
// The `lending_market` field is at byte offset 32 in the Reserve account
// (after the 8-byte discriminator and 8-byte tag and 16-byte LastUpdate).
let filters = vec![
RpcFilterType::Memcmp(Memcmp::new_raw_bytes(32, market_pubkey.to_bytes().to_vec())),
RpcFilterType::DataSize(8616), // Reserve account size
];
let accounts = rpc_client.get_program_accounts_with_config(
&KLEND_PROGRAM_ID,
RpcProgramAccountsConfig {
filters: Some(filters),
..Default::default()
},
)?;
// --- 3. Parse each reserve and print key metrics -------------------------------------
for (pubkey, account) in &accounts {
let reserve = klend_interface::from_account_data::<Reserve>(account.data())?;
let borrowed: f64 =
Fraction::from_bits(u128::from(reserve.liquidity.borrowed_amount_sf)).to_num();
let price: f64 =
Fraction::from_bits(u128::from(reserve.liquidity.market_price_sf)).to_num();
println!("Reserve: {pubkey}");
println!(
" Available liquidity: {}",
reserve.liquidity.total_available_amount
);
println!(" Borrowed amount: {borrowed:.6}");
println!(" Market price: {price:.6}");
println!(" Mint decimals: {}", reserve.liquidity.mint_decimals);
println!(" LTV: {}%", reserve.config.loan_to_value_pct);
println!(" Deposit limit: {}", reserve.config.deposit_limit);
println!(" Borrow limit: {}", reserve.config.borrow_limit);
}
Ok(())
}user_position — Read an obligation and display positions
//! Read an obligation and display deposit/borrow positions and health metrics.
//!
//! Demonstrates how to:
//! - Derive an obligation PDA for a given user and market
//! - Parse the obligation account
//! - Iterate over active deposits and borrows
//! - Compute health metrics from scaled fraction fields
//!
//! ```text
//! cargo run --example user_position
//! ```
use std::str::FromStr;
use klend_interface::{pda, state::Obligation, Fraction, KLEND_PROGRAM_ID};
use solana_client::rpc_client::RpcClient;
use solana_pubkey::Pubkey;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let rpc_client = RpcClient::new("https://api.mainnet-beta.solana.com");
let market_pubkey = Pubkey::from_str("7u3HeHxYDLhnCoErrtycNokbQYbWGzLs6JSDqGAv5PfF")?;
let user_pubkey = Pubkey::from_str("EZC9wzVCvihCsCHEMGADYdsRhcpdRYWzSCZAVegSCfqY")?;
// --- 1. Derive obligation PDA --------------------------------------------------------
// tag=0, id=0 is the default "Vanilla" obligation
let (obligation_pubkey, _bump) = pda::obligation(
&KLEND_PROGRAM_ID,
0,
0,
&user_pubkey,
&market_pubkey,
&Pubkey::default(),
&Pubkey::default(),
);
// --- 2. Fetch and parse ---------------------------------------------------------------
let obligation_account = rpc_client.get_account(&obligation_pubkey)?;
let obligation = klend_interface::from_account_data::<Obligation>(&obligation_account.data)?;
println!("Obligation owner: {}", obligation.owner);
// --- 3. Active deposits --------------------------------------------------------------
println!("\nDeposits (Collateral):");
for deposit in &obligation.deposits {
if deposit.deposit_reserve == Pubkey::default() {
continue;
}
println!(" Reserve: {}", deposit.deposit_reserve);
println!(
" Deposited amount (cTokens): {}",
deposit.deposited_amount
);
}
// --- 4. Active borrows ---------------------------------------------------------------
println!("\nBorrows:");
for borrow in &obligation.borrows {
if borrow.borrow_reserve == Pubkey::default() {
continue;
}
println!(" Reserve: {}", borrow.borrow_reserve);
let borrowed: f64 = Fraction::from_bits(borrow.borrowed_amount()).to_num();
println!(" Borrowed amount: {borrowed:.6}");
}
// --- 5. Health metrics ---------------------------------------------------------------
let sf = |v: u128| -> f64 { Fraction::from_bits(v).to_num() };
let deposited_value = sf(obligation.deposited_value());
let allowed_borrow = sf(obligation.allowed_borrow_value());
let unhealthy_borrow = sf(obligation.unhealthy_borrow_value());
let borrowed_value = sf(obligation.borrow_factor_adjusted_debt_value());
println!("\nMetrics:");
println!(" Deposited value: ${deposited_value:.2}");
println!(" Allowed borrow value: ${allowed_borrow:.2}");
println!(" Unhealthy borrow value: ${unhealthy_borrow:.2}");
println!(" Current borrow value: ${borrowed_value:.2}");
println!(" Liquidatable: {}", obligation.is_liquidatable());
println!(
" Borrowing disabled: {}",
obligation.borrowing_disabled > 0
);
Ok(())
}cpi_deposit_and_borrow — CPI from an Anchor program: deposit collateral and borrow
// CPI Example: Deposit collateral and borrow from an Anchor program
//
// This file is a reference example — it is NOT compiled as part of
// klend-interface. It demonstrates how an on-chain Anchor program can
// use klend-interface instruction builders to CPI into Kamino Lending.
//
// Scenario: your program holds Token A in a vault, deposits it as
// collateral into a Klend obligation, then borrows Token B.
//
// To adapt this to your project:
// 1. Add `klend-interface` and `anchor-lang` to your Cargo.toml
// 2. Copy the relevant structs and handler logic below
// 3. Adjust PDA seeds and account constraints to match your program
use anchor_lang::prelude::*;
use klend_interface::{
instructions::{borrow, deposit, obligation},
pda as klend_pda,
types::InitObligationArgs,
KLEND_PROGRAM_ID,
};
declare_id!("YourProgram1111111111111111111111111111111111");
/// PDA seeds for the authority that owns the vaults and the obligation.
const AUTHORITY_SEED: &[u8] = b"authority";
#[program]
pub mod cpi_example {
use super::*;
/// Deposit `deposit_amount` of Token A as collateral, then borrow
/// `borrow_amount` of Token B from Klend.
///
/// The PDA `authority` acts as the obligation owner and signs all
/// CPI calls via `invoke_signed`.
pub fn deposit_and_borrow(
ctx: Context<DepositAndBorrow>,
deposit_amount: u64,
borrow_amount: u64,
) -> Result<()> {
let lending_market_key = ctx.accounts.lending_market.key();
let authority_seeds: &[&[u8]] = &[
AUTHORITY_SEED,
lending_market_key.as_ref(),
&[ctx.bumps.authority],
];
// -----------------------------------------------------------------
// 1. Init obligation (skip if already initialized)
// -----------------------------------------------------------------
let init_ix = obligation::init_obligation(
obligation::InitObligationAccounts {
obligation_owner: ctx.accounts.authority.key(),
fee_payer: ctx.accounts.payer.key(),
obligation: ctx.accounts.obligation.key(),
lending_market: ctx.accounts.lending_market.key(),
seed1_account: Pubkey::default(),
seed2_account: Pubkey::default(),
owner_user_metadata: ctx.accounts.owner_user_metadata.key(),
},
InitObligationArgs { tag: 0, id: 0 },
);
solana_program::program::invoke_signed(
&init_ix,
&[
ctx.accounts.authority.to_account_info(),
ctx.accounts.payer.to_account_info(),
ctx.accounts.obligation.to_account_info(),
ctx.accounts.lending_market.to_account_info(),
// seed1_account and seed2_account are Pubkey::default() —
// we still need to pass *some* AccountInfo. In practice you
// would pass a dummy account or the system program. Here we
// re-use system_program for both since the seeds are unused.
ctx.accounts.system_program.to_account_info(),
ctx.accounts.system_program.to_account_info(),
ctx.accounts.owner_user_metadata.to_account_info(),
ctx.accounts.rent.to_account_info(),
ctx.accounts.system_program.to_account_info(),
],
&[authority_seeds],
)?;
// -----------------------------------------------------------------
// 2. Deposit Token A as collateral
// -----------------------------------------------------------------
let deposit_ix =
deposit::deposit_reserve_liquidity_and_obligation_collateral_v2(
deposit::DepositReserveLiquidityAndObligationCollateralV2Accounts {
owner: ctx.accounts.authority.key(),
obligation: ctx.accounts.obligation.key(),
lending_market: ctx.accounts.lending_market.key(),
lending_market_authority: ctx.accounts.lending_market_authority.key(),
reserve: ctx.accounts.deposit_reserve.key(),
reserve_liquidity_mint: ctx.accounts.deposit_reserve_liquidity_mint.key(),
reserve_liquidity_supply: ctx.accounts.deposit_reserve_liquidity_supply.key(),
reserve_collateral_mint: ctx.accounts.deposit_reserve_collateral_mint.key(),
reserve_destination_deposit_collateral: ctx
.accounts
.deposit_reserve_collateral_supply
.key(),
user_source_liquidity: ctx.accounts.vault_a.key(),
placeholder_user_destination_collateral: None,
liquidity_token_program: ctx.accounts.token_program.key(),
obligation_farm_user_state: None,
reserve_farm_state: None,
},
deposit_amount,
);
solana_program::program::invoke_signed(
&deposit_ix,
&[
ctx.accounts.authority.to_account_info(),
ctx.accounts.obligation.to_account_info(),
ctx.accounts.lending_market.to_account_info(),
ctx.accounts.lending_market_authority.to_account_info(),
ctx.accounts.deposit_reserve.to_account_info(),
ctx.accounts.deposit_reserve_liquidity_mint.to_account_info(),
ctx.accounts.deposit_reserve_liquidity_supply.to_account_info(),
ctx.accounts.deposit_reserve_collateral_mint.to_account_info(),
ctx.accounts.deposit_reserve_collateral_supply.to_account_info(),
ctx.accounts.vault_a.to_account_info(),
ctx.accounts.klend_program.to_account_info(), // placeholder (None)
ctx.accounts.token_program.to_account_info(),
ctx.accounts.token_program.to_account_info(), // liquidity_token_program
ctx.accounts.instructions_sysvar.to_account_info(),
ctx.accounts.klend_program.to_account_info(), // farm user state (None)
ctx.accounts.klend_program.to_account_info(), // reserve farm state (None)
ctx.accounts.farms_program.to_account_info(),
],
&[authority_seeds],
)?;
// -----------------------------------------------------------------
// 3. Borrow Token B
// -----------------------------------------------------------------
//
// remaining_accounts must include the deposit reserve so Klend can
// verify collateral. Each deposit reserve is passed as:
// AccountMeta { pubkey, is_signer: false, is_writable: true }
let remaining = vec![AccountMeta {
pubkey: ctx.accounts.deposit_reserve.key(),
is_signer: false,
is_writable: true,
}];
let borrow_ix = borrow::borrow_obligation_liquidity_v2(
borrow::BorrowObligationLiquidityV2Accounts {
owner: ctx.accounts.authority.key(),
obligation: ctx.accounts.obligation.key(),
lending_market: ctx.accounts.lending_market.key(),
lending_market_authority: ctx.accounts.lending_market_authority.key(),
borrow_reserve: ctx.accounts.borrow_reserve.key(),
borrow_reserve_liquidity_mint: ctx
.accounts
.borrow_reserve_liquidity_mint
.key(),
reserve_source_liquidity: ctx
.accounts
.borrow_reserve_liquidity_supply
.key(),
borrow_reserve_liquidity_fee_receiver: ctx
.accounts
.borrow_reserve_fee_receiver
.key(),
user_destination_liquidity: ctx.accounts.vault_b.key(),
referrer_token_state: None,
token_program: ctx.accounts.token_program.key(),
obligation_farm_user_state: None,
reserve_farm_state: None,
},
borrow_amount,
remaining,
);
solana_program::program::invoke_signed(
&borrow_ix,
&[
ctx.accounts.authority.to_account_info(),
ctx.accounts.obligation.to_account_info(),
ctx.accounts.lending_market.to_account_info(),
ctx.accounts.lending_market_authority.to_account_info(),
ctx.accounts.borrow_reserve.to_account_info(),
ctx.accounts.borrow_reserve_liquidity_mint.to_account_info(),
ctx.accounts.borrow_reserve_liquidity_supply.to_account_info(),
ctx.accounts.borrow_reserve_fee_receiver.to_account_info(),
ctx.accounts.vault_b.to_account_info(),
ctx.accounts.klend_program.to_account_info(), // referrer (None)
ctx.accounts.token_program.to_account_info(),
ctx.accounts.instructions_sysvar.to_account_info(),
ctx.accounts.klend_program.to_account_info(), // farm user state (None)
ctx.accounts.klend_program.to_account_info(), // reserve farm state (None)
ctx.accounts.farms_program.to_account_info(),
// remaining: deposit reserve (for collateral check)
ctx.accounts.deposit_reserve.to_account_info(),
],
&[authority_seeds],
)?;
Ok(())
}
}
// ---------------------------------------------------------------------------
// Account validation struct
// ---------------------------------------------------------------------------
#[derive(Accounts)]
pub struct DepositAndBorrow<'info> {
// -- Program-owned accounts --
/// PDA authority that owns the vaults and the Klend obligation.
/// Seeds: `[b"authority", lending_market.key()]`
#[account(
seeds = [AUTHORITY_SEED, lending_market.key().as_ref()],
bump,
)]
pub authority: SystemAccount<'info>,
/// Token A vault — holds the collateral token, owned by `authority`.
#[account(mut, token::authority = authority)]
pub vault_a: Account<'info, TokenAccount>,
/// Token B vault — receives the borrowed token, owned by `authority`.
#[account(mut, token::authority = authority)]
pub vault_b: Account<'info, TokenAccount>,
/// Fee payer for obligation init (can be the caller).
#[account(mut)]
pub payer: Signer<'info>,
// -- Klend accounts (passed through for CPI) --
/// CHECK: Klend lending market account.
pub lending_market: UncheckedAccount<'info>,
/// CHECK: Klend lending market authority PDA.
/// Derived as `klend_pda::lending_market_authority(&KLEND_PROGRAM_ID, &lending_market)`.
pub lending_market_authority: UncheckedAccount<'info>,
/// CHECK: Obligation PDA owned by `authority`.
/// Derived as `klend_pda::obligation(&KLEND_PROGRAM_ID, 0, 0, &authority, &lending_market, ...)`.
#[account(mut)]
pub obligation: UncheckedAccount<'info>,
/// CHECK: User metadata PDA for `authority`.
/// Derived as `klend_pda::user_metadata(&KLEND_PROGRAM_ID, &authority)`.
pub owner_user_metadata: UncheckedAccount<'info>,
// -- Deposit reserve (Token A) accounts --
/// CHECK: Klend reserve for Token A.
#[account(mut)]
pub deposit_reserve: UncheckedAccount<'info>,
/// CHECK: Token A mint.
pub deposit_reserve_liquidity_mint: UncheckedAccount<'info>,
/// CHECK: Reserve's Token A supply vault.
/// Derived as `klend_pda::reserve_liquidity_supply(&KLEND_PROGRAM_ID, &deposit_reserve)`.
#[account(mut)]
pub deposit_reserve_liquidity_supply: UncheckedAccount<'info>,
/// CHECK: Reserve's cToken mint.
/// Derived as `klend_pda::reserve_collateral_mint(&KLEND_PROGRAM_ID, &deposit_reserve)`.
#[account(mut)]
pub deposit_reserve_collateral_mint: UncheckedAccount<'info>,
/// CHECK: Reserve's cToken supply vault.
/// Derived as `klend_pda::reserve_collateral_supply(&KLEND_PROGRAM_ID, &deposit_reserve)`.
#[account(mut)]
pub deposit_reserve_collateral_supply: UncheckedAccount<'info>,
// -- Borrow reserve (Token B) accounts --
/// CHECK: Klend reserve for Token B.
#[account(mut)]
pub borrow_reserve: UncheckedAccount<'info>,
/// CHECK: Token B mint.
pub borrow_reserve_liquidity_mint: UncheckedAccount<'info>,
/// CHECK: Borrow reserve's Token B supply vault.
/// Derived as `klend_pda::reserve_liquidity_supply(&KLEND_PROGRAM_ID, &borrow_reserve)`.
#[account(mut)]
pub borrow_reserve_liquidity_supply: UncheckedAccount<'info>,
/// CHECK: Borrow reserve's fee receiver vault.
/// Derived as `klend_pda::reserve_fee_receiver(&KLEND_PROGRAM_ID, &borrow_reserve)`.
#[account(mut)]
pub borrow_reserve_fee_receiver: UncheckedAccount<'info>,
// -- Programs and sysvars --
/// The Klend program.
/// CHECK: Validated by address.
#[account(address = KLEND_PROGRAM_ID)]
pub klend_program: UncheckedAccount<'info>,
/// CHECK: Kamino Farms program.
pub farms_program: UncheckedAccount<'info>,
pub token_program: Program<'info, Token>,
pub system_program: Program<'info, System>,
pub rent: Sysvar<'info, Rent>,
/// CHECK: Sysvar instructions (required by Klend).
#[account(address = solana_program::sysvar::instructions::ID)]
pub instructions_sysvar: UncheckedAccount<'info>,
}Re-exports§
pub use errors::LendingError;pub use fraction::FRACTION_ONE_SCALED;pub use helpers::CallbackAccounts;pub use helpers::FarmsAccounts;pub use helpers::ObligationContext;pub use helpers::ObligationContextError;pub use helpers::ObligationInfo;pub use helpers::ReserveInfo;pub use state::from_account_data;pub use state::AccountDataError;
Modules§
- discriminators
- errors
- Klend program error codes.
- fraction
- Fixed-point fraction type matching the on-chain representation.
- helpers
- instructions
- pda
- state
- types
- util
Constants§
- ASSOCIATED_
TOKEN_ PROGRAM_ ID - FARMS_
PROGRAM_ ID - Kamino Farms program ID.
- KLEND_
PROGRAM_ ID - Kamino Lending (mainnet) program ID.
- KLEND_
STAGING_ PROGRAM_ ID - Kamino Lending (staging / devnet) program ID.
- KVAULT_
PROGRAM_ ID - Kamino Vault (mainnet) program ID — used as the progress callback program
for
KlendQueueAccountingHandlerOnKvault. - MAX_
AMOUNT - Sentinel value that tells the program to use the maximum available amount (e.g. repay all debt, withdraw all collateral).
- SYSTEM_
PROGRAM_ ID - SYSVAR_
INSTRUCTIONS_ ID - SYSVAR_
RENT_ ID - TOKEN_
PROGRAM_ ID
Type Aliases§
- Fraction
- Fixed-point fraction type: 128-bit unsigned, 68 integer bits, 60 fractional bits.