Documentation Index
Fetch the complete documentation index at: https://kamino.com/docs/llms.txt
Use this file to discover all available pages before exploring further.
Deposit with Referral
A UserMetadata account stores the referrer information. The referrer must be included when the account is created. After it is set, the referrer cannot be changed.- TypeScript
- Rust
Import Dependencies
Import the required packages for Solana RPC communication, Kamino SDK operations, and Kit transaction building.import {
createSolanaRpc,
createSolanaRpcSubscriptions,
address,
pipe,
createTransactionMessage,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
appendTransactionMessageInstructions,
signTransactionMessageWithSigners,
sendAndConfirmTransactionFactory,
getSignatureFromTransaction,
some,
} from '@solana/kit';
import {
KaminoMarket,
KaminoAction,
VanillaObligation,
PROGRAM_ID,
parseKeypairFile,
DEFAULT_RECENT_SLOT_DURATION_MS,
} from '@kamino-finance/klend-sdk';
import BN from 'bn.js';
Setup Market and Check Existing Referrer
Load the keypair, initialize the market, and check whether a referrer is already associated with the account.const KEYPAIR_FILE = '/path/to/your/keypair.json';
const signer = await parseKeypairFile(KEYPAIR_FILE);
const rpc = createSolanaRpc('https://api.mainnet-beta.solana.com');
const rpcSubscriptions = createSolanaRpcSubscriptions('wss://api.mainnet-beta.solana.com');
const marketPubkey = address('7u3HeHxYDLhnCoErrtycNokbQYbWGzLs6JSDqGAv5PfF'); // Main Market
const market = await KaminoMarket.load(rpc, marketPubkey, DEFAULT_RECENT_SLOT_DURATION_MS);
const usdcMint = address('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'); // USDC
const depositAmount = new BN(1_000_000); // 1 USDC
// Referrer address - UPDATE THIS with the referrer's wallet address
const referrer = address('EZC9wzVCvihCsCHEMGADYdsRhcpdRYWzSCfqY');
// Check if user already has UserMetadata with a different referrer
const [, currentUserMetadata] = await market!.getUserMetadata(signer.address);
const actualReferrer =
currentUserMetadata && currentUserMetadata.referrer.toString() !== referrer.toString()
? currentUserMetadata.referrer
: referrer;
Once a UserMetadata account has a referrer, it cannot be changed. If a referrer is already set, any new referrer value is ignored, and the original referrer continues to receive commission from the user’s borrowing activity.
Build Deposit Instructions with Referrer
Generate deposit instructions that include the referrer information.let depositAction = await KaminoAction.buildDepositTxns(
market!,
depositAmount,
usdcMint,
signer,
new VanillaObligation(PROGRAM_ID),
true,
undefined,
1_000_000,
true,
false,
{ skipInitialization: false, skipLutCreation: false },
some(actualReferrer)
);
const hasSetup = (depositAction.setupIxs || []).length > 0;
// Combine all instructions
let instructions = [
...(depositAction.setupIxs || []),
...(depositAction.lendingIxs || []),
...(depositAction.cleanupIxs || []),
];
The referrer earns commission from the user’s future borrowing activity. This creates a permanent, non-revocable link between the user and the referrer.
Send Initial Transaction
Fetch the latest blockhash and build the transaction message.const signatures: string[] = [];
try {
const { value: depositBlockhash } = await rpc
.getLatestBlockhash({ commitment: 'finalized' })
.send();
const depositTx = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(signer, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(depositBlockhash, tx),
(tx) => appendTransactionMessageInstructions(instructions, tx)
);
const depositSigned = await signTransactionMessageWithSigners(depositTx);
const depositSignature = getSignatureFromTransaction(depositSigned);
signatures.push(depositSignature);
await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(depositSigned, {
commitment: 'confirmed',
skipPreflight: true,
});
console.log(`Deposit successful! Signature: ${depositSignature}`);
Handle Retry Logic
Detect and handle setup-related errors that may occur.} catch (error: unknown) {
const errorMsg = error instanceof Error ? error.message : String(error);
const is0x17a3 =
errorMsg.includes('0x17a3') ||
errorMsg.includes('6051') ||
errorMsg.includes('IncorrectInstructionInPosition');
if (hasSetup && is0x17a3) {
When setup instructions are included, the first transaction may fail with error 0x17a3. The retry logic handles this by waiting for blockchain state to settle, reloading the market, and rebuilding instructions.
await new Promise((resolve) => setTimeout(resolve, 2000));
const reloadedMarket = await KaminoMarket.load(rpc, marketPubkey, DEFAULT_RECENT_SLOT_DURATION_MS);
depositAction = await KaminoAction.buildDepositTxns(
reloadedMarket!,
depositAmount,
usdcMint,
signer,
new VanillaObligation(PROGRAM_ID),
true,
undefined,
1_000_000,
true,
false,
{ skipInitialization: false, skipLutCreation: false },
some(actualReferrer)
);
instructions = [
...(depositAction.setupIxs || []),
...(depositAction.lendingIxs || []),
...(depositAction.cleanupIxs || []),
];
const { value: retryBlockhash } = await rpc
.getLatestBlockhash({ commitment: 'finalized' })
.send();
const retryTx = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(signer, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(retryBlockhash, tx),
(tx) => appendTransactionMessageInstructions(instructions, tx)
);
const retrySigned = await signTransactionMessageWithSigners(retryTx);
const retrySignature = getSignatureFromTransaction(retrySigned);
signatures.push(retrySignature);
await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(retrySigned, {
commitment: 'confirmed',
skipPreflight: true,
});
console.log(
`Deposit successful! Signature: ${retrySignature}\nSetup transaction: https://solscan.io/tx/${signatures[0]}`
);
} else {
console.error('Deposit failed:', error);
}
}
The deposit with referral is complete. The referrer is now permanently linked to the account and will earn commission from the user’s future borrowing activity.
klend-interface is a lightweight Rust instruction builder — it creates Vec<Instruction> with required refresh instructions prepended automatically. Use ReserveInfo::from_account_data(pubkey, &data) to construct reserve info from raw RPC account bytes. For obligation-based operations, ObligationContext is the recommended approach — it fetches the obligation and its associated reserves, then provides convenient .deposit(), .borrow(), .repay(), and .withdraw() methods.Add Dependencies
[dependencies]
klend-interface = "0.1.0"
solana-pubkey = "2.1"
solana-instruction = "2.1"
solana-sdk = "~2.3"
solana-client = "~2.3"
spl-token = "7"
spl-associated-token-account = "6"
Set Up RPC Client
use solana_client::rpc_client::RpcClient;
use solana_sdk::signer::keypair::read_keypair_file;
use solana_sdk::signer::Signer;
let rpc_client = RpcClient::new("https://api.mainnet-beta.solana.com");
let signer = read_keypair_file("/path/to/your/keypair.json")
.expect("Failed to read keypair file");
let owner = signer.pubkey();
Initialize User with Referrer
Create the user metadata account linked to a referrer before depositing.use klend_interface::helpers;
use klend_interface::pda;
use klend_interface::KLEND_PROGRAM_ID;
use solana_pubkey::Pubkey;
use std::str::FromStr;
let referrer = Pubkey::from_str("EZC9wzVCvihCsCHEMGADYdsRhcpdRYWzSCfqY").unwrap();
// Derive the referrer's user_metadata PDA
let (referrer_user_metadata, _) = pda::user_metadata(&KLEND_PROGRAM_ID, &referrer);
// Your user's address lookup table (created during onboarding)
let user_lookup_table = Pubkey::from_str("YOUR_USER_LOOKUP_TABLE_PUBKEY").unwrap();
let init_ixs = helpers::lifecycle::init_user(
owner,
owner, // fee payer
user_lookup_table,
Some(referrer_user_metadata), // links to this referrer
);
Once a UserMetadata account has a referrer, it cannot be changed. The original referrer permanently earns commission from the user’s borrowing activity.
Build Obligation Context
Derive the obligation PDA and build the context for depositing.use klend_interface::ObligationContext;
let lending_market = Pubkey::from_str("7u3HeHxYDLhnCoErrtycNokbQYbWGzLs6JSDqGAv5PfF").unwrap();
let reserve_pubkey = Pubkey::from_str("D6q6wuQSrifJKZYpR1M8R4YawnLDtDsMmWM1NbBmgJ59").unwrap();
// 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
)?;
Build Deposit Instructions
use spl_associated_token_account::get_associated_token_address;
let liquidity_mint = Pubkey::from_str("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v").unwrap();
let user_source_liquidity = get_associated_token_address(&owner, &liquidity_mint);
let deposit_ixs = ctx.deposit(
owner,
&reserve_pubkey,
user_source_liquidity,
1_000_000, // 1 USDC
)?;
Send Transactions
use solana_sdk::transaction::Transaction;
use solana_sdk::message::Message;
// Transaction 1: Initialize user metadata with referrer
let message = Message::new(&init_ixs, Some(&owner));
let recent_blockhash = rpc_client.get_latest_blockhash()?;
let tx = Transaction::new(&[&signer], message, recent_blockhash);
rpc_client.send_and_confirm_transaction(&tx)?;
// Transaction 2: Deposit with referral
let message = Message::new(&deposit_ixs, Some(&owner));
let recent_blockhash = rpc_client.get_latest_blockhash()?;
let tx = Transaction::new(&[&signer], message, recent_blockhash);
let signature = rpc_client.send_and_confirm_transaction(&tx)?;
println!("Deposit with referral successful! Signature: {signature}");