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

# Configure Oracles

> Wire each reserve to a price feed with the right TWAP and staleness guards

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

| Source          | What it is                                                                                                                                    | When to use                                                   |
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
| **Scope**       | Kamino'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 Core**   | Direct Pyth price account.                                                                                                                    | Asset is published on Pyth and you want a single-source feed. |
| **Switchboard** | Direct 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

| Field                  | Type                     | What it does                                                                                               |
| ---------------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------- |
| `name`                 | string (≤32 bytes UTF-8) | Asset symbol                                                                                               |
| `maxAgePriceSeconds`   | u64                      | Reject the spot price if older than this many seconds                                                      |
| `maxAgeTwapSeconds`    | u64                      | Reject the TWAP if older than this many seconds. Required if TWAP is enabled                               |
| `maxTwapDivergenceBps` | u64                      | Maximum 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                |
| `blockPriceUsage`      | `0` or `1`               | If `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

<Tabs>
  <Tab title="SDK">
    ## 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`.

    <Steps>
      <Step title="Initialize KaminoManager and fetch the reserve">
        ```typescript theme={null}
        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 };
        ```
      </Step>

      <Step title="Update Scope oracle configuration">
        ```typescript theme={null}
        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]`.
      </Step>

      <Step title="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`:

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

    ### Looking up oracle IDs

    ```typescript theme={null}
    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.
  </Tab>

  <Tab title="API">
    <Info>
      **Oracle configuration is not available via the REST API.** The API serves read access to oracle-derived prices and reserve metrics; oracle configuration is an admin operation available through the **SDK** or **Kamino CLI**.
    </Info>

    To update a reserve's oracle config, use the **SDK** or **Kamino CLI** tabs.

    To read current price data via API, see the [API reference](/build/api-reference/introduction).
  </Tab>

  <Tab title="Kamino CLI">
    ## Configure oracles via CLI

    Oracle configuration sits inside the reserve config JSON under the `tokenInfo` key. Edit it like any other reserve field, then apply via `update-reserve-config`. For a focused command that only updates the Scope oracle, use the SDK (no dedicated CLI command exists).

    ### Find the right oracle IDs

    ```bash theme={null}
    yarn kamino-manager get-oracle-mappings
    ```

    Returns the full set of Scope feed pubkeys and indices, Pyth price accounts, and Switchboard aggregators that klend supports for known mints. Copy the IDs for your asset into the reserve config.

    For an asset that isn't in `get-oracle-mappings`:

    | Source                     | What to do                                                                                              |
    | -------------------------- | ------------------------------------------------------------------------------------------------------- |
    | Asset is on Pyth           | Look up the price account on [pyth.network](https://pyth.network) → use directly in `pythConfiguration` |
    | Asset is on Switchboard    | Look up the aggregator on the Switchboard explorer → use directly in `switchboardConfiguration`         |
    | Asset has no public oracle | Coordinate with the Kamino team to add it to Scope before listing                                       |

    ### Scope configuration

    ```json theme={null}
    "tokenInfo": {
      "name": "USDC",
      "maxAgePriceSeconds": 120,
      "maxAgeTwapSeconds": 240,
      "maxTwapDivergenceBps": 4050,
      "scopeConfiguration": {
        "priceFeed": "3NJYftD5sjVfxSnUdZ1wVML8f3aC6mp1CXCL6L7TnU8C",
        "priceChain": [0, 65535, 65535, 65535],
        "twapChain": [52, 65535, 65535, 65535]
      }
    }
    ```

    | Field        | What it is                                                                                     |
    | ------------ | ---------------------------------------------------------------------------------------------- |
    | `priceFeed`  | The Scope `OraclePrices` account pubkey                                                        |
    | `priceChain` | Up to four `u16` indices into the Scope feed; the first non-`65535` entry is the primary price |
    | `twapChain`  | Same shape, for the TWAP indices                                                               |

    `65535` is the sentinel for "unused slot." A single-hop price uses `[N, 65535, 65535, 65535]`.

    ### Pyth configuration

    ```json theme={null}
    "pythConfiguration": {
      "price": "<PYTH_PRICE_ACCOUNT_PUBKEY>"
    }
    ```

    ### Switchboard configuration

    ```json theme={null}
    "switchboardConfiguration": {
      "priceAggregator": "<SWITCHBOARD_PRICE_AGGREGATOR>",
      "twapAggregator": "<SWITCHBOARD_TWAP_AGGREGATOR>"
    }
    ```

    The TWAP aggregator is required if `maxTwapDivergenceBps > 0`.

    ### Apply the update

    ```bash theme={null}
    # Download the current reserve config
    yarn kamino-manager download-reserve-config \
      --reserve <RESERVE_ADDRESS> \
      --output ./configs/<RESERVE>.json

    # Edit ./configs/<RESERVE>.json — update tokenInfo

    # Inspect the change
    yarn kamino-manager update-reserve-config \
      --reserve <RESERVE_ADDRESS> \
      --reserve-config-path ./configs/<RESERVE>.json \
      --mode inspect

    # Apply
    yarn kamino-manager update-reserve-config \
      --reserve <RESERVE_ADDRESS> \
      --reserve-config-path ./configs/<RESERVE>.json \
      --mode execute
    ```
  </Tab>
</Tabs>

## TWAP and staleness guards

The two guards work together to reject manipulated or stale prices.

| Guard                  | What it catches                                                                                                                                  |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `maxAgePriceSeconds`   | Spot price older than this is rejected. Set to `120` for liquid assets, lower for high-stakes reserves                                           |
| `maxAgeTwapSeconds`    | TWAP older than this is rejected. Typically 2–4× `maxAgePriceSeconds`                                                                            |
| `maxTwapDivergenceBps` | If 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:

```json theme={null}
"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 type                           | Recommended 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 coverage         | Pyth Core directly                              |
| Long-tail with Switchboard only      | Switchboard                                     |
| Tokenized off-chain assets           | Coordinate with the Kamino team to add to Scope |

## Reference

* [Reserve config reference](/curators/markets/reserve-parameters) — every `tokenInfo` field documented
* [Oracle architecture overview](/security/oracles) — Scope, price protection, manipulation resistance
