Skip to main content
The Oracle Program delivers accurate price data to the protocol by integrating with trusted oracle providers. It calculates exchange rates by combining multiple price sources in a structured sequence, ensuring reliable asset valuations.
NOTEThe Oracle program has been audited by Zenith and Offside.Oracle Program Address: jupnw4B6Eqs7ft6rxpzYLJZYSnrpRgPcr589n5Kv4oc

Hop-Based Oracle System

The system computes exchange rates by processing prices from up to four sources in a sequential chain. Each source contributes to the final rate through multiplication or division, with the option to invert values as needed. The output of each hop becomes the input for the next. For example, to derive the JUPSOL/SOL rate, the system combines JUPSOL/USD and SOL/USD feeds, inverting the latter to obtain USD/SOL.

Supported source types

SourceDescriptionAccounts per hop
PythPyth push oracle feeds1
ChainlinkChainlink Solana feeds1
RedstoneRedstone oracle feeds1
StakePoolSPL Stake Pool (Jito, Marinade, etc.)1
MsolPoolMarinade mSOL pool state1
SinglePoolSPL Single Pool (Helius, Jupiter, Nansen, Emerald, Shift, Kiln)3 (stake, mint, stake program)
JupLendJupLend fToken exchange rate4 (Lending, TokenReserve, RateModel, FTokenMint)
This design enables the system to:
  • Aggregate rates from multiple feeds and providers, reducing dependency on any single source
  • Support diverse asset types including LSTs, stablecoins, and protocol tokens
  • Validate data integrity at each step

Validation

Freshness

Operation typeMax age
User operations (supply, borrow, repay, withdraw)600 seconds (10 minutes)
Liquidations7200 seconds (2 hours)
Pyth and Redstone use Unix timestamp freshness. Chainlink uses slot-based freshness (≈400 ms per slot).

Confidence (Pyth only)

Pyth feeds include a confidence interval. The oracle rejects feeds whose confidence exceeds:
Operation typeMax confidence
User operations2% of price
Liquidations4% of price
Only Pyth validates confidence. Other sources (Chainlink, Redstone, StakePool, MsolPool, SinglePool, JupLend) either derive rates on-chain or only enforce freshness; they do not expose or check a confidence interval.

Providers

ProviderSourceUse case
PythPythSpot prices (SOL, ETH, BTC, JLP, etc.)
ChainlinkChainlinkSOL/USD, WBTC/USD, EURC/USD
RedstoneRedstoneAdditional price feeds
MarinadeMsolPoolmSOL/SOL
JitoStakePoolJITOSOL/SOL
SPL Single PoolSinglePoolStaked SOL (Helius, Jupiter, Nansen, Emerald, Shift, Kiln)
JupLendJupLendfToken exchange rates (e.g., JUPUSD)

Oracle Verification Script

Use this script to verify oracle prices by providing a nonce. The oracle PDA is derived from the oracle seed and a 16-bit nonce.
For oracles that use SinglePool or JupLend sources, remainingAccounts must include 3 or 4 consecutive source accounts per hop respectively, as configured in the oracle. The script below works for simple oracles (e.g., single Pyth or Chainlink feed).
import { Program, AnchorProvider } from "@coral-xyz/anchor";
import { Connection, PublicKey } from "@solana/web3.js";
import axios from "axios";

const NONCE = 2; // Change this to test different oracles
const ORACLE_PROGRAM_ID = new PublicKey("jupnw4B6Eqs7ft6rxpzYLJZYSnrpRgPcr589n5Kv4oc");
const IDL_URL = "https://raw.githubusercontent.com/jup-ag/jupiter-lend/refs/heads/main/target/idl/oracle.json";
const RPC_URL = "<RPC URL>";

class OracleReader {
  private program: Program;

  private constructor(program: Program) {
    this.program = program;
  }

  static async create(): Promise<OracleReader> {
    const connection = new Connection(RPC_URL);
    const response = await axios.get(IDL_URL);
    const idl = response.data;

    const wallet = {
      signTransaction: () => { throw new Error("Read-only"); },
      signAllTransactions: () => { throw new Error("Read-only"); },
      publicKey: new PublicKey("11111111111111111111111111111111"),
    };

    const provider = new AnchorProvider(connection, wallet, {});
    const program = new Program(idl, provider);
    return new OracleReader(program);
  }

  private findOraclePDA(nonce: number): PublicKey {
    const [pda] = PublicKey.findProgramAddressSync(
      [
        Buffer.from("oracle"),
        Buffer.from(new Uint8Array(new Uint16Array([nonce]).buffer)),
      ],
      ORACLE_PROGRAM_ID
    );
    return pda;
  }

  async getPrice(nonce: number) {
    const oraclePDA = this.findOraclePDA(nonce);
    const oracleAccount = await this.program.account.oracle.fetch(oraclePDA);

    const remainingAccounts = oracleAccount.sources.map((source: any) => ({
      pubkey: source.source,
      isWritable: false,
      isSigner: false,
    }));

    const price = await this.program.methods
      .getExchangeRateOperate(nonce)
      .accounts({ oracle: oraclePDA })
      .remainingAccounts(remainingAccounts)
      .view();

    return {
      nonce,
      oraclePDA: oraclePDA.toString(),
      price: price.toString(),
      sources: oracleAccount.sources.length
    };
  }
}

async function main() {
  const reader = await OracleReader.create();
  const result = await reader.getPrice(NONCE);
  console.log(result);
}

main().catch(console.error);