Pyth: the on-chain price layer, Push vs Pull, and the price account schema
Pyth's publisher network, the aggregated price account, the Push (Solana-native) vs Pull (cross-chain) models, EMA price, confidence intervals, and how to consume a feed safely.
devrels.xyz/a/82Pyth 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.
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 symbolOnce 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)
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:
actual_price = agg.price × 10^exponent
Example for SOL/USD:
agg.price = 23045123456
exponent = -8
→ actual = 23045123456 × 10^-8 = $230.45123456Always apply the exponent. Hardcoding decimals breaks every time Pyth adjusts a feed's precision (rare but happens).
Reading a Pyth price on Solana
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.
// 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:
// 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
- docs.pyth.network
- Pyth price feed IDs (canonical addresses per symbol)
- pyth-network/pyth-crosschain — Pull model implementation
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.
Keep reading
Pyth is not “a price API with a logo” — it is publisher-sourced market data you can verify on-chain, usually by pulling an update into the same transaction that needs the price.
Subscribe to Lazer, drop the signed payload in the trade tx, verify, then read price. That is what a program actually consumes.
TxODDS publishes Merkle roots of every data packet to Solana, making sports data tamper-evident and independently verifiable. Developers subscribe on-chain using TxL tokens (USDT → TxL → program subscription → API token), then query live odds, scores, and settlement feeds. Free tier covers the 2026 World Cup.
Get new articles in your inbox
Technical deep-dives on Solana tooling, infrastructure, and ecosystem. No noise.
