Skip to main content
Create a Kamino vault with custom fee structures, deposit limits, and allocation strategies. The SDK manages vault initialization, metadata setup, and lookup table creation to optimize future transactions.

Creating a Vault

Initialize a new vault with custom configuration and prepare it for user deposits.
1

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 {
  KaminoManager,
  KaminoVaultConfig,
  getMedianSlotDurationInMsFromLastEpochs,
} from '@kamino-finance/klend-sdk';
import { parseKeypairFile } from '@kamino-finance/klend-sdk/dist/utils/signer.js';
import { TOKEN_PROGRAM_ADDRESS } from '@solana-program/token';
import { Decimal } from 'decimal.js';
2

Load Admin Keypair and Initialize Manager

Load the admin keypair from file and initialize the Kamino manager with proper slot timing.
const KEYPAIR_FILE = '/path/to/your/keypair.json';

const adminSigner = await parseKeypairFile(KEYPAIR_FILE);

const rpc = createSolanaRpc('https://api.mainnet-beta.solana.com');
const rpcSubscriptions = createSolanaRpcSubscriptions('wss://api.mainnet-beta.solana.com');
const slotDuration = await getMedianSlotDurationInMsFromLastEpochs();
const kaminoManager = new KaminoManager(rpc, slotDuration);
parseKeypairFile loads an existing keypair from a JSON file that will become the vault admin. The slot duration is used for accurate vault timing calculations.
3

Configure Vault Parameters

Create a vault configuration with fee structures, deposit limits, and allocation settings.
const kaminoVaultConfig = new KaminoVaultConfig({
  admin: adminSigner,
  tokenMint: address('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'),
  tokenMintProgramId: TOKEN_PROGRAM_ADDRESS,
  performanceFeeRatePercentage: new Decimal(15.0),
  managementFeeRatePercentage: new Decimal(2.0),
  name: 'MyCustomVault',
  vaultTokenSymbol: 'USDC',
  vaultTokenName: 'MyCustomVaultToken',
  minDepositAmount: new Decimal(1000000),
  minWithdrawAmount: new Decimal(1000000),
  minInvestAmount: new Decimal(1000000),
  minInvestDelaySlots: 150,
  unallocatedWeight: 500,
  unallocatedTokensCap: new Decimal(2000000),
} as any);
The configuration sets fee percentages, minimum amounts for deposits/withdrawals, and allocation parameters. Fees are collected by the vault admin, and the unallocated weight determines how funds are distributed across lending markets.
4

Generate Vault Creation Instructions

Pass the vault configuration to the Kamino manager to generate all required initialization instructions.
const { vault: vaultSigner, initVaultIxs: instructions } = await kaminoManager.createVaultIxs(kaminoVaultConfig);

const allInstructions = [
  ...instructions.createAtaIfNeededIxs,
  ...instructions.initVaultIxs,
  instructions.createLUTIx,
  instructions.initSharesMetadataIx,
];
The createVaultIxs method takes the vault configuration from Step 3 and returns a vault signer (the vault’s address and keypair) plus instruction bundles for creating associated token accounts, initializing the vault, creating a lookup table (LUT), and initializing share token metadata.
5

Build and Sign Transaction

Use Kit’s functional pipe pattern to build and sign the transaction with a fresh blockhash.
const { value: latestBlockhash } = await rpc
  .getLatestBlockhash({
    commitment: 'finalized',
  })
  .send();

const transactionMessage = pipe(
  createTransactionMessage({ version: 0 }),
  (tx) => setTransactionMessageFeePayerSigner(adminSigner, tx),
  (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
  (tx) => appendTransactionMessageInstructions(allInstructions, tx)
);

const signedTransaction = await signTransactionMessageWithSigners(transactionMessage);
Kit’s pipe function enables functional composition of transaction building steps. The signTransactionMessageWithSigners automatically handles all required signers including the vault keypair embedded in the instructions.
6

Send and Confirm Transaction

Send the transaction with built-in confirmation. Once confirmed, the vault is fully created and functional. The vault address is available from vaultSigner.address.
const signature = getSignatureFromTransaction(signedTransaction);

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

console.log('Vault creation successful! Vault ID:', vaultSigner.address);

setImmediate(async () => {
  try {
    await new Promise((resolve) => setTimeout(resolve, 2000));

    const { value: lutBlockhash } = await rpc
      .getLatestBlockhash({
        commitment: 'finalized',
      })
      .send();

    const lutMessage = pipe(
      createTransactionMessage({ version: 0 }),
      (tx) => setTransactionMessageFeePayerSigner(adminSigner, tx),
      (tx) => setTransactionMessageLifetimeUsingBlockhash(lutBlockhash, tx),
      (tx) => appendTransactionMessageInstructions(instructions.populateLUTIxs, tx)
    );

    const signedLutTx = await signTransactionMessageWithSigners(lutMessage);
    const lutSignature = getSignatureFromTransaction(signedLutTx);

    await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedLutTx, {
      commitment: 'confirmed',
      skipPreflight: true,
    });
  } catch (error) {
    // LUT population failure doesn't affect vault functionality
  }
});

Full Code Example

import {
  createSolanaRpc,
  createSolanaRpcSubscriptions,
  address,
  pipe,
  createTransactionMessage,
  setTransactionMessageFeePayerSigner,
  setTransactionMessageLifetimeUsingBlockhash,
  appendTransactionMessageInstructions,
  signTransactionMessageWithSigners,
  sendAndConfirmTransactionFactory,
  getSignatureFromTransaction,
} from '@solana/kit';
import {
  KaminoManager,
  KaminoVaultConfig,
  getMedianSlotDurationInMsFromLastEpochs,
} from '@kamino-finance/klend-sdk';
import { parseKeypairFile } from '@kamino-finance/klend-sdk/dist/utils/signer.js';
import { TOKEN_PROGRAM_ADDRESS } from '@solana-program/token';
import { Decimal } from 'decimal.js';

const KEYPAIR_FILE = '/path/to/your/keypair.json';

const adminSigner = await parseKeypairFile(KEYPAIR_FILE);

const rpc = createSolanaRpc('https://api.mainnet-beta.solana.com');
const rpcSubscriptions = createSolanaRpcSubscriptions('wss://api.mainnet-beta.solana.com');
const slotDuration = await getMedianSlotDurationInMsFromLastEpochs();
const kaminoManager = new KaminoManager(rpc, slotDuration);

const kaminoVaultConfig = new KaminoVaultConfig({
  admin: adminSigner,
  tokenMint: address('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'),
  tokenMintProgramId: TOKEN_PROGRAM_ADDRESS,
  performanceFeeRatePercentage: new Decimal(15.0),
  managementFeeRatePercentage: new Decimal(2.0),
  name: 'MyCustomVault',
  vaultTokenSymbol: 'USDC',
  vaultTokenName: 'MyCustomVaultToken',
  minDepositAmount: new Decimal(1000000),
  minWithdrawAmount: new Decimal(1000000),
  minInvestAmount: new Decimal(1000000),
  minInvestDelaySlots: 150,
  unallocatedWeight: 500,
  unallocatedTokensCap: new Decimal(2000000),
} as any);

const { vault: vaultSigner, initVaultIxs: instructions } = await kaminoManager.createVaultIxs(kaminoVaultConfig);

const allInstructions = [
  ...instructions.createAtaIfNeededIxs,
  ...instructions.initVaultIxs,
  instructions.createLUTIx,
  instructions.initSharesMetadataIx,
];

const { value: latestBlockhash } = await rpc
  .getLatestBlockhash({
    commitment: 'finalized',
  })
  .send();

const transactionMessage = pipe(
  createTransactionMessage({ version: 0 }),
  (tx) => setTransactionMessageFeePayerSigner(adminSigner, tx),
  (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
  (tx) => appendTransactionMessageInstructions(allInstructions, tx)
);

const signedTransaction = await signTransactionMessageWithSigners(transactionMessage);

const signature = getSignatureFromTransaction(signedTransaction);

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

console.log('Vault creation successful! Vault ID:', vaultSigner.address);

setImmediate(async () => {
  try {
    await new Promise((resolve) => setTimeout(resolve, 2000));

    const { value: lutBlockhash } = await rpc
      .getLatestBlockhash({
        commitment: 'finalized',
      })
      .send();

    const lutMessage = pipe(
      createTransactionMessage({ version: 0 }),
      (tx) => setTransactionMessageFeePayerSigner(adminSigner, tx),
      (tx) => setTransactionMessageLifetimeUsingBlockhash(lutBlockhash, tx),
      (tx) => appendTransactionMessageInstructions(instructions.populateLUTIxs, tx)
    );

    const signedLutTx = await signTransactionMessageWithSigners(lutMessage);
    const lutSignature = getSignatureFromTransaction(signedLutTx);

    await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedLutTx, {
      commitment: 'confirmed',
      skipPreflight: true,
    });
  } catch (error) {
    // LUT population failure doesn't affect vault functionality
  }
});