Documentation Index
Fetch the complete documentation index at: https://mintlify.com/kamino-finance/klend/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Kamino Lending’s referral system allows referrers to earn a portion of protocol fees generated by users they refer. Referrers create a unique state account and URL, then receive fees when referred users borrow, repay, or use flash loans.
Referrer State Structures
The referral system uses three main account types defined in state/referral.rs:
ReferrerState
// state/referral.rs:119
pub struct ReferrerState {
pub short_url: Pubkey, // Link to ShortUrl account
pub owner: Pubkey, // Referrer's wallet
}
The main referrer account linking the referrer to their short URL.
ShortUrl
// state/referral.rs:126
pub struct ShortUrl {
pub referrer: Pubkey, // Referrer's wallet
pub short_url: String, // Custom URL identifier
}
Stores the referrer’s custom short URL for user tracking.
ReferrerTokenState
// state/referral.rs:38
pub struct ReferrerTokenState {
pub referrer: Pubkey, // Referrer's wallet
pub mint: Pubkey, // Token mint (one per asset)
pub amount_unclaimed_sf: u128, // Unclaimed fees (scaled fraction)
pub amount_cumulative_sf: u128, // Total fees earned (scaled fraction)
pub bump: u64, // PDA bump seed
pub padding: [u64; 31],
}
Tracks fees earned per token. Referrers have one ReferrerTokenState per reserve/mint.
Display Format (state/referral.rs:64):
let amount_unclaimed = Fraction::from_bits(*amount_unclaimed_sf)
.saturating_to_num::<u64>();
let amount_cumulative = Fraction::from_bits(*amount_cumulative_sf)
.saturating_to_num::<u64>();
Fees are stored as scaled fractions for precision, converted to integers for display.
Setting Up as a Referrer
Step 1: Initialize Referrer State and Short URL
Handler: handler_init_referrer_state_and_short_url.rs:11
pub fn init_referrer_state_and_short_url(
ctx: Context<InitReferrerStateAndShortUrl>,
short_url: String,
) -> Result<()>
This instruction creates your ReferrerState and ShortUrl accounts.
Requirements (handler_init_referrer_state_and_short_url.rs:12):
require!(
short_url
.chars()
.all(|char| char.is_ascii_alphanumeric() || char == '_' || char == '-'),
LendingError::ShortUrlNotAsciiAlphanumeric
);
- Short URL must contain only ASCII alphanumeric characters, underscores, or hyphens
- Must have a
UserMetadata account already created
Accounts:
referrer: Signer and fee payer
referrer_state: PDA ["referrer_state", referrer.key()]
referrer_short_url: PDA ["short_url", short_url.bytes()]
referrer_user_metadata: Your existing user metadata account
Example:
import { PublicKey } from '@solana/web3.js';
import { initReferrerStateAndShortUrl } from './kamino-sdk';
const setupReferrer = async (shortUrl: string) => {
// Find PDAs
const [referrerState] = PublicKey.findProgramAddressSync(
[Buffer.from("referrer_state"), wallet.publicKey.toBuffer()],
KAMINO_PROGRAM_ID
);
const [shortUrlAccount] = PublicKey.findProgramAddressSync(
[Buffer.from("short_url"), Buffer.from(shortUrl)],
KAMINO_PROGRAM_ID
);
const [userMetadata] = PublicKey.findProgramAddressSync(
[Buffer.from("user_metadata"), wallet.publicKey.toBuffer()],
KAMINO_PROGRAM_ID
);
// Create instruction
const ix = await initReferrerStateAndShortUrl({
referrer: wallet.publicKey,
referrerState,
referrerShortUrl: shortUrlAccount,
referrerUserMetadata: userMetadata,
shortUrl,
});
const tx = new Transaction().add(ix);
await sendAndConfirmTransaction(connection, tx, [wallet]);
console.log(`Referrer setup complete! Short URL: ${shortUrl}`);
};
// Usage
await setupReferrer("my_referral_link");
Step 2: Initialize Referrer Token State
Handler: handler_init_referrer_token_state.rs:8
pub fn init_referrer_token_state(
ctx: Context<InitReferrerTokenState>,
) -> Result<()>
Create a ReferrerTokenState for each reserve/token you want to earn fees from.
Initialization (handler_init_referrer_token_state.rs:14):
*referrer_token_state = ReferrerTokenState {
referrer,
mint: reserve.liquidity.mint_pubkey,
amount_unclaimed_sf: 0,
amount_cumulative_sf: 0,
bump: bump.into(),
padding: [0; 31],
};
Accounts:
payer: Fee payer (can be different from referrer)
lending_market: The lending market
reserve: The reserve to track fees for
referrer: The referrer’s wallet
referrer_token_state: PDA ["referrer_token_state", referrer.key(), reserve.key()]
Example:
const initTokenState = async (reserve: PublicKey, referrer: PublicKey) => {
const [referrerTokenState, bump] = PublicKey.findProgramAddressSync(
[
Buffer.from("referrer_token_state"),
referrer.toBuffer(),
reserve.toBuffer(),
],
KAMINO_PROGRAM_ID
);
const ix = await initReferrerTokenState({
payer: wallet.publicKey,
lendingMarket,
reserve,
referrer,
referrerTokenState,
});
await sendAndConfirmTransaction(connection, new Transaction().add(ix), [wallet]);
};
// Initialize for USDC reserve
await initTokenState(usdcReserve, wallet.publicKey);
How Referrers Earn Fees
Referrers earn fees when their referred users perform these actions:
1. Borrowing
Fee Accumulation (lending_operations.rs:324):
if let Some(mut referrer_token_state) = referrer_token_state {
if lending_market.referral_fee_bps > 0 {
add_referrer_fee(
borrow_reserve,
&mut referrer_token_state,
Fraction::from_num(referrer_fee),
)?;
borrow_reserve.liquidity.total_available_amount += referrer_fee;
}
}
When a user borrows with a referrer, the referrer earns a portion of the origination fee.
2. Flash Loans
Fee Accumulation (lending_operations.rs:1791):
if let Some(referrer_token_state_loader) = referrer_token_state_loader {
if lending_market.referral_fee_bps > 0 {
let referrer_token_state = &mut referrer_token_state_loader.get_mut()?;
add_referrer_fee(
reserve,
referrer_token_state,
Fraction::from_num(referrer_fee),
)?;
reserve.liquidity.total_available_amount += referrer_fee;
}
}
Referrers earn fees when referred users execute flash loans.
3. Ongoing Interest
Continuous Accrual (lending_operations.rs:1959):
pub fn accumulate_referrer_fees<'info, T>(
program_id: &Pubkey,
borrow_reserve_info_key: Pubkey,
borrow_reserve: &mut Reserve,
obligation_referrer: &Pubkey,
lending_market_referral_fee_bps: u16,
slots_elapsed: u64,
borrowed_amount_f: Fraction,
previous_borrowed_amount_f: Fraction,
obligation_has_referrer: bool,
referrer_token_states_iter: &mut impl Iterator<Item = T>,
) -> Result<()>
Referrers continuously earn fees from the interest accrued on their referred users’ borrows.
Fee Calculation (lending_operations.rs:2001):
let referrer_fee_f = net_new_variable_debt_f * absolute_referral_rate;
Fee Addition Logic
Implementation (lending_operations.rs:1944):
pub fn add_referrer_fee(
borrow_reserve: &mut Reserve,
referrer_token_state: &mut ReferrerTokenState,
referrer_fee: Fraction,
) -> Result<()> {
let referrer_fee_sf = referrer_fee.to_sf();
referrer_token_state.amount_unclaimed_sf += referrer_fee_sf;
referrer_token_state.amount_cumulative_sf += referrer_fee_sf;
borrow_reserve.liquidity.accumulated_referrer_fees_sf += referrer_fee_sf;
Ok(())
}
Fees are tracked in both the ReferrerTokenState and the reserve’s accumulated fees.
Claiming Referrer Fees
withdraw_referrer_fees Instruction
Handler: handler_withdraw_referrer_fees.rs:16
pub fn withdraw_referrer_fees(
ctx: Context<WithdrawReferrerFees>,
) -> Result<()>
Withdraws accumulated referrer fees to your token account.
Withdrawal Logic (lending_operations.rs:2040):
pub fn withdraw_referrer_fees(
reserve: &mut Reserve,
slot: Slot,
referrer_token_state: &mut ReferrerTokenState,
) -> Result<u64> {
// Reserve must be fresh
if reserve.last_update.is_stale(slot, PriceStatusFlags::ALL_CHECKS)? {
return err!(LendingError::ReserveStale);
}
let withdraw_amount = reserve.get_withdraw_referrer_fees(referrer_token_state);
if withdraw_amount == 0 {
return err!(LendingError::InsufficientReferralFeesToRedeem);
}
reserve.withdraw_referrer_fees(withdraw_amount, referrer_token_state)?;
reserve.last_update.mark_stale();
Ok(withdraw_amount)
}
Accounts:
referrer: Signer (must be the referrer owner)
referrer_token_state: PDA ["referrer_token_state", referrer.key(), reserve.key()]
reserve: The reserve to withdraw fees from
reserve_liquidity_mint: The reserve’s token mint
reserve_supply_liquidity: The reserve’s supply vault
referrer_token_account: Your token account (receives fees)
lending_market: The lending market
lending_market_authority: PDA signer
Example:
const claimReferrerFees = async (reserve: PublicKey) => {
// Load accounts
const reserveData = await loadReserve(reserve);
const [referrerTokenState] = PublicKey.findProgramAddressSync(
[
Buffer.from("referrer_token_state"),
wallet.publicKey.toBuffer(),
reserve.toBuffer(),
],
KAMINO_PROGRAM_ID
);
// Get or create token account
const referrerTokenAccount = await getOrCreateAssociatedTokenAccount(
connection,
wallet,
reserveData.liquidity.mintPubkey,
wallet.publicKey
);
// Withdraw fees
const ix = await withdrawReferrerFees({
referrer: wallet.publicKey,
referrerTokenState,
reserve,
reserveLiquidityMint: reserveData.liquidity.mintPubkey,
reserveSupplyLiquidity: reserveData.liquidity.supplyVault,
referrerTokenAccount: referrerTokenAccount.address,
lendingMarket,
lendingMarketAuthority,
});
const tx = new Transaction().add(ix);
const sig = await sendAndConfirmTransaction(connection, tx, [wallet]);
console.log(`Fees claimed! Signature: ${sig}`);
};
Complete Setup and Claiming Example
import { Connection, PublicKey, Transaction } from '@solana/web3.js';
import { KaminoLending } from './kamino-sdk';
class ReferralManager {
constructor(
private connection: Connection,
private wallet: Wallet,
private programId: PublicKey
) {}
// Step 1: One-time setup
async setupReferrer(shortUrl: string) {
// Create referrer state and short URL
const setupIx = await this.createSetupInstruction(shortUrl);
await this.sendTransaction([setupIx]);
console.log(`✓ Referrer state created with URL: ${shortUrl}`);
}
// Step 2: Initialize token states for reserves you want to track
async initializeTokenStates(reserves: PublicKey[]) {
for (const reserve of reserves) {
const ix = await this.createInitTokenStateInstruction(reserve);
await this.sendTransaction([ix]);
console.log(`✓ Token state initialized for reserve: ${reserve}`);
}
}
// Step 3: Monitor unclaimed fees
async getUnclaimedFees(reserve: PublicKey): Promise<number> {
const [referrerTokenState] = PublicKey.findProgramAddressSync(
[
Buffer.from("referrer_token_state"),
this.wallet.publicKey.toBuffer(),
reserve.toBuffer(),
],
this.programId
);
const accountInfo = await this.connection.getAccountInfo(referrerTokenState);
if (!accountInfo) return 0;
// Deserialize ReferrerTokenState
const data = accountInfo.data;
const amountUnclaimedSf = data.readBigUInt64LE(64); // Offset to amount_unclaimed_sf
// Convert scaled fraction to decimal
const unclaimed = Number(amountUnclaimedSf) / (2 ** 60); // Approximate
return unclaimed;
}
// Step 4: Claim all accumulated fees
async claimAllFees(reserves: PublicKey[]) {
for (const reserve of reserves) {
const unclaimed = await this.getUnclaimedFees(reserve);
if (unclaimed > 0) {
const ix = await this.createWithdrawFeesInstruction(reserve);
await this.sendTransaction([ix]);
console.log(`✓ Claimed ${unclaimed} fees from reserve: ${reserve}`);
}
}
}
private async sendTransaction(instructions: TransactionInstruction[]) {
const tx = new Transaction().add(...instructions);
return await sendAndConfirmTransaction(this.connection, tx, [this.wallet]);
}
}
// Usage
const manager = new ReferralManager(connection, wallet, KAMINO_PROGRAM_ID);
// One-time setup
await manager.setupReferrer("my_kamino_link");
await manager.initializeTokenStates([usdcReserve, solReserve, ethReserve]);
// Regular fee claims
setInterval(async () => {
await manager.claimAllFees([usdcReserve, solReserve, ethReserve]);
}, 24 * 60 * 60 * 1000); // Daily
Fee Distribution
Referral fees are a percentage of protocol fees, controlled by lending_market.referral_fee_bps:
// If referral_fee_bps = 2000 (20%)
// And user pays 100 USDC in borrow fees:
const protocolFee = 100;
const referralFeeBps = 2000; // 20%
const referrerFee = protocolFee * (referralFeeBps / 10000);
// referrerFee = 20 USDC to referrer
// 80 USDC to protocol
Best Practices
- Initialize token states early: Set up
ReferrerTokenState for all reserves you expect users to interact with
- Regular claims: Claim fees regularly to compound earnings
- Monitor metrics: Track cumulative vs unclaimed fees to measure referral performance
- User experience: Share your short URL prominently for easy user attribution
- Token account management: Ensure you have token accounts for all mints before claiming
Troubleshooting
InsufficientReferralFeesToRedeem:
- No unclaimed fees available
- Wait for referred users to generate more activity
ShortUrlNotAsciiAlphanumeric:
- Use only letters, numbers, underscores, and hyphens in short URLs
ReserveStale:
- Reserve needs to be refreshed before withdrawing fees
- Ensure you call
refresh_reserve in the same transaction or beforehand