Deposit assets into Kamino Earn vaults to start earning yield. The SDK handles transaction building, instruction creation, and vault interaction.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.
Complete Flow
Implement the full deposit flow either in an off-chain TypeScript client or via on-chain Rust CPI within your Anchor program.- TypeScript
- Rust
- Rust (CPI)
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,
} from '@solana/kit';
import { KaminoVault } from '@kamino-finance/klend-sdk';
import { parseKeypairFile } from '@kamino-finance/klend-sdk/dist/utils/signer.js';
import { Decimal } from 'decimal.js';
@solana/kit provides modern utilities for RPC, transaction building, and signing. @kamino-finance/klend-sdk contains vault operation methods.Load Keypair and Initialize Vault
Load the keypair from file, initialize RPC connections, and create the vault instance.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 vault = new KaminoVault(
rpc,
address('HDsayqAsDWy3QvANGqh2yNraqcD8Fnjgh73Mhb3WRS5E') // USDC vault
);
parseKeypairFile loads an existing keypair from a JSON file.Build Deposit Instructions
Generate deposit instructions for the specified amount.const depositAmount = new Decimal(1.0);
const bundle = await vault.depositIxs(signer, depositAmount);
const instructions = [...(bundle.depositIxs || [])];
if (!instructions.length) {
throw new Error('No instructions returned by Kamino SDK');
}
The
depositIxs method returns the necessary instructions to deposit assets into the vault.Build and Send Transaction
Fetch the latest blockhash and construct the transaction message.const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(signer, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
(tx) => appendTransactionMessageInstructions(instructions, tx)
);
Kit’s
pipe function enables functional composition of transaction building steps for cleaner, more maintainable code.const signedTransaction = await signTransactionMessageWithSigners(transactionMessage);
const signature = getSignatureFromTransaction(signedTransaction);
await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedTransaction, {
commitment: 'confirmed',
skipPreflight: true,
});
console.log('Deposit successful! Signature:', signature);
The deposit is complete. Your assets are now deposited in the vault and earning yield.
A standalone off-chain Rust client that calls the Kamino Vault program directly with your wallet keypair.
Add Dependencies
[dependencies]
solana-client = "3"
solana-instruction = "3"
solana-keypair = "3"
solana-message = "3"
solana-pubkey = "3"
solana-signer = "3"
solana-transaction = "3"
Set Up RPC Client and Wallet
Load the Solana CLI wallet from~/.config/solana/id.json and create an RPC client.use solana_client::rpc_client::RpcClient;
use solana_keypair::read_keypair_file;
use solana_signer::Signer;
let rpc = RpcClient::new("https://api.mainnet-beta.solana.com".to_string());
let home = std::env::var("HOME").unwrap();
let user = read_keypair_file(format!("{}/.config/solana/id.json", home))
.expect("Failed to load wallet from ~/.config/solana/id.json");
The wallet needs at least ~0.01 SOL (transaction fees + ATA rent) and enough USDC to cover the deposit amount configured below.
Define Program IDs and Vault Constants
Hard-code the mainnet program IDs and the Steakhouse USDC vault state, plus its allocated klend reserves and lending markets.use solana_pubkey::{pubkey, Pubkey};
const KVAULT_PROGRAM_ID: Pubkey = pubkey!("KvauGMspG5k6rtzrqqn7WNn3oZdyKqLKwK2XWQ8FLjd");
const KLEND_PROGRAM_ID: Pubkey = pubkey!("KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD");
const TOKEN_PROGRAM: Pubkey = pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
const ATA_PROGRAM: Pubkey = pubkey!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
const SYSTEM_PROGRAM: Pubkey = pubkey!("11111111111111111111111111111111");
const VAULT_STATE: Pubkey = pubkey!("HDsayqAsDWy3QvANGqh2yNraqcD8Fnjgh73Mhb3WRS5E");
const TOKEN_VAULT: Pubkey = pubkey!("CKTEDx5z19CntAB9B66AxuS98S1NuCgMvfpsew7TQwi");
const TOKEN_MINT: Pubkey = pubkey!("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
const SHARES_MINT: Pubkey = pubkey!("7D8C5pDFxug58L9zkwK7bCiDg4kD4AygzbcZUmf5usHS");
const BASE_VAULT_AUTHORITY: Pubkey = pubkey!("AyY6VCkHfTWdFs7SqBbu6AnCqLUhgzVHBzW3WcJu5Jc8");
const RESERVES: &[Pubkey] = &[
pubkey!("Ga4rZytCpq1unD4DbEJ5bkHeUz9g3oh9AAFEi6vSauXp"),
pubkey!("D6q6wuQSrifJKZYpR1M8R4YawnLDtDsMmWM1NbBmgJ59"),
pubkey!("9FRZvAsjDJ6WM8BJ2S45h9PoDCLAq8DNY9zZDX7MyGzT"),
pubkey!("Atj6UREVWa7WxbF2EMKNyfmYUY1U1txughe2gjhcPDCo"),
];
const LENDING_MARKETS: &[Pubkey] = &[
pubkey!("DxXdAyU3kCjnyggvHmY5nAwg5cRbbmdyX3npfDMjjMek"),
pubkey!("7u3HeHxYDLhnCoErrtycNokbQYbWGzLs6JSDqGAv5PfF"),
pubkey!("GMqmFygF5iSm5nkckYU6tieggFcR42SyjkkhK5rswFRs"),
pubkey!("6WEGfej9B9wjxRs6t4BYpb9iCXd8CpTpJ8fVSNzHCC5y"),
];
const DEPOSIT_DISCRIMINATOR: [u8; 8] = [242, 35, 198, 137, 82, 225, 242, 182];
const DEPOSIT_AMOUNT: u64 = 1_000_000;
DEPOSIT_DISCRIMINATOR is the 8-byte instruction selector kvault expects on the wire: the first 8 bytes of sha256("global:deposit"). DEPOSIT_AMOUNT is in raw units — USDC has 6 decimals, so 1_000_000 = 1 USDC.RESERVES and LENDING_MARKETS must be in matching index order — kvault expects each reserve paired with its lending market at the same position in remaining_accounts.Derive User Token Accounts
Compute the user’s ATAs for USDC and kVUSDC, plus the kvault event-authority PDA.let user_token_ata = ata(&user.pubkey(), &TOKEN_MINT);
let user_shares_ata = ata(&user.pubkey(), &SHARES_MINT);
let (event_authority, _) =
Pubkey::find_program_address(&[b"__event_authority"], &KVAULT_PROGRAM_ID);
fn ata(owner: &Pubkey, mint: &Pubkey) -> Pubkey {
Pubkey::find_program_address(
&[owner.as_ref(), TOKEN_PROGRAM.as_ref(), mint.as_ref()],
&ATA_PROGRAM,
)
.0
}
Build and Send Setup Transaction
Create both ATAs idempotently — if they already exist, the instruction is a no-op.use solana_instruction::{AccountMeta, Instruction};
use solana_message::{Message, VersionedMessage};
use solana_transaction::versioned::VersionedTransaction;
let setup_ixs = vec![
ata_create_idempotent_ix(&user.pubkey(), &user.pubkey(), &TOKEN_MINT),
ata_create_idempotent_ix(&user.pubkey(), &user.pubkey(), &SHARES_MINT),
];
let blockhash = rpc.get_latest_blockhash().unwrap();
let setup_msg = Message::new_with_blockhash(&setup_ixs, Some(&user.pubkey()), &blockhash);
let setup_tx = VersionedTransaction::try_new(
VersionedMessage::Legacy(setup_msg),
&[&user],
)
.unwrap();
let sig = rpc.send_and_confirm_transaction(&setup_tx).unwrap();
println!("Setup tx: {}", sig);
fn ata_create_idempotent_ix(payer: &Pubkey, owner: &Pubkey, mint: &Pubkey) -> Instruction {
let ata_addr = ata(owner, mint);
Instruction {
program_id: ATA_PROGRAM,
accounts: vec![
AccountMeta::new(*payer, true),
AccountMeta::new(ata_addr, false),
AccountMeta::new_readonly(*owner, false),
AccountMeta::new_readonly(*mint, false),
AccountMeta::new_readonly(SYSTEM_PROGRAM, false),
AccountMeta::new_readonly(TOKEN_PROGRAM, false),
],
data: vec![1],
}
}
data: vec![1] selects create_idempotent on the SPL Associated Token Account program — discriminator 1 makes the call a no-op when the ATA already exists.Build the Deposit Instruction
Buildkamino_vault::deposit with the 8-byte discriminator, the 13 named accounts, and the reserves + lending markets appended as remaining_accounts.let deposit_ix = build_deposit_ix(
&user.pubkey(),
&user_token_ata,
&user_shares_ata,
&event_authority,
DEPOSIT_AMOUNT,
);
fn build_deposit_ix(
user: &Pubkey,
user_token_ata: &Pubkey,
user_shares_ata: &Pubkey,
event_authority: &Pubkey,
max_amount: u64,
) -> Instruction {
let mut data = Vec::with_capacity(16);
data.extend_from_slice(&DEPOSIT_DISCRIMINATOR);
data.extend_from_slice(&max_amount.to_le_bytes());
let mut metas = vec![
AccountMeta::new(*user, true),
AccountMeta::new(VAULT_STATE, false),
AccountMeta::new(TOKEN_VAULT, false),
AccountMeta::new_readonly(TOKEN_MINT, false),
AccountMeta::new_readonly(BASE_VAULT_AUTHORITY, false),
AccountMeta::new(SHARES_MINT, false),
AccountMeta::new(*user_token_ata, false),
AccountMeta::new(*user_shares_ata, false),
AccountMeta::new_readonly(KLEND_PROGRAM_ID, false),
AccountMeta::new_readonly(TOKEN_PROGRAM, false),
AccountMeta::new_readonly(TOKEN_PROGRAM, false),
AccountMeta::new_readonly(*event_authority, false),
AccountMeta::new_readonly(KVAULT_PROGRAM_ID, false),
];
for r in RESERVES {
metas.push(AccountMeta::new(*r, false));
}
for m in LENDING_MARKETS {
metas.push(AccountMeta::new_readonly(*m, false));
}
Instruction {
program_id: KVAULT_PROGRAM_ID,
accounts: metas,
data,
}
}
The 13 named accounts in order are:
user, vault_state, token_vault, token_mint, base_vault_authority, shares_mint, user_token_ata, user_shares_ata, klend_program, token_program, shares_token_program, event_authority, kvault_program. Instruction data is [discriminator (8 bytes) | max_amount (u64 LE, 8 bytes)].Send the Deposit Transaction and Verify
Send the deposit transaction, then read the resulting kVUSDC balance from the user’s shares ATA.let blockhash = rpc.get_latest_blockhash().unwrap();
let msg = Message::new_with_blockhash(&[deposit_ix], Some(&user.pubkey()), &blockhash);
let tx = VersionedTransaction::try_new(VersionedMessage::Legacy(msg), &[&user]).unwrap();
let sig = rpc.send_and_confirm_transaction(&tx).unwrap();
println!("Deposit tx: {}", sig);
let acc = rpc.get_account(&user_shares_ata).unwrap();
let bal = u64::from_le_bytes(acc.data[64..72].try_into().unwrap());
println!("Shares balance (kVUSDC raw): {}", bal);
The shares balance is parsed from bytes 64..72 of the SPL token account data (the
amount field, u64 little-endian).The deposit is complete. Your USDC has been transferred to the vault, and you’ve received kVUSDC shares redeemable at the current share price.
This example shows how an on-chain Anchor program can CPI into a Kamino Vault to deposit assets. A PDA authority owns the user token and shares vaults and signs the CPI via
invoke_signed.Add Dependencies
[dependencies]
anchor-lang = "0.30"
anchor-spl = "0.30"
solana-program = "2.1"
Define Program IDs and Discriminator
use anchor_lang::prelude::*;
use anchor_lang::solana_program::{
instruction::{AccountMeta, Instruction},
program::invoke_signed,
};
use anchor_spl::token::{Token, TokenAccount};
declare_id!("YourProgram1111111111111111111111111111111111");
const AUTHORITY_SEED: &[u8] = b"vault_authority";
// Mainnet program IDs — see accordion below for devnet equivalents
pub const KVAULT_PROGRAM_ID: Pubkey = pubkey!("KvauGMspG5k6rtzrqqn7WNn3oZdyKqLKwK2XWQ8FLjd");
pub const KLEND_PROGRAM_ID: Pubkey = pubkey!("KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD");
// sha256("global:deposit")[..8]
pub const KVAULT_DEPOSIT_DISCRIMINATOR: [u8; 8] = [242, 35, 198, 137, 82, 225, 242, 182];
Mainnet Program IDs
Mainnet Program IDs
| Constant | Address |
|---|---|
KVAULT_PROGRAM_ID | KvauGMspG5k6rtzrqqn7WNn3oZdyKqLKwK2XWQ8FLjd |
KLEND_PROGRAM_ID | KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD |
Devnet Program IDs
Devnet Program IDs
| Constant | Address |
|---|---|
KVAULT_PROGRAM_ID | devkRngFnfp4gBc5a3LsadgbQKdPo8MSZ4prFiNSVmY |
KLEND_PROGRAM_ID | KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD |
Define the Deposit Handler
#[program]
pub mod vault_deposit {
use super::*;
pub fn deposit<'info>(ctx: Context<'info, Deposit<'info>>, max_amount: u64) -> Result<()> {
let vault_state_key = ctx.accounts.vault_state.key();
let authority_seeds: &[&[u8]] = &[
AUTHORITY_SEED,
vault_state_key.as_ref(),
&[ctx.bumps.authority],
];
Forward Remaining Accounts
Klend reserve allocations live inremaining_accounts. Forward them to the kvault deposit ix unchanged. let remaining_metas: Vec<AccountMeta> = ctx
.remaining_accounts
.iter()
.map(|a| AccountMeta {
pubkey: *a.key,
is_signer: a.is_signer,
is_writable: a.is_writable,
})
.collect();
Build the Deposit Instruction
Pack the discriminator +max_amount (little-endian u64) as instruction data, and assemble the account-meta list in the order kvault expects. let mut data = Vec::with_capacity(16);
data.extend_from_slice(&KVAULT_DEPOSIT_DISCRIMINATOR);
data.extend_from_slice(&max_amount.to_le_bytes());
let mut metas = vec![
AccountMeta::new(ctx.accounts.authority.key(), true),
AccountMeta::new(ctx.accounts.vault_state.key(), false),
AccountMeta::new(ctx.accounts.token_vault.key(), false),
AccountMeta::new_readonly(ctx.accounts.token_mint.key(), false),
AccountMeta::new_readonly(ctx.accounts.base_vault_authority.key(), false),
AccountMeta::new(ctx.accounts.shares_mint.key(), false),
AccountMeta::new(ctx.accounts.user_token_ata.key(), false),
AccountMeta::new(ctx.accounts.user_shares_ata.key(), false),
AccountMeta::new_readonly(ctx.accounts.klend_program.key(), false),
AccountMeta::new_readonly(ctx.accounts.token_program.key(), false),
AccountMeta::new_readonly(ctx.accounts.shares_token_program.key(), false),
AccountMeta::new_readonly(ctx.accounts.event_authority.key(), false),
AccountMeta::new_readonly(ctx.accounts.kvault_program.key(), false),
];
metas.extend(remaining_metas);
let ix = Instruction {
program_id: KVAULT_PROGRAM_ID,
accounts: metas,
data,
};
Invoke Signed and Sign with the PDA
let mut account_infos = vec![
ctx.accounts.authority.to_account_info(),
ctx.accounts.vault_state.to_account_info(),
ctx.accounts.token_vault.to_account_info(),
ctx.accounts.token_mint.to_account_info(),
ctx.accounts.base_vault_authority.to_account_info(),
ctx.accounts.shares_mint.to_account_info(),
ctx.accounts.user_token_ata.to_account_info(),
ctx.accounts.user_shares_ata.to_account_info(),
ctx.accounts.klend_program.to_account_info(),
ctx.accounts.token_program.to_account_info(),
ctx.accounts.shares_token_program.to_account_info(),
ctx.accounts.event_authority.to_account_info(),
ctx.accounts.kvault_program.to_account_info(),
];
account_infos.extend(ctx.remaining_accounts.iter().cloned());
invoke_signed(&ix, &account_infos, &[authority_seeds])?;
Ok(())
}
}
Account Validation Struct
Deposit accounts
#[derive(Accounts)]
pub struct Deposit<'info> {
#[account(
mut,
seeds = [AUTHORITY_SEED, vault_state.key().as_ref()],
bump,
)]
pub authority: SystemAccount<'info>,
#[account(mut, token::authority = authority)]
pub user_token_ata: Account<'info, TokenAccount>,
#[account(mut, token::authority = authority)]
pub user_shares_ata: Account<'info, TokenAccount>,
/// CHECK: Kvault state account.
#[account(mut)]
pub vault_state: UncheckedAccount<'info>,
/// CHECK: Token vault owned by the Kvault program.
#[account(mut)]
pub token_vault: UncheckedAccount<'info>,
/// CHECK: Underlying token mint.
pub token_mint: UncheckedAccount<'info>,
/// CHECK: Kvault base vault authority PDA.
pub base_vault_authority: UncheckedAccount<'info>,
/// CHECK: Shares mint.
#[account(mut)]
pub shares_mint: UncheckedAccount<'info>,
/// CHECK: Event-emit PDA owned by the Kvault program.
pub event_authority: UncheckedAccount<'info>,
/// CHECK: The Kvault program.
#[account(address = KVAULT_PROGRAM_ID)]
pub kvault_program: UncheckedAccount<'info>,
/// CHECK: The Klend program.
#[account(address = KLEND_PROGRAM_ID)]
pub klend_program: UncheckedAccount<'info>,
pub token_program: Program<'info, Token>,
pub shares_token_program: Program<'info, Token>,
}
Full Code Example
- TypeScript
- Rust
- Rust (CPI)
import {
createSolanaRpc,
createSolanaRpcSubscriptions,
address,
pipe,
createTransactionMessage,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
appendTransactionMessageInstructions,
signTransactionMessageWithSigners,
sendAndConfirmTransactionFactory,
getSignatureFromTransaction,
} from '@solana/kit';
import { KaminoVault } from '@kamino-finance/klend-sdk';
import { parseKeypairFile } from '@kamino-finance/klend-sdk/dist/utils/signer.js';
import { Decimal } from 'decimal.js';
// Configuration - UPDATE THESE VALUES
const KEYPAIR_FILE = '/path/to/your/keypair.json';
// Load keypair from file
const signer = await parseKeypairFile(KEYPAIR_FILE);
// Initialize RPC and RPC Subscriptions
const rpc = createSolanaRpc('https://api.mainnet-beta.solana.com');
const rpcSubscriptions = createSolanaRpcSubscriptions('wss://api.mainnet-beta.solana.com');
const vault = new KaminoVault(
rpc,
address('HDsayqAsDWy3QvANGqh2yNraqcD8Fnjgh73Mhb3WRS5E') // USDC vault
);
// Build deposit instructions
const depositAmount = new Decimal(1.0);
const bundle = await vault.depositIxs(signer, depositAmount);
const instructions = [...(bundle.depositIxs || [])];
if (!instructions.length) {
throw new Error('No instructions returned by Kamino SDK');
}
// Build and sign transaction using functional pipe pattern
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(signer, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
(tx) => appendTransactionMessageInstructions(instructions, tx)
);
const signedTransaction = await signTransactionMessageWithSigners(transactionMessage);
// Send and confirm transaction
const signature = getSignatureFromTransaction(signedTransaction);
await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedTransaction, {
commitment: 'confirmed',
skipPreflight: true,
});
console.log('Deposit successful! Signature:', signature);
use solana_client::rpc_client::RpcClient;
use solana_instruction::{AccountMeta, Instruction};
use solana_keypair::read_keypair_file;
use solana_message::{Message, VersionedMessage};
use solana_pubkey::{pubkey, Pubkey};
use solana_signer::Signer;
use solana_transaction::versioned::VersionedTransaction;
const KVAULT_PROGRAM_ID: Pubkey = pubkey!("KvauGMspG5k6rtzrqqn7WNn3oZdyKqLKwK2XWQ8FLjd");
const KLEND_PROGRAM_ID: Pubkey = pubkey!("KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD");
const TOKEN_PROGRAM: Pubkey = pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
const ATA_PROGRAM: Pubkey = pubkey!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
const SYSTEM_PROGRAM: Pubkey = pubkey!("11111111111111111111111111111111");
const VAULT_STATE: Pubkey = pubkey!("HDsayqAsDWy3QvANGqh2yNraqcD8Fnjgh73Mhb3WRS5E");
const TOKEN_VAULT: Pubkey = pubkey!("CKTEDx5z19CntAB9B66AxuS98S1NuCgMvfpsew7TQwi");
const TOKEN_MINT: Pubkey = pubkey!("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
const SHARES_MINT: Pubkey = pubkey!("7D8C5pDFxug58L9zkwK7bCiDg4kD4AygzbcZUmf5usHS");
const BASE_VAULT_AUTHORITY: Pubkey = pubkey!("AyY6VCkHfTWdFs7SqBbu6AnCqLUhgzVHBzW3WcJu5Jc8");
const RESERVES: &[Pubkey] = &[
pubkey!("Ga4rZytCpq1unD4DbEJ5bkHeUz9g3oh9AAFEi6vSauXp"),
pubkey!("D6q6wuQSrifJKZYpR1M8R4YawnLDtDsMmWM1NbBmgJ59"),
pubkey!("9FRZvAsjDJ6WM8BJ2S45h9PoDCLAq8DNY9zZDX7MyGzT"),
pubkey!("Atj6UREVWa7WxbF2EMKNyfmYUY1U1txughe2gjhcPDCo"),
];
const LENDING_MARKETS: &[Pubkey] = &[
pubkey!("DxXdAyU3kCjnyggvHmY5nAwg5cRbbmdyX3npfDMjjMek"),
pubkey!("7u3HeHxYDLhnCoErrtycNokbQYbWGzLs6JSDqGAv5PfF"),
pubkey!("GMqmFygF5iSm5nkckYU6tieggFcR42SyjkkhK5rswFRs"),
pubkey!("6WEGfej9B9wjxRs6t4BYpb9iCXd8CpTpJ8fVSNzHCC5y"),
];
const DEPOSIT_DISCRIMINATOR: [u8; 8] = [242, 35, 198, 137, 82, 225, 242, 182];
const DEPOSIT_AMOUNT: u64 = 1_000_000;
fn main() {
let rpc = RpcClient::new("https://api.mainnet-beta.solana.com".to_string());
let home = std::env::var("HOME").unwrap();
let user = read_keypair_file(format!("{}/.config/solana/id.json", home))
.expect("Failed to load wallet from ~/.config/solana/id.json");
println!("User wallet: {}", user.pubkey());
let user_token_ata = ata(&user.pubkey(), &TOKEN_MINT);
let user_shares_ata = ata(&user.pubkey(), &SHARES_MINT);
let (event_authority, _) =
Pubkey::find_program_address(&[b"__event_authority"], &KVAULT_PROGRAM_ID);
println!("user_token_ata (USDC): {}", user_token_ata);
println!("user_shares_ata (kVUSDC): {}", user_shares_ata);
let setup_ixs = vec![
ata_create_idempotent_ix(&user.pubkey(), &user.pubkey(), &TOKEN_MINT),
ata_create_idempotent_ix(&user.pubkey(), &user.pubkey(), &SHARES_MINT),
];
let blockhash = rpc.get_latest_blockhash().unwrap();
let setup_msg = Message::new_with_blockhash(&setup_ixs, Some(&user.pubkey()), &blockhash);
let setup_tx = VersionedTransaction::try_new(
VersionedMessage::Legacy(setup_msg),
&[&user],
)
.unwrap();
let sig = rpc.send_and_confirm_transaction(&setup_tx).unwrap();
println!("Setup tx: {}", sig);
let deposit_ix = build_deposit_ix(
&user.pubkey(),
&user_token_ata,
&user_shares_ata,
&event_authority,
DEPOSIT_AMOUNT,
);
let blockhash = rpc.get_latest_blockhash().unwrap();
let msg = Message::new_with_blockhash(&[deposit_ix], Some(&user.pubkey()), &blockhash);
let tx = VersionedTransaction::try_new(VersionedMessage::Legacy(msg), &[&user]).unwrap();
let sig = rpc.send_and_confirm_transaction(&tx).unwrap();
println!("Deposit tx: {}", sig);
let acc = rpc.get_account(&user_shares_ata).unwrap();
let bal = u64::from_le_bytes(acc.data[64..72].try_into().unwrap());
println!("Shares balance (kVUSDC raw): {}", bal);
}
fn ata(owner: &Pubkey, mint: &Pubkey) -> Pubkey {
Pubkey::find_program_address(
&[owner.as_ref(), TOKEN_PROGRAM.as_ref(), mint.as_ref()],
&ATA_PROGRAM,
)
.0
}
fn ata_create_idempotent_ix(payer: &Pubkey, owner: &Pubkey, mint: &Pubkey) -> Instruction {
let ata_addr = ata(owner, mint);
Instruction {
program_id: ATA_PROGRAM,
accounts: vec![
AccountMeta::new(*payer, true),
AccountMeta::new(ata_addr, false),
AccountMeta::new_readonly(*owner, false),
AccountMeta::new_readonly(*mint, false),
AccountMeta::new_readonly(SYSTEM_PROGRAM, false),
AccountMeta::new_readonly(TOKEN_PROGRAM, false),
],
data: vec![1],
}
}
fn build_deposit_ix(
user: &Pubkey,
user_token_ata: &Pubkey,
user_shares_ata: &Pubkey,
event_authority: &Pubkey,
max_amount: u64,
) -> Instruction {
let mut data = Vec::with_capacity(16);
data.extend_from_slice(&DEPOSIT_DISCRIMINATOR);
data.extend_from_slice(&max_amount.to_le_bytes());
let mut metas = vec![
AccountMeta::new(*user, true),
AccountMeta::new(VAULT_STATE, false),
AccountMeta::new(TOKEN_VAULT, false),
AccountMeta::new_readonly(TOKEN_MINT, false),
AccountMeta::new_readonly(BASE_VAULT_AUTHORITY, false),
AccountMeta::new(SHARES_MINT, false),
AccountMeta::new(*user_token_ata, false),
AccountMeta::new(*user_shares_ata, false),
AccountMeta::new_readonly(KLEND_PROGRAM_ID, false),
AccountMeta::new_readonly(TOKEN_PROGRAM, false),
AccountMeta::new_readonly(TOKEN_PROGRAM, false),
AccountMeta::new_readonly(*event_authority, false),
AccountMeta::new_readonly(KVAULT_PROGRAM_ID, false),
];
for r in RESERVES {
metas.push(AccountMeta::new(*r, false));
}
for m in LENDING_MARKETS {
metas.push(AccountMeta::new_readonly(*m, false));
}
Instruction {
program_id: KVAULT_PROGRAM_ID,
accounts: metas,
data,
}
}
use anchor_lang::prelude::*;
use anchor_lang::solana_program::{
instruction::{AccountMeta, Instruction},
program::invoke_signed,
};
use anchor_spl::token::{Token, TokenAccount};
declare_id!("YourProgram1111111111111111111111111111111111");
const AUTHORITY_SEED: &[u8] = b"vault_authority";
pub const KVAULT_PROGRAM_ID: Pubkey = pubkey!("KvauGMspG5k6rtzrqqn7WNn3oZdyKqLKwK2XWQ8FLjd");
pub const KLEND_PROGRAM_ID: Pubkey = pubkey!("KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD");
// sha256("global:deposit")[..8]
pub const KVAULT_DEPOSIT_DISCRIMINATOR: [u8; 8] = [242, 35, 198, 137, 82, 225, 242, 182];
#[program]
pub mod vault_deposit {
use super::*;
pub fn deposit<'info>(ctx: Context<'info, Deposit<'info>>, max_amount: u64) -> Result<()> {
let vault_state_key = ctx.accounts.vault_state.key();
let authority_seeds: &[&[u8]] = &[
AUTHORITY_SEED,
vault_state_key.as_ref(),
&[ctx.bumps.authority],
];
let remaining_metas: Vec<AccountMeta> = ctx
.remaining_accounts
.iter()
.map(|a| AccountMeta {
pubkey: *a.key,
is_signer: a.is_signer,
is_writable: a.is_writable,
})
.collect();
let mut data = Vec::with_capacity(16);
data.extend_from_slice(&KVAULT_DEPOSIT_DISCRIMINATOR);
data.extend_from_slice(&max_amount.to_le_bytes());
let mut metas = vec![
AccountMeta::new(ctx.accounts.authority.key(), true),
AccountMeta::new(ctx.accounts.vault_state.key(), false),
AccountMeta::new(ctx.accounts.token_vault.key(), false),
AccountMeta::new_readonly(ctx.accounts.token_mint.key(), false),
AccountMeta::new_readonly(ctx.accounts.base_vault_authority.key(), false),
AccountMeta::new(ctx.accounts.shares_mint.key(), false),
AccountMeta::new(ctx.accounts.user_token_ata.key(), false),
AccountMeta::new(ctx.accounts.user_shares_ata.key(), false),
AccountMeta::new_readonly(ctx.accounts.klend_program.key(), false),
AccountMeta::new_readonly(ctx.accounts.token_program.key(), false),
AccountMeta::new_readonly(ctx.accounts.shares_token_program.key(), false),
AccountMeta::new_readonly(ctx.accounts.event_authority.key(), false),
AccountMeta::new_readonly(ctx.accounts.kvault_program.key(), false),
];
metas.extend(remaining_metas);
let ix = Instruction {
program_id: KVAULT_PROGRAM_ID,
accounts: metas,
data,
};
let mut account_infos = vec![
ctx.accounts.authority.to_account_info(),
ctx.accounts.vault_state.to_account_info(),
ctx.accounts.token_vault.to_account_info(),
ctx.accounts.token_mint.to_account_info(),
ctx.accounts.base_vault_authority.to_account_info(),
ctx.accounts.shares_mint.to_account_info(),
ctx.accounts.user_token_ata.to_account_info(),
ctx.accounts.user_shares_ata.to_account_info(),
ctx.accounts.klend_program.to_account_info(),
ctx.accounts.token_program.to_account_info(),
ctx.accounts.shares_token_program.to_account_info(),
ctx.accounts.event_authority.to_account_info(),
ctx.accounts.kvault_program.to_account_info(),
];
account_infos.extend(ctx.remaining_accounts.iter().cloned());
invoke_signed(&ix, &account_infos, &[authority_seeds])?;
Ok(())
}
}
#[derive(Accounts)]
pub struct Deposit<'info> {
#[account(
mut,
seeds = [AUTHORITY_SEED, vault_state.key().as_ref()],
bump,
)]
pub authority: SystemAccount<'info>,
#[account(mut, token::authority = authority)]
pub user_token_ata: Account<'info, TokenAccount>,
#[account(mut, token::authority = authority)]
pub user_shares_ata: Account<'info, TokenAccount>,
/// CHECK: Kvault state account.
#[account(mut)]
pub vault_state: UncheckedAccount<'info>,
/// CHECK: Token vault owned by the Kvault program.
#[account(mut)]
pub token_vault: UncheckedAccount<'info>,
/// CHECK: Underlying token mint.
pub token_mint: UncheckedAccount<'info>,
/// CHECK: Kvault base vault authority PDA.
pub base_vault_authority: UncheckedAccount<'info>,
/// CHECK: Shares mint.
#[account(mut)]
pub shares_mint: UncheckedAccount<'info>,
/// CHECK: Event-emit PDA owned by the Kvault program.
pub event_authority: UncheckedAccount<'info>,
/// CHECK: The Kvault program.
#[account(address = KVAULT_PROGRAM_ID)]
pub kvault_program: UncheckedAccount<'info>,
/// CHECK: The Klend program.
#[account(address = KLEND_PROGRAM_ID)]
pub klend_program: UncheckedAccount<'info>,
pub token_program: Program<'info, Token>,
pub shares_token_program: Program<'info, Token>,
}