> ## 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

> Deposit assets into Kamino Earn vaults

Deposit assets into Kamino Earn vaults to start earning yield. The SDK handles transaction building, instruction creation, and vault interaction.

## Complete Flow

Implement the full deposit flow either in an off-chain TypeScript client or via on-chain Rust CPI within your Anchor program.

<Tabs>
  <Tab title="TypeScript">
    <Steps>
      <Step>
        ### Import Dependencies

        Import the required packages for Solana RPC communication, Kamino SDK operations, and Kit transaction building.

        ```typescript theme={null}
        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';
        ```

        <Note>
          `@solana/kit` provides modern utilities for RPC, transaction building, and signing. `@kamino-finance/klend-sdk` contains vault operation methods.
        </Note>
      </Step>

      <Step>
        ### Load Keypair and Initialize Vault

        Load the keypair from file, initialize RPC connections, and create the vault instance.

        ```typescript theme={null}
        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
        );
        ```

        <Note>
          `parseKeypairFile` loads an existing keypair from a JSON file.
        </Note>
      </Step>

      <Step>
        ### Build Deposit Instructions

        Generate deposit instructions for the specified amount.

        ```typescript theme={null}
        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');
        }
        ```

        <Info>
          The `depositIxs` method returns the necessary instructions to deposit assets into the vault.
        </Info>
      </Step>

      <Step>
        ### Build and Send Transaction

        Fetch the latest blockhash and construct the transaction message.

        ```typescript theme={null}
        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)
        );
        ```

        <Note>
          Kit's `pipe` function enables functional composition of transaction building steps for cleaner, more maintainable code.
        </Note>

        Sign and send the transaction with built-in confirmation.

        ```typescript theme={null}
        const signedTransaction = await signTransactionMessageWithSigners(transactionMessage);

        const signature = getSignatureFromTransaction(signedTransaction);

        await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedTransaction, {
          commitment: 'confirmed',
          skipPreflight: true,
        });

        console.log('Deposit successful! Signature:', signature);
        ```

        <Check>
          The deposit is complete. Your assets are now deposited in the vault and earning yield.
        </Check>
      </Step>
    </Steps>
  </Tab>

  <Tab title="Rust">
    <Info>
      A standalone off-chain Rust client that calls the Kamino Vault program directly with your wallet keypair.
    </Info>

    <Steps>
      <Step>
        ### Add Dependencies

        ```toml theme={null}
        [dependencies]
        solana-client      = "3"
        solana-instruction = "3"
        solana-keypair     = "3"
        solana-message     = "3"
        solana-pubkey      = "3"
        solana-signer      = "3"
        solana-transaction = "3"
        ```
      </Step>

      <Step>
        ### Set Up RPC Client and Wallet

        Load the Solana CLI wallet from `~/.config/solana/id.json` and create an RPC client.

        ```rust theme={null}
        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");
        ```

        <Note>
          The wallet needs at least \~0.01 SOL (transaction fees + ATA rent) and enough USDC to cover the deposit amount configured below.
        </Note>
      </Step>

      <Step>
        ### 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.

        ```rust theme={null}
        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;
        ```

        <Note>
          `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.
        </Note>

        <Warning>
          `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`.
        </Warning>
      </Step>

      <Step>
        ### Derive User Token Accounts

        Compute the user's ATAs for USDC and kVUSDC, plus the kvault event-authority PDA.

        ```rust theme={null}
        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);
        ```

        ```rust theme={null}
        fn ata(owner: &Pubkey, mint: &Pubkey) -> Pubkey {
            Pubkey::find_program_address(
                &[owner.as_ref(), TOKEN_PROGRAM.as_ref(), mint.as_ref()],
                &ATA_PROGRAM,
            )
            .0
        }
        ```
      </Step>

      <Step>
        ### Build and Send Setup Transaction

        Create both ATAs idempotently — if they already exist, the instruction is a no-op.

        ```rust theme={null}
        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);
        ```

        ```rust theme={null}
        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],
            }
        }
        ```

        <Note>
          `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.
        </Note>
      </Step>

      <Step>
        ### Build the Deposit Instruction

        Build `kamino_vault::deposit` with the 8-byte discriminator, the 13 named accounts, and the reserves + lending markets appended as `remaining_accounts`.

        ```rust theme={null}
        let deposit_ix = build_deposit_ix(
            &user.pubkey(),
            &user_token_ata,
            &user_shares_ata,
            &event_authority,
            DEPOSIT_AMOUNT,
        );
        ```

        ```rust theme={null}
        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,
            }
        }
        ```

        <Note>
          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)]`.
        </Note>
      </Step>

      <Step>
        ### Send the Deposit Transaction and Verify

        Send the deposit transaction, then read the resulting kVUSDC balance from the user's shares ATA.

        ```rust theme={null}
        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);
        ```

        <Note>
          The shares balance is parsed from bytes 64..72 of the SPL token account data (the `amount` field, u64 little-endian).
        </Note>

        <Check>
          The deposit is complete. Your USDC has been transferred to the vault, and you've received kVUSDC shares redeemable at the current share price.
        </Check>
      </Step>
    </Steps>
  </Tab>

  <Tab title="Rust (CPI)">
    <Info>
      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`.
    </Info>

    <Steps>
      <Step>
        ### Add Dependencies

        ```toml theme={null}
        [dependencies]
        anchor-lang = "0.30"
        anchor-spl = "0.30"
        solana-program = "2.1"
        ```
      </Step>

      <Step>
        ### Define Program IDs and Discriminator

        ```rust theme={null}
        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];
        ```

        <AccordionGroup>
          <Accordion title="Mainnet Program IDs">
            <br />

            | Constant            | Address                                       |
            | ------------------- | --------------------------------------------- |
            | `KVAULT_PROGRAM_ID` | `KvauGMspG5k6rtzrqqn7WNn3oZdyKqLKwK2XWQ8FLjd` |
            | `KLEND_PROGRAM_ID`  | `KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD` |
          </Accordion>

          <Accordion title="Devnet Program IDs">
            <br />

            | Constant            | Address                                       |
            | ------------------- | --------------------------------------------- |
            | `KVAULT_PROGRAM_ID` | `devkRngFnfp4gBc5a3LsadgbQKdPo8MSZ4prFiNSVmY` |
            | `KLEND_PROGRAM_ID`  | `KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD` |
          </Accordion>
        </AccordionGroup>
      </Step>

      <Step>
        ### Define the Deposit Handler

        ```rust theme={null}
        #[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],
                ];
        ```
      </Step>

      <Step>
        ### Forward Remaining Accounts

        Klend reserve allocations live in `remaining_accounts`. Forward them to the kvault deposit ix unchanged.

        ```rust theme={null}
                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();
        ```
      </Step>

      <Step>
        ### 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.

        ```rust theme={null}
                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,
                };
        ```
      </Step>

      <Step>
        ### Invoke Signed and Sign with the PDA

        ```rust theme={null}
                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(())
            }
        }
        ```
      </Step>

      <Step>
        ### Account Validation Struct

        ```rust expandable title="Deposit accounts" theme={null}
        #[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>,
        }
        ```
      </Step>
    </Steps>
  </Tab>
</Tabs>

#### Full Code Example

<Tabs>
  <Tab title="TypeScript">
    ```typescript expandable theme={null}
    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);
    ```
  </Tab>

  <Tab title="Rust">
    ```rust expandable theme={null}
    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,
        }
    }
    ```
  </Tab>

  <Tab title="Rust (CPI)">
    ```rust expandable theme={null}
    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>,
    }
    ```
  </Tab>
</Tabs>
