All articles
solanapythoracledefi

Pyth: the on-chain price layer, Push vs Pull, and the price account schema

Pyth aggregates publisher-submitted prices and publishes them on-chain. Here's the price account shape, Push vs Pull models, and how to read a feed in code.

Pyth is the price oracle that most major Solana DeFi protocols (Drift, Kamino, marginfi, MarginFi, etc.) consume. It aggregates prices submitted by ~100 first-party publishers — exchanges, market makers, trading firms — into a single price account per symbol, published on-chain every Solana slot.

Two distribution models matter: Push (Solana- native, account-based) and Pull (cross-chain, message-based). Same data, different integration shape.

The publisher → aggregate flow

Every Pyth symbol has a fixed set of authorised publishers. Each slot, each publisher submits its current price + a confidence interval to the Pyth oracle program. The program aggregates these into a single canonical price with a derived confidence, weighted by publisher reliability and recency.

text
Slot N: Publisher 1 submits  price=100.05, conf=0.10
        Publisher 2 submits  price=100.02, conf=0.08
        Publisher 3 submits  price=99.98,  conf=0.12
        ...
        Aggregator computes  price=100.01, conf=0.09
        Aggregate written to the on-chain PriceAccount for this symbol

Once aggregated, the price lives in a deterministic PriceAccount at a fixed address per symbol. Any consumer can getAccountInfo on that address and read the current price.

The PriceAccount schema (Push model)

rust
pub struct PriceAccount {
    pub magic:        u32,             // 0xa1b2c3d4 — version marker
    pub version:      u32,
    pub atype:        u32,             // 3 = Price
    pub size:         u32,             // bytes used
    pub price_type:   u32,             // 1 = Price (vs TwapAccount etc)
    pub exponent:     i32,             // applied to price → actual_price = price * 10^exponent
    pub num:          u32,             // num publishers
    pub num_qt:       u32,             // num successful quotes this slot
    pub last_slot:    u64,
    pub valid_slot:   u64,             // last slot this price was valid in
    pub ema_price:    PriceInfo,       // exponentially-weighted moving average
    pub ema_conf:     PriceInfo,
    pub timestamp:    i64,
    pub min_pub:      u8,              // minimum publishers required for "trading" status
    pub message_sent: u8,
    pub max_latency:  u8,
    pub drv3:         u8,
    pub drv4:         u32,
    pub product:      Pubkey,          // → ProductAccount with metadata (symbol, asset_type)
    pub next:         Pubkey,          // legacy
    pub prev_slot:    u64,
    pub prev_price:   i64,
    pub prev_conf:    u64,
    pub prev_timestamp: i64,
    pub agg:          PriceInfo {
        price:        i64,             // the aggregated price (scaled by exponent)
        conf:         u64,             // confidence interval (1-sigma uncertainty)
        status:       u32,             // 1=Trading, 2=Halted, 3=Auction, 4=Ignored, 0=Unknown
        corp_act:     u32,
        pub_slot:     u64,
    },
    pub components:   [PriceComponent; 32], // per-publisher data
}

The exponent

Pyth stores prices as i64 with a separate exponent: i32. To get the actual decimal price:

text
actual_price = agg.price × 10^exponent

Example for SOL/USD:
  agg.price = 23045123456
  exponent  = -8
  → actual  = 23045123456 × 10^-8 = $230.45123456

Always apply the exponent. Hardcoding decimals breaks every time Pyth adjusts a feed's precision (rare but happens).

Reading a Pyth price on Solana

rust
use pyth_solana_receiver_sdk::price_update::{get_feed_id_from_hex, PriceUpdateV2};

#[derive(Accounts)]
pub struct UsePyth<'info> {
    pub price_update: Account<'info, PriceUpdateV2>,
}

pub fn read_price(ctx: Context<UsePyth>) -> Result<()> {
    let price_update = &ctx.accounts.price_update;
    let feed_id = get_feed_id_from_hex(
        "0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d", // SOL/USD
    )?;

    // get_price_no_older_than enforces a maximum age (seconds)
    let price = price_update.get_price_no_older_than(
        &Clock::get()?,
        60,         // max age in seconds
        &feed_id,
    )?;

    // price.price is i64 scaled by 10^price.exponent
    msg!("SOL/USD: {} (conf {}, exp {})", price.price, price.conf, price.exponent);
    Ok(())
}

Push vs Pull

Push (Solana-native). Publishers submit to a single Pyth oracle program; the aggregated price account is updated every slot, automatically. Consumers just getAccountInfo. Used by every Solana-native protocol because there's no extra step.

Pull (cross-chain). Pyth's aggregator lives on Pythnet (a separate appchain). Prices are signed by a Wormhole-style guardian set and relayed to consumer chains on-demand. Consumers fetch a signed price update from Hermes (Pyth's API), include it as an instruction in their transaction, and the on-chain Pyth Receiver verifies + writes it. Used on EVM chains and now also offered on Solana for fresher prices when needed.

typescript
// Pull pattern on Solana — fetch a signed update from Hermes, include in tx
import { HermesClient } from "@pythnetwork/hermes-client"

const hermes = new HermesClient("https://hermes.pyth.network")
const updates = await hermes.getLatestPriceUpdates([
  "0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d", // SOL/USD feed_id
])

// updates.binary.data contains the wire-format updates to pass to
// the Pyth Receiver program's post_update instruction.

Using the confidence interval

Pyth's killer feature is the confidence interval (1-sigma uncertainty). Most oracles give you a price; Pyth gives you a price and how sure it is. Production DeFi protocols reject trades when confidence widens past a threshold:

rust
// Reject prices where uncertainty > 1% of price
let conf_pct = (price.conf as f64) / (price.price as f64).abs();
require!(conf_pct < 0.01, MyError::PriceTooUncertain);

// Reject stale prices (>60 seconds since update)
let age = clock.unix_timestamp - price.publish_time;
require!(age < 60, MyError::PriceStale);

Without these checks, your protocol takes whatever Pyth happens to publish during low-quality data periods (publisher outages, thin liquidity moments). The checks above are the minimum viable defensive posture.

References

Pyth is the price layer most Solana DeFi reads from. Apply the exponent, enforce age limits, gate on confidence — those three habits cover 90% of oracle-related production incidents.

Pyth: the on-chain price layer, Push vs Pull, and the price account schema | devrels.xyz