Skip to main content
Learn how to 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

Create a vault configuration with the core identity and fee structure.
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',
  // Optional — defaults shown below:
  minDepositAmount: 1000,        // lamports (default: 1000)
  minWithdrawAmount: 10,         // lamports (default: 10)
  minInvestAmount: 0,            // lamports (default: 0)
  minInvestDelaySlots: 0,        // slots (default: 0)
  withdrawalPenaltyBps: 1,       // basis points (default: 1)
  withdrawalPenaltyLamports: 1,  // lamports (default: 1)
  crankFundFeePerReserve: 1,     // lamports (default: 1)
});
The operational parameters above are optional — omit them to use the defaults. All parameters are editable post-creation using specialized update methods. Unallocated weight and cap are set post-creation only — see the Configure Vault Parameters step below.
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,
  ...instructions.setFarmToVaultIxs,
];
The createVaultIxs method returns a vault signer plus instruction bundles for creating associated token accounts, initializing the vault, creating a lookup table (LUT), initializing share token metadata, and registering the Vault Farm and First Loss Capital Farm addresses on the vault. The farm accounts themselves are created in separate transactions — see the farm creation step below.
5

Build, Send, and Confirm Transaction

Use Kit’s functional pipe pattern to build, sign, and send the transaction. We define a reusable helper since vault creation involves multiple transactions:
// Helper to build, sign, and send a transaction
async function buildAndSendTx(ixs) {
  const { value: blockhash } = await rpc.getLatestBlockhash({ commitment: 'finalized' }).send();
  const signed = await signTransactionMessageWithSigners(
    pipe(
      createTransactionMessage({ version: 0 }),
      (tx) => setTransactionMessageFeePayerSigner(adminSigner, tx),
      (tx) => setTransactionMessageLifetimeUsingBlockhash(blockhash, tx),
      (tx) => appendTransactionMessageInstructions(ixs, tx)
    )
  );
  await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signed, {
    commitment: 'confirmed',
    skipPreflight: true,
  });
}

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

// Populate LUT in background (optimizes future tx sizes, not required)
setImmediate(async () => {
  try {
    await new Promise((resolve) => setTimeout(resolve, 2000));
    await buildAndSendTx(instructions.populateLUTIxs);
  } catch (error) {
    // LUT population failure doesn't affect vault functionality
  }
});
Kit’s pipe function enables functional composition of transaction building steps. signTransactionMessageWithSigners automatically handles all required signers including the vault keypair embedded in the instructions.
6

Create and Attach Farms

The createVaultIxs method generates instructions for the Vault Farm (reward distribution), First Loss Capital Farm (loss-absorbing capital), and Autocompound Farm. Each farm requires two sequential transactions: setup and update.
// Using buildAndSendTx helper from previous step
for (const farm of instructions.createVaultFarms) {
  await buildAndSendTx(farm.setupFarmIxs);  // Create farm account
  await buildAndSendTx(farm.updateFarmIxs);  // Configure farm-side settings
  console.log('Farm created:', farm.farm.address);
}
Farm creation is ideally done during vault initialization. If skipped, farms can be created post-creation using kaminoManager.createVaultFarmIxs(adminSigner, vault) to create the farm account, then registered on the vault using kaminoManager.updateVaultFarmIxs(vault, farm) for the Vault Farm or kaminoManager.updateVaultFirstLossCapitalFarmIxs(vault, farm) for the FLC Farm. See Farms & Rewards for the full post-creation flow.
7

Configure Vault Parameters (Post-Creation)

After the vault is created, configure parameters that are only available post-creation (unallocated weight/cap), or update any parameter set during init. Each method returns { updateVaultConfigIx } — send via buildAndSendTx.
import { KaminoVault } from '@kamino-finance/klend-sdk';

const vault = new KaminoVault(rpc, vaultSigner.address);

// Unallocated weight/cap (post-creation only — not available in KaminoVaultConfig)
const { updateVaultConfigIx: unallocWeightIx } =
  await kaminoManager.updateVaultUnallocatedWeightIxs(vault, 500, adminSigner);
const { updateVaultConfigIx: unallocCapIx } =
  await kaminoManager.updateVaultUnallocatedTokensCapIxs(vault, 2000000, adminSigner);
await buildAndSendTx([unallocWeightIx]);
await buildAndSendTx([unallocCapIx]);

// Any init parameter can also be updated post-creation:
// await kaminoManager.updateVaultMinDepositAmountIxs(vault, 1000, adminSigner)
// await kaminoManager.updateVaultMinWithdrawAmountIxs(vault, 10, adminSigner)
// await kaminoManager.updateVaultMinInvestAmountIxs(vault, 0, adminSigner)
// await kaminoManager.updateVaultMinInvestDelaySlotsIxs(vault, 0, adminSigner)
// await kaminoManager.updateVaultWithdrawalPenaltyBpsIxs(vault, 1, adminSigner)
// await kaminoManager.updateVaultWithdrawalPenaltyLamportsIxs(vault, 1, adminSigner)
// await kaminoManager.updateVaultCrankFundFeePerReserveIxs(vault, 1, adminSigner)

Full Code Example

import {
  createSolanaRpc,
  createSolanaRpcSubscriptions,
  address,
  pipe,
  createTransactionMessage,
  setTransactionMessageFeePayerSigner,
  setTransactionMessageLifetimeUsingBlockhash,
  appendTransactionMessageInstructions,
  signTransactionMessageWithSigners,
  sendAndConfirmTransactionFactory,
  getSignatureFromTransaction,
} from '@solana/kit';
import {
  KaminoManager,
  KaminoVault,
  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);

// Step 1: Create the vault
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',
  // Optional — defaults shown below:
  minDepositAmount: 1000,
  minWithdrawAmount: 10,
  minInvestAmount: 0,
  minInvestDelaySlots: 0,
  withdrawalPenaltyBps: 1,
  withdrawalPenaltyLamports: 1,
  crankFundFeePerReserve: 1,
});

// Helper to build, sign, and send a transaction
async function buildAndSendTx(ixs) {
  const { value: blockhash } = await rpc.getLatestBlockhash({ commitment: 'finalized' }).send();
  const signed = await signTransactionMessageWithSigners(
    pipe(
      createTransactionMessage({ version: 0 }),
      (tx) => setTransactionMessageFeePayerSigner(adminSigner, tx),
      (tx) => setTransactionMessageLifetimeUsingBlockhash(blockhash, tx),
      (tx) => appendTransactionMessageInstructions(ixs, tx)
    )
  );
  await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signed, {
    commitment: 'confirmed',
    skipPreflight: true,
  });
}

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

// Step 1: Create the vault
await buildAndSendTx([
  ...instructions.createAtaIfNeededIxs,
  ...instructions.initVaultIxs,
  instructions.createLUTIx,
  instructions.initSharesMetadataIx,
  ...instructions.setFarmToVaultIxs,
]);
console.log('Vault created! Address:', vaultSigner.address);

// Step 2: Create and attach farms (Vault Farm + First Loss Capital Farm)
for (const farm of instructions.createVaultFarms) {
  await buildAndSendTx(farm.setupFarmIxs);
  await buildAndSendTx(farm.updateFarmIxs);
  console.log('Farm created:', farm.farm.address);
}

// Step 3: Populate LUT in background
setImmediate(async () => {
  try {
    await new Promise((resolve) => setTimeout(resolve, 2000));
    await buildAndSendTx(instructions.populateLUTIxs);
  } catch (error) {
    // LUT population failure doesn't affect vault functionality
  }
});

// Step 4: Configure vault parameters post-creation (unallocated weight/cap)
const vault = new KaminoVault(rpc, vaultSigner.address);

const { updateVaultConfigIx: unallocWeightIx } =
  await kaminoManager.updateVaultUnallocatedWeightIxs(vault, 500, adminSigner);
const { updateVaultConfigIx: unallocCapIx } =
  await kaminoManager.updateVaultUnallocatedTokensCapIxs(vault, 2000000, adminSigner);

// Build and send each config update as a transaction
await buildAndSendTx([unallocWeightIx]);
await buildAndSendTx([unallocCapIx]);