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.

Every reserve needs at least one valid oracle. Oracle configuration lives inside the reserve config under tokenInfo. klend supports three oracle sources, with Scope as the recommended path for most assets.

Supported oracle sources

SourceWhat it isWhen to use
ScopeKamino’s price aggregator. Combines Pyth Core, Switchboard, and direct on-chain feeds into a single chain of indices. Manipulation-resistant.Default choice. Use for almost every asset.
Pyth CoreDirect Pyth price account.Asset is published on Pyth and you want a single-source feed.
SwitchboardDirect Switchboard aggregator.Asset is published on Switchboard but not on Pyth or Scope.
A reserve can configure any or all of the three. If multiple are configured, klend uses them according to the chain semantics defined in tokenInfo.

Required tokenInfo fields

FieldTypeWhat it does
namestring (≤32 bytes UTF-8)Asset symbol
maxAgePriceSecondsu64Reject the spot price if older than this many seconds
maxAgeTwapSecondsu64Reject the TWAP if older than this many seconds. Required if TWAP is enabled
maxTwapDivergenceBpsu64Maximum divergence between spot and TWAP in basis points. 0 disables the guard
heuristic{ lower, upper, exp }Sanity bounds. Reject prices outside [lower * 10^exp, upper * 10^exp]. All zeros disables
blockPriceUsage0 or 1If 1, the reserve is configured but the program rejects any operation needing a price (emergency switch)
Plus exactly one (or more) of:
  • scopeConfiguration — Scope feed pubkey + price chain + TWAP chain
  • pythConfiguration — Pyth price account pubkey
  • switchboardConfiguration — Switchboard price aggregator pubkey + optional TWAP aggregator

Update oracle config via SDK

The most direct path for changing just the Scope oracle on an existing reserve is kaminoManager.updateReserveScopeOracleConfigurationIxs. For broader reserve updates that include oracle changes, use updateReserveIxs with a full ReserveConfig.
1

Initialize KaminoManager and fetch the reserve

import {
  createSolanaRpc,
  createSolanaRpcSubscriptions,
  address,
  pipe,
  createTransactionMessage,
  setTransactionMessageFeePayerSigner,
  setTransactionMessageLifetimeUsingBlockhash,
  appendTransactionMessageInstructions,
  signTransactionMessageWithSigners,
  sendAndConfirmTransactionFactory,
} from '@solana/kit';
import {
  KaminoManager,
  DEFAULT_RECENT_SLOT_DURATION_MS,
  PROGRAM_ID,
  MarketWithAddress,
} from '@kamino-finance/klend-sdk';
import { LendingMarket, Reserve } from '@kamino-finance/klend-sdk/dist/@codegen/klend/accounts';
import { parseKeypairFile } from '@kamino-finance/klend-sdk/dist/utils/signer.js';

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);

const marketAddress = address('<MARKET_ADDRESS>');
const reserveAddress = address('<RESERVE_ADDRESS>');

const market = await LendingMarket.fetch(rpc, marketAddress);
if (!market) throw new Error('Market not found');
const marketWithAddress: MarketWithAddress = { address: marketAddress, state: market };
2

Update Scope oracle configuration

const updateIxs = await kaminoManager.updateReserveScopeOracleConfigurationIxs(
  adminSigner,
  marketWithAddress,
  reserveAddress,
  {
    scopePriceConfigAddress: address('3NJYftD5sjVfxSnUdZ1wVML8f3aC6mp1CXCL6L7TnU8C'),
    scopeChain:     [0, 65535, 65535, 65535],
    scopeTwapChain: [52, 65535, 65535, 65535],
  },
);

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',
  });
}

for (const { ixs } of updateIxs) {
  await buildAndSendTx(ixs);
}
65535 (u16::MAX) is the unused-slot sentinel. A single-hop Scope chain uses [N, 65535, 65535, 65535].
3

Updating Pyth or Switchboard, or the TWAP / staleness guards

For non-Scope sources, or for adjusting TWAP / staleness fields, fetch the reserve’s full config, mutate the relevant tokenInfo fields, and apply via updateReserveIxs:
const reserve = await Reserve.fetch(rpc, reserveAddress);
if (!reserve) throw new Error('Reserve not found');

const newConfig = { ...reserve.config };
newConfig.tokenInfo = {
  ...reserve.config.tokenInfo,
  maxAgePriceSeconds: 120n,
  maxAgeTwapSeconds: 240n,
  maxTwapDivergenceBps: 4050n,
  pythConfiguration: {
    price: address('<PYTH_PRICE_ACCOUNT>'),
  },
};

const updateIxs = await kaminoManager.updateReserveIxs(
  adminSigner,
  marketWithAddress,
  reserveAddress,
  newConfig,
);

for (const { ixs } of updateIxs) {
  await buildAndSendTx(ixs);
}

Looking up oracle IDs

import { ScopeConfig } from '@kamino-finance/scope-sdk';

const scopeConfig = await ScopeConfig.fetch(rpc, address('<SCOPE_CONFIG_PUBKEY>'));
// Inspect scopeConfig.tokensMetadata to find the index for your asset
Or call the kamino-manager CLI’s get-oracle-mappings command (next tab) to retrieve the canonical IDs.

TWAP and staleness guards

The two guards work together to reject manipulated or stale prices.
GuardWhat it catches
maxAgePriceSecondsSpot price older than this is rejected. Set to 120 for liquid assets, lower for high-stakes reserves
maxAgeTwapSecondsTWAP older than this is rejected. Typically 2–4× maxAgePriceSeconds
maxTwapDivergenceBpsIf spot deviates from TWAP by more than this, the price is rejected. Set to 4050 (≈40.5%) for volatile assets, lower for stables. 0 disables
If TWAP is enabled (maxTwapDivergenceBps > 0) the underlying oracle must have a TWAP. Scope and Switchboard expose TWAPs; Pyth Core exposes EMA. The on-chain validator returns InvalidTwapConfig if you enable the guard against a source with no TWAP.

Price heuristic

The heuristic is an absolute price band:
"heuristic": {
  "lower": 9,
  "upper": 11,
  "exp": 1
}
With exp: 1, the price is accepted only when 0.9 ≤ price ≤ 1.1. Useful for stables you expect to peg. Set all zeros to disable.

Block price usage (emergency)

blockPriceUsage: 1 is an emergency switch: the reserve is configured normally but the program refuses any operation that needs the price. Use it when you need to halt a reserve immediately while preparing a real config update.

Choosing for new assets

Asset typeRecommended oracle
Major stablecoins (USDC, USDT, USDS)Scope, with a tight heuristic and TWAP guard
Major liquid assets (SOL, BTC, ETH)Scope
LSTs (mSOL, jitoSOL, INF, etc.)Scope (uses LST → SOL → USD chain)
Long-tail with Pyth coveragePyth Core directly
Long-tail with Switchboard onlySwitchboard
Tokenized off-chain assetsCoordinate with the Kamino team to add to Scope

Reference