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.

Refresh Prices

Scope prices are pushed on-chain by permissionless “crank” transactions: anyone can sign and send a refresh, no admin authority required. The Scope SDK builds one instruction that refreshes the specified token slots for a feed. This is the instruction Multiply and flash-loan flows bundle in front of a leverage operation so reserves read fresh prices.
1

Import Dependencies

Import the Scope SDK plus the Solana Kit transaction-building helpers and a keypair loader.
import {
    createSolanaRpc,
    createSolanaRpcSubscriptions,
    pipe,
    createTransactionMessage,
    setTransactionMessageFeePayerSigner,
    setTransactionMessageLifetimeUsingBlockhash,
    appendTransactionMessageInstructions,
    signTransactionMessageWithSigners,
    getSignatureFromTransaction,
    assertIsTransactionWithinSizeLimit,
    sendAndConfirmTransactionFactory,
} from "@solana/kit";
import { parseKeypairFile } from "@kamino-finance/klend-sdk/dist/utils/signer";
import { Scope, SCOPE_MAINNET_HUBBLE_FEED } from "@kamino-finance/scope-sdk";
2

Configure Constants and Initialize Scope

Set the keypair path, RPC endpoints, and the slot indices to refresh.
const KEYPAIR_FILE = "/path/to/your/keypair.json";
const RPC_ENDPOINT = "https://api.mainnet-beta.solana.com";
const WS_ENDPOINT = "wss://api.mainnet-beta.solana.com";

// Slot indices to refresh (e.g. SOL, ETH, BTC on the Hubble feed).
const TOKENS = [0, 1, 2];

const signer = await parseKeypairFile(KEYPAIR_FILE);
const rpc = createSolanaRpc(RPC_ENDPOINT);
const rpcSubscriptions = createSolanaRpcSubscriptions(WS_ENDPOINT);
const scope = new Scope("mainnet-beta", rpc);
3

Build the Refresh Instruction

refreshPriceListIx returns one instruction that refreshes the listed slots, or null if there is nothing to refresh.
const ix = await scope.refreshPriceListIx(
    { config: SCOPE_MAINNET_HUBBLE_FEED.configuration },
    TOKENS,
);
if (!ix) console.log(`Nothing to refresh for tokens [${TOKENS}]`);

console.log(`Built refresh ix: program ${ix.programAddress}, ${ix.accounts?.length ?? 0} accounts`);
The refresh is permissionless: any wallet can pay fees and send it. The Scope program writes the new dated prices to the feed’s OraclePrices account.
4

Build, Sign, and Send Transaction

Compose the v0 transaction, sign, verify it fits the size limit, then send and confirm.
const { value: blockhash } = await rpc
    .getLatestBlockhash({ commitment: "finalized" })
    .send();

const transactionMessage = pipe(
    createTransactionMessage({ version: 0 }),
    (m) => setTransactionMessageFeePayerSigner(signer, m),
    (m) => setTransactionMessageLifetimeUsingBlockhash(blockhash, m),
    (m) => appendTransactionMessageInstructions([ix], m),
);

const signedTransaction = await signTransactionMessageWithSigners(transactionMessage);
assertIsTransactionWithinSizeLimit(signedTransaction);
const signature = getSignatureFromTransaction(signedTransaction);

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

console.log("Refresh successful! Signature:", signature);
The refresh is complete. The targeted slots now have fresh prices and timestamps on-chain.

Full Code Example

import {
    createSolanaRpc,
    createSolanaRpcSubscriptions,
    pipe,
    createTransactionMessage,
    setTransactionMessageFeePayerSigner,
    setTransactionMessageLifetimeUsingBlockhash,
    appendTransactionMessageInstructions,
    signTransactionMessageWithSigners,
    getSignatureFromTransaction,
    assertIsTransactionWithinSizeLimit,
    sendAndConfirmTransactionFactory,
} from "@solana/kit";
import { parseKeypairFile } from "@kamino-finance/klend-sdk/dist/utils/signer";
import { Scope, SCOPE_MAINNET_HUBBLE_FEED } from "@kamino-finance/scope-sdk";

const KEYPAIR_FILE = "/path/to/your/keypair.json";
const RPC_ENDPOINT = "https://api.mainnet-beta.solana.com";
const WS_ENDPOINT = "wss://api.mainnet-beta.solana.com";

const TOKENS = [0, 1, 2];

const signer = await parseKeypairFile(KEYPAIR_FILE);
const rpc = createSolanaRpc(RPC_ENDPOINT);
const rpcSubscriptions = createSolanaRpcSubscriptions(WS_ENDPOINT);
const scope = new Scope("mainnet-beta", rpc);

const ix = await scope.refreshPriceListIx(
    { config: SCOPE_MAINNET_HUBBLE_FEED.configuration },
    TOKENS,
);
if (!ix) console.log(`Nothing to refresh for tokens [${TOKENS}]`);

const { value: blockhash } = await rpc
    .getLatestBlockhash({ commitment: "finalized" })
    .send();

const transactionMessage = pipe(
    createTransactionMessage({ version: 0 }),
    (m) => setTransactionMessageFeePayerSigner(signer, m),
    (m) => setTransactionMessageLifetimeUsingBlockhash(blockhash, m),
    (m) => appendTransactionMessageInstructions([ix], m),
);

const signedTransaction = await signTransactionMessageWithSigners(transactionMessage);
assertIsTransactionWithinSizeLimit(signedTransaction);
const signature = getSignatureFromTransaction(signedTransaction);

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

console.log("Refresh successful! Signature:", signature);