Pyth Network Price Feeds Integration: Real-Time Oracle Data on Solana
Pyth Network delivers sub-second price updates from 90+ first-party data providers, powering over $300B in trading volume across DeFi. Unlike traditional oracles with minute-long delays, Pyth's pull-based architecture ensures your application always uses the freshest price data. This guide covers on-chain integration, off-chain price fetching, and production-ready patterns.
TL;DR
- **Pull-based model** - You fetch prices when needed, not when the oracle updates
- **Sub-second latency** - Price updates every 400ms from first-party sources
- **Confidence intervals** - Every price includes uncertainty bounds for risk management
- **Cross-chain support** - Same price feeds work on Solana, EVM, Cosmos, and more
- **Cost-efficient** - Only pay for prices you actually use in your transactions
Prerequisites
Before integrating Pyth, you should have:
- Basic understanding of Solana programming
- Familiarity with Anchor framework (for on-chain)
- Experience with TypeScript/JavaScript (for off-chain)
- Understanding of oracle concepts
Package versions used in this guide:
- `@pythnetwork/pyth-solana-receiver`: 0.13.0
- `@pythnetwork/hermes-client`: 1.3.0
- `@solana/web3.js`: 1.98.4
- `anchor-lang`: 0.32.1
Understanding Pyth's Architecture
Pull vs Push Oracles
Traditional oracles (like Chainlink on EVM) push prices on-chain at regular intervals. Pyth uses a pull model:
Traditional (Push):
Oracle → Blockchain ← Your App
(periodic updates)
Pyth (Pull):
Hermes API → Your App → Blockchain
(on-demand)Key Benefits
| Feature | Traditional Oracle | Pyth Network |
|---------|-------------------|--------------|
| Update Frequency | 1-60 minutes | 400ms |
| Latency | Variable | Sub-second |
| Cost | Subsidized | Pay-per-use |
| Data Sources | Aggregated | First-party |
| Confidence Data | Usually none | Built-in |
Pyth Price Feed IDs
Every asset has a unique 32-byte feed ID. Common Solana ecosystem feeds:
export const PYTH_PRICE_FEEDS = {
// Crypto
'SOL/USD': '0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d',
'BTC/USD': '0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43',
'ETH/USD': '0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace',
'USDC/USD': '0xeaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a',
'USDT/USD': '0x2b89b9dc8fdf9f34709a5b106b472f0f39bb6ca9ce04b0fd7f2e971688e2e53b',
// Solana DeFi Tokens
'JUP/USD': '0x0a0408d619e9380abad35060f9192039ed5042fa6f82301d0e48bb52be830996',
'BONK/USD': '0x72b021217ca3fe68922a19aaf990109cb9d84e9ad004b4d2025ad6f529314419',
'RAY/USD': '0x91568baa8beb53db23eb3fb7f22c6e8bd303d103919e19733f2bb642d3e7987a',
// Forex & Commodities
'EUR/USD': '0xa995d00bb36a63cef7fd2c287dc105fc8f3d93779f062f09551b0af3e81ec30b',
'XAU/USD': '0x765d2ba906dbc32ca17cc11f5310a89e9ee1f6420508c63861f2f8ba4ee34bb2',
};Off-Chain: Fetching Prices from Hermes
Setting Up the Hermes Client
import { HermesClient } from '@pythnetwork/hermes-client';
// Initialize Hermes client
const hermes = new HermesClient('https://hermes.pyth.network');
// Mainnet endpoints
const HERMES_ENDPOINTS = {
primary: 'https://hermes.pyth.network',
backup: 'https://hermes-beta.pyth.network',
};Fetching Latest Prices
import { HermesClient } from '@pythnetwork/hermes-client';
async function getLatestPrices(feedIds: string[]): Promise<Map<string, PriceData>> {
const hermes = new HermesClient('https://hermes.pyth.network');
const priceUpdates = await hermes.getLatestPriceUpdates(feedIds);
const prices = new Map<string, PriceData>();
for (const update of priceUpdates.parsed || []) {
const price = update.price;
const feedId = update.id;
// Convert to human-readable price
const priceValue = Number(price.price) * Math.pow(10, price.expo);
const confidence = Number(price.conf) * Math.pow(10, price.expo);
prices.set(feedId, {
price: priceValue,
confidence,
publishTime: price.publish_time,
feedId,
});
console.log(`${feedId}: $${priceValue.toFixed(4)} ±$${confidence.toFixed(4)}`);
}
return prices;
}
interface PriceData {
price: number;
confidence: number;
publishTime: number;
feedId: string;
}
// Usage
const solFeed = '0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d';
const btcFeed = '0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43';
const prices = await getLatestPrices([solFeed, btcFeed]);Streaming Real-Time Prices
import { HermesClient } from '@pythnetwork/hermes-client';
async function streamPrices(feedIds: string[]): Promise<void> {
const hermes = new HermesClient('https://hermes.pyth.network');
// Subscribe to price updates
const eventSource = await hermes.getPriceUpdatesStream(feedIds, {
parsed: true,
allowUnordered: true,
});
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.parsed) {
for (const update of data.parsed) {
const price = Number(update.price.price) * Math.pow(10, update.price.expo);
console.log(`[${new Date().toISOString()}] ${update.id}: $${price.toFixed(4)}`);
}
}
};
eventSource.onerror = (error) => {
console.error('Stream error:', error);
eventSource.close();
};
console.log('Streaming prices... Press Ctrl+C to stop');
}
// Start streaming
streamPrices([
'0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d', // SOL
]);On-Chain: Using Pyth in Solana Programs
Anchor Program Integration
use anchor_lang::prelude::*;
use pyth_solana_receiver_sdk::price_update::{
get_feed_id_from_hex,
PriceUpdateV2,
};
declare_id!("YOUR_PROGRAM_ID");
// SOL/USD feed ID
const SOL_USD_FEED_ID: &str = "0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d";
#[program]
pub mod pyth_consumer {
use super::*;
pub fn check_sol_price(ctx: Context<CheckPrice>) -> Result<()> {
let price_update = &ctx.accounts.price_update;
// Get the price, no older than 30 seconds
let maximum_age = 30;
let feed_id = get_feed_id_from_hex(SOL_USD_FEED_ID)?;
let price = price_update.get_price_no_older_than(
&Clock::get()?,
maximum_age,
&feed_id,
)?;
// Price is stored as price * 10^exponent
// For SOL/USD: price=15000000000, expo=-8 means $150.00
msg!(
"SOL/USD Price: {} x 10^{}",
price.price,
price.exponent
);
msg!(
"Confidence: {} x 10^{}",
price.conf,
price.exponent
);
msg!("Publish Time: {}", price.publish_time);
Ok(())
}
pub fn execute_if_price_above(
ctx: Context<ConditionalExecution>,
min_price: i64,
) -> Result<()> {
let price_update = &ctx.accounts.price_update;
let feed_id = get_feed_id_from_hex(SOL_USD_FEED_ID)?;
let price = price_update.get_price_no_older_than(
&Clock::get()?,
30,
&feed_id,
)?;
// Normalize price to 8 decimals for comparison
let normalized_price = if price.exponent >= 0 {
price.price * 10_i64.pow(price.exponent as u32)
} else {
price.price / 10_i64.pow((-price.exponent) as u32)
};
require!(
normalized_price >= min_price,
ErrorCode::PriceTooLow
);
msg!("Price condition met! Executing...");
// Your logic here
Ok(())
}
}
#[derive(Accounts)]
pub struct CheckPrice<'info> {
/// The Pyth price update account
pub price_update: Account<'info, PriceUpdateV2>,
}
#[derive(Accounts)]
pub struct ConditionalExecution<'info> {
pub price_update: Account<'info, PriceUpdateV2>,
#[account(mut)]
pub user: Signer<'info>,
}
#[error_code]
pub enum ErrorCode {
#[msg("Price is below minimum threshold")]
PriceTooLow,
}Cargo.toml Dependencies
[dependencies]
anchor-lang = "0.32.1"
pyth-solana-receiver-sdk = "0.4.0"Client-Side: Posting Price Updates
Before your on-chain program can read prices, you must post the price update to the chain:
import { PythSolanaReceiver } from '@pythnetwork/pyth-solana-receiver';
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
import { AnchorProvider, Wallet } from '@coral-xyz/anchor';
import { HermesClient } from '@pythnetwork/hermes-client';
async function postPriceAndExecute(
connection: Connection,
wallet: Keypair,
feedIds: string[]
): Promise<string> {
// 1. Fetch latest price update data from Hermes
const hermes = new HermesClient('https://hermes.pyth.network');
const priceUpdates = await hermes.getLatestPriceUpdates(feedIds, {
encoding: 'base64',
});
// 2. Create Pyth Solana Receiver instance
const provider = new AnchorProvider(
connection,
new Wallet(wallet),
{ commitment: 'confirmed' }
);
const pythSolanaReceiver = new PythSolanaReceiver({
connection,
wallet: new Wallet(wallet),
});
// 3. Build transaction with price update
const transactionBuilder = pythSolanaReceiver.newTransactionBuilder({
closeUpdateAccounts: true, // Reclaim rent after use
});
// Add price update to transaction
await transactionBuilder.addPostPriceUpdates(priceUpdates.binary.data);
// 4. Add your program's instructions
await transactionBuilder.addPriceConsumerInstructions(
async (getPriceUpdateAccount) => {
// Get the price update account for your feed
const priceUpdateAccount = getPriceUpdateAccount(feedIds[0]);
// Create your program's instruction here
// This is where you call your Anchor program
const yourInstruction = await createYourInstruction(priceUpdateAccount);
return [{ ix: yourInstruction, signers: [] }];
}
);
// 5. Send the transaction
const txs = await transactionBuilder.buildVersionedTransactions({
computeUnitPriceMicroLamports: 50000,
});
const signatures = await provider.sendAll(txs, { skipPreflight: true });
console.log('Transaction signatures:', signatures);
return signatures[0];
}
// Helper to create your instruction
async function createYourInstruction(
priceUpdateAccount: PublicKey
): Promise<TransactionInstruction> {
// Implement based on your program
// ...
}Complete Example: Price-Gated Mint
A practical example combining Pyth prices with NFT minting:
On-Chain Program
use anchor_lang::prelude::*;
use pyth_solana_receiver_sdk::price_update::{
get_feed_id_from_hex,
PriceUpdateV2,
};
declare_id!("YOUR_PROGRAM_ID");
const SOL_USD_FEED_ID: &str = "0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d";
#[program]
pub mod price_gated_mint {
use super::*;
pub fn initialize(ctx: Context<Initialize>, min_sol_price: i64) -> Result<()> {
let config = &mut ctx.accounts.config;
config.authority = ctx.accounts.authority.key();
config.min_sol_price = min_sol_price; // e.g., 150_00000000 for $150
config.mints_count = 0;
config.bump = ctx.bumps.config;
Ok(())
}
pub fn mint_if_price_valid(ctx: Context<MintNft>) -> Result<()> {
let config = &ctx.accounts.config;
let price_update = &ctx.accounts.price_update;
// Get current SOL price
let feed_id = get_feed_id_from_hex(SOL_USD_FEED_ID)?;
let price_data = price_update.get_price_no_older_than(
&Clock::get()?,
60, // Max 60 seconds old
&feed_id,
)?;
// Normalize to 8 decimals
let current_price = normalize_price(price_data.price, price_data.exponent);
msg!("Current SOL price: {}", current_price);
msg!("Minimum required: {}", config.min_sol_price);
require!(
current_price >= config.min_sol_price,
ErrorCode::PriceBelowMinimum
);
// Price condition met - proceed with mint logic
let config = &mut ctx.accounts.config;
config.mints_count = config.mints_count.checked_add(1)
.ok_or(ErrorCode::Overflow)?;
msg!("Mint #{} successful at SOL price: {}",
config.mints_count, current_price);
// Add your actual minting CPI here
Ok(())
}
}
fn normalize_price(price: i64, exponent: i32) -> i64 {
if exponent >= -8 {
price * 10_i64.pow((8 + exponent) as u32)
} else {
price / 10_i64.pow((-8 - exponent) as u32)
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(
init,
payer = authority,
space = 8 + Config::INIT_SPACE,
seeds = [b"config"],
bump
)]
pub config: Account<'info, Config>,
#[account(mut)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct MintNft<'info> {
#[account(
mut,
seeds = [b"config"],
bump = config.bump
)]
pub config: Account<'info, Config>,
pub price_update: Account<'info, PriceUpdateV2>,
#[account(mut)]
pub minter: Signer<'info>,
}
#[account]
#[derive(InitSpace)]
pub struct Config {
pub authority: Pubkey,
pub min_sol_price: i64,
pub mints_count: u64,
pub bump: u8,
}
#[error_code]
pub enum ErrorCode {
#[msg("SOL price is below minimum required")]
PriceBelowMinimum,
#[msg("Arithmetic overflow")]
Overflow,
}Client Code
import { Program, AnchorProvider, Wallet, BN } from '@coral-xyz/anchor';
import { Connection, Keypair, PublicKey, SystemProgram } from '@solana/web3.js';
import { PythSolanaReceiver } from '@pythnetwork/pyth-solana-receiver';
import { HermesClient } from '@pythnetwork/hermes-client';
import { PriceGatedMint } from '../target/types/price_gated_mint';
const SOL_USD_FEED = '0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d';
async function mintWithPriceCheck(
program: Program<PriceGatedMint>,
connection: Connection,
wallet: Keypair
): Promise<string> {
// 1. Get fresh price data
const hermes = new HermesClient('https://hermes.pyth.network');
const priceUpdates = await hermes.getLatestPriceUpdates([SOL_USD_FEED], {
encoding: 'base64',
});
// 2. Set up Pyth receiver
const pythReceiver = new PythSolanaReceiver({
connection,
wallet: new Wallet(wallet),
});
// 3. Build transaction
const txBuilder = pythReceiver.newTransactionBuilder({
closeUpdateAccounts: true,
});
await txBuilder.addPostPriceUpdates(priceUpdates.binary.data);
// 4. Add mint instruction
const [configPda] = PublicKey.findProgramAddressSync(
[Buffer.from('config')],
program.programId
);
await txBuilder.addPriceConsumerInstructions(
async (getPriceUpdateAccount) => {
const priceAccount = getPriceUpdateAccount(SOL_USD_FEED);
const ix = await program.methods
.mintIfPriceValid()
.accounts({
config: configPda,
priceUpdate: priceAccount,
minter: wallet.publicKey,
})
.instruction();
return [{ ix, signers: [] }];
}
);
// 5. Execute
const provider = new AnchorProvider(connection, new Wallet(wallet), {});
const txs = await txBuilder.buildVersionedTransactions({
computeUnitPriceMicroLamports: 50000,
});
const sigs = await provider.sendAll(txs, { skipPreflight: true });
console.log('Mint transaction:', sigs[0]);
return sigs[0];
}Handling Confidence Intervals
Pyth provides confidence intervals for risk management:
pub fn safe_price_check(
price_update: &Account<PriceUpdateV2>,
feed_id: &[u8; 32],
max_confidence_ratio: u64, // e.g., 100 = 1%
) -> Result<i64> {
let price_data = price_update.get_price_no_older_than(
&Clock::get()?,
30,
feed_id,
)?;
// Calculate confidence as percentage of price
let confidence_ratio = (price_data.conf as u128)
.checked_mul(10000)
.unwrap()
.checked_div(price_data.price.unsigned_abs() as u128)
.unwrap() as u64;
require!(
confidence_ratio <= max_confidence_ratio,
ErrorCode::PriceUncertaintyTooHigh
);
Ok(price_data.price)
}Common Pitfalls
- **Stale prices** - Always check `publish_time` and set appropriate `maximum_age`
- **Exponent handling** - Prices use variable exponents; normalize before comparison
- **Missing price updates** - Post price data before calling your program
- **Wrong feed ID** - Double-check feed IDs match your target asset
- **Ignoring confidence** - Use confidence intervals for production systems
Best Practices
- **Cache Hermes results** - Don't fetch for every calculation
- **Set reasonable staleness** - 30-60 seconds for most DeFi applications
- **Handle errors gracefully** - Price feeds can fail; have fallbacks
- **Monitor confidence** - High confidence intervals indicate volatile markets
- **Use multiple feeds** - Cross-reference critical prices
Next Steps and Resources
Learn More:
- [Pyth Documentation](https://docs.pyth.network)
- [Price Feed IDs](https://pyth.network/price-feeds)
- [Hermes API Reference](https://docs.pyth.network/price-feeds/api-reference)
Build Something:
- Create a dynamic pricing DEX with Pyth feeds
- Build a liquidation bot monitoring multiple assets
- Implement a price-weighted voting system
Building DeFi applications that need reliable price data? Builderz has extensive experience integrating Pyth Network for trading systems and protocols. Contact us to discuss your project.



