Skip to main content

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.

A reserve is the per-asset lending pool inside a market. Each reserve holds one mint, has its own LTV / liquidation threshold / IR curve, and connects to one oracle. A market without reserves accepts nothing; reserves are how you list assets.
Each (mint, market) pair can have at most one reserve. If you need multiple reserves for the same asset (e.g., separate fixed-term tracks), each one needs its own market.

Prerequisites

ItemWhere to get it
Market addressOutput of Create a market
Token mint addressThe SPL Token or Token-2022 mint of the asset
Mint program IDTokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA for SPL Token, TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb for Token-2022
Oracle IDScope feed pubkey, Pyth price account, or Switchboard aggregator (see Configure oracles)
Risk parametersLTV, liquidation threshold, IR curve, caps (see Risk parameters)

Add a reserve via SDK

The SDK exposes kaminoManager.addAssetToMarketIxs(params) which returns instructions for both creating the reserve account and applying its initial config. Build the asset config using AssetReserveConfig (or AssetReserveConfigCli if you want to load a JSON-shaped config).
1

Import dependencies

import {
  createSolanaRpc,
  createSolanaRpcSubscriptions,
  address,
  generateKeyPairSigner,
  pipe,
  createTransactionMessage,
  setTransactionMessageFeePayerSigner,
  setTransactionMessageLifetimeUsingBlockhash,
  appendTransactionMessageInstructions,
  signTransactionMessageWithSigners,
  sendAndConfirmTransactionFactory,
} from '@solana/kit';
import {
  KaminoManager,
  AssetReserveConfig,
  DEFAULT_RECENT_SLOT_DURATION_MS,
  PROGRAM_ID,
} 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 { findAssociatedTokenPda } from '@solana-program/token-2022';
import { Decimal } from 'decimal.js';
2

Initialize KaminoManager

const adminSigner = await parseKeypairFile('/path/to/admin.json');

const rpc = createSolanaRpc('https://api.mainnet-beta.solana.com');
const rpcSubscriptions = createSolanaRpcSubscriptions('wss://api.mainnet-beta.solana.com');

const kaminoManager = new KaminoManager(rpc, DEFAULT_RECENT_SLOT_DURATION_MS, PROGRAM_ID);
3

Build the asset config

const assetConfig = new AssetReserveConfig({
  mint: address('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'),     // USDC
  mintTokenProgram: TOKEN_PROGRAM_ADDRESS,
  tokenName: 'USDC',
  mintDecimals: 6,
  priceFeed: {
    scopePriceConfigAddress: address('<SCOPE_ORACLE_PRICES_PUBKEY>'),
    scopeChain: [0],                                                  // Scope chain indices
    scopeTwapChain: [52],
  },
  loanToValuePct: 90,
  liquidationThresholdPct: 92,
  borrowRateCurve: {
    points: [
      { utilizationRateBps: 0,     borrowRateBps: 0 },
      { utilizationRateBps: 7000,  borrowRateBps: 400 },
      { utilizationRateBps: 9000,  borrowRateBps: 1500 },
      { utilizationRateBps: 10000, borrowRateBps: 5000 },
      // pad to 11 points by repeating the final point
      { utilizationRateBps: 10000, borrowRateBps: 5000 },
      { utilizationRateBps: 10000, borrowRateBps: 5000 },
      { utilizationRateBps: 10000, borrowRateBps: 5000 },
      { utilizationRateBps: 10000, borrowRateBps: 5000 },
      { utilizationRateBps: 10000, borrowRateBps: 5000 },
      { utilizationRateBps: 10000, borrowRateBps: 5000 },
      { utilizationRateBps: 10000, borrowRateBps: 5000 },
    ],
  },
  depositLimit: new Decimal(1_000_000),
  borrowLimit:  new Decimal(900_000),
});
For full control over every reserve field (including elevation-groups, fixed-term, withdrawal caps, etc.), use AssetReserveConfigCli and load a ReserveConfig JSON object:
import { AssetReserveConfigCli } from '@kamino-finance/klend-sdk';
import { ReserveConfig } from '@kamino-finance/klend-sdk/dist/@codegen/klend/types';

const reserveConfig: ReserveConfig = /* fully-populated ReserveConfig object */;
const assetConfig = new AssetReserveConfigCli(
  address('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'),
  TOKEN_PROGRAM_ADDRESS,
  reserveConfig,
);
4

Generate the reserve keypair and admin liquidity source

const reserveKeypair = await generateKeyPairSigner();

// The admin's associated token account for the asset (used to seed the reserve at init)
const [adminLiquiditySource] = await findAssociatedTokenPda({
  mint: assetConfig.mint,
  owner: adminSigner.address,
  tokenProgram: TOKEN_PROGRAM_ADDRESS,
});
5

Build and submit the transactions

addAssetToMarketIxs returns two batches of instructions — one for creating the reserve account, another for applying the config. Submit them in order.
const { createReserveIxs, configUpdateIxs } = await kaminoManager.addAssetToMarketIxs({
  admin: adminSigner,
  adminLiquiditySource,
  marketAddress: address('<MARKET_ADDRESS>'),
  assetConfig,
  reserveKeypair,
});

async function buildAndSendTx(ixs: any[]) {
  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',
  });
}

// Step 1: create the reserve account
await buildAndSendTx(createReserveIxs);

// Step 2: apply the config (may be split across multiple transactions for size reasons)
for (const { ixs } of configUpdateIxs) {
  await buildAndSendTx(ixs);
}

console.log('Reserve created:', reserveKeypair.address);
configUpdateIxs is an array because a full config update may exceed a single transaction’s size limit; the SDK splits it into chunks. Submit each chunk in order.

Cloning an existing reserve config via SDK

To spawn a sibling reserve with the same parameters as an existing one, fetch the source’s config and pass it through AssetReserveConfigCli:
import { Reserve } from '@kamino-finance/klend-sdk/dist/@codegen/klend/accounts';

const sourceReserve = await Reserve.fetch(rpc, address('<EXISTING_RESERVE_ADDRESS>'));
if (!sourceReserve) throw new Error('Reserve not found');

const clonedConfig = sourceReserve.config;
// Update tokenInfo and any reserve-specific fields before applying
clonedConfig.tokenInfo.name = encodeTokenName('NEW_TOKEN');
clonedConfig.tokenInfo.scopeConfiguration.priceFeed = address('<NEW_SCOPE_FEED>');

const newAssetConfig = new AssetReserveConfigCli(
  address('<NEW_TOKEN_MINT>'),
  TOKEN_PROGRAM_ADDRESS,
  clonedConfig,
);
// Use newAssetConfig in addAssetToMarketIxs

What the program does on-chain

  1. Allocates a new Reserve zero-copy account
  2. Initializes its ReserveConfig from your input
  3. Sets Reserve.lending_market to your market address
  4. Validates that the oracle accounts referenced in tokenInfo actually match the configured pubkeys
  5. Initializes the reserve’s liquidity and collateral substructures (zero balances)
  6. Initializes the reserve’s WithdrawQueue (empty)
The reserve immediately accepts deposits if status: 0 (Active) and depositLimit > 0. Borrows additionally require borrowLimit > 0.

Verify

After creation:
ChannelHow
SDKReserve.fetch(rpc, reserveAddress) and inspect config
CLIyarn kamino-manager download-reserve-config --reserve <RESERVE_ADDRESS>
APIGET https://api.kamino.finance/kamino-market/<MARKET>/reserves/metrics
Confirm:
  • status: 0
  • tokenInfo.scopeConfiguration.priceFeed (or Pyth / Switchboard) is populated
  • borrowRateCurve.points[0].utilizationRateBps == 0
  • depositLimit > 0 and borrowLimit > 0 if you want both directions usable

Common errors

ErrorCause
InvalidOracleConfigThe oracle account in tokenInfo doesn’t match the on-chain account passed to the instruction. Run get-oracle-mappings.
InvalidPythPriceAccount / InvalidSwitchboardAccount / InvalidScopePriceAccountSame as above, oracle-specific.
InvalidTwapConfigTWAP enabled (maxTwapDivergenceBps > 0) but maxAgeTwapSeconds == 0, or the configured oracle source has no TWAP.
Reserve already exists for this mintThis (mint, market) pair already has a reserve. Use update-reserve-config instead.

What’s next

Configure oracles

Pick the right oracle source and set TWAP and staleness guards.

Risk parameters

Choose LTV, thresholds, IR curves, and caps for the asset.