Use Prices on Solana and SVM Chains
Steps to verify prices on Solana and other SVM Chains
This guide covers steps to verify prices on Solana and other SVM Chains. Choose a Contract explains which to use.
Pyth Pro
Integrating with Pyth Pro in smart contracts is a three-step process:
- Use Pyth Lazer SDK into SVM smart contracts to parse the price updates.
- Subscribe to Pyth Pro websocket to receive price updates on backend or frontend.
- Include the price updates into the smart contract transactions.
Use Pyth Lazer SDK in smart contracts
Pyth Pro/Lazer provides a Solana SDK, which allows applications to parse and verify the price updates on Solana-compatible chains (such as Fogo).
To start, add the following to your Cargo.toml file (please check the current latest version at crates.io):
[dependencies]
pyth-lazer-solana-contract = { version = "x.y.z", features = ["no-entrypoint"] }Now you can create an instruction or multiple instructions that will receive Pyth Pro messages. The instruction data sent to your program should include a byte array containing the Pyth Pro message. The instruction data can also contain any other parameters your contracts may need.
In order to successfully validate price updates, the instruction needs to receive the following accounts:
- Fee payer account
- Pyth Lazer program (
pytd2yyk641x7ak7mkaasSJVXh6YYZnC7wTmtgAyxPt) - Pyth Lazer storage account (
3rdJbqfnagQ4yx9HXJViD4zc4xpiSqmFsKpPuSCQVyQL) - Pyth Lazer treasury account (
Gx4MBPb1vqZLJajZmsKLg8fGw9ErhoKsR8LeKcCKFyak) - The standard Solana system program account
- The standard Solana instructions sysvar account
You may also add any other accounts your contract needs.
The code snippets below are part of the full consumer contract example available on Github.
The following code can be used to set up a new instruction within a SVM contract:
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, FromPrimitive)]
pub enum Instruction {
//...
/// Update price.
/// Data: `UpdateArgs` followed by a signed Pyth Lazer update.
/// Accounts:
/// 1. payer account
/// 2. example data account [writable]
/// 3. pyth program account [readonly]
/// 4. pyth storage account [readonly]
/// 5. pyth treasury account [writable]
/// 6. system program [readonly]
/// 7. instructions sysvar sysvar account [readonly]
Update = 1,
}
/// Inputs to the `Update` instruction. `UpdateArgs` must be followed by a signed Pyth Lazer message.
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
#[repr(C, packed)]
pub struct UpdateArgs {
/// Example argument
pub hello: u64,
}
/// Program entrypoint's implementation.
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
// In our example contract, the first byte is the ID of the instruction.
let instruction = *instruction_data
.first()
.ok_or(ProgramError::InvalidInstructionData)?;
let instruction =
Instruction::from_u8(instruction).ok_or(ProgramError::InvalidInstructionData)?;
let instruction_args = &instruction_data[1..];
match instruction {
Instruction::Initialize => {
process_initialize_instruction(program_id, accounts, instruction_args)
}
Instruction::Update => process_update_instruction(program_id, accounts, instruction_args),
}
}
pub fn process_update_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_args: &[u8],
) -> ProgramResult {
// Verify accounts passed to the instruction.
if accounts.len() != 7 {
return Err(ProgramError::NotEnoughAccountKeys);
}
let payer_account = &accounts[0];
let data_account = &accounts[1];
let _pyth_program_account = &accounts[2];
let pyth_storage_account = &accounts[3];
let pyth_treasury_account = &accounts[4];
let system_program_account = &accounts[5];
let instructions_sysvar_account = &accounts[6];
// See below for next steps...
}Invoke the Pyth Lazer on-chain program with appropriate arguments to validate the Pyth Lazer signature of the message.
// We expect the instruction to the built-in ed25519 program to be
// the first instruction within the transaction.
let ed25519_instruction_index = 0;
// We expect our signature to be the first (and only) signature to be checked
// by the built-in ed25519 program within the transaction.
let signature_index = 0;
// Verify Lazer signature.
invoke(
&ProgramInstruction::new_with_bytes(
pyth_lazer_solana_contract::ID,
&VerifyMessage {
message_data: pyth_message.to_vec(),
ed25519_instruction_index,
signature_index,
}
.data(),
vec![
AccountMeta::new(*payer_account.key, true),
AccountMeta::new_readonly(*pyth_storage_account.key, false),
AccountMeta::new(*pyth_treasury_account.key, false),
AccountMeta::new_readonly(*system_program_account.key, false),
AccountMeta::new_readonly(*instructions_sysvar_account.key, false),
],
),
&[
payer_account.clone(),
pyth_storage_account.clone(),
pyth_treasury_account.clone(),
system_program_account.clone(),
instructions_sysvar_account.clone(),
],
)?;Note: When using native ed25519 signatures on SVM, we must use the built-in ed25519 program provided by the SVM runtime. This program can't be invoked from another contract. Instead, it must be called in an explicit instruction within the submitted transaction. This means that the sender of the transaction must include that instruction in the transaction. Our SDK leverages SVM runtime capabilities to ensure the ed25519 program has been correctly called in the transaction.
Now parse the message received from Pyth Pro.
// Deserialize and use the payload.
let data = PayloadData::deserialize_slice_le(verified.payload)
.map_err(|_| ProgramError::InvalidInstructionData)?;
if data.feeds.is_empty() || data.feeds[0].properties.is_empty() {
return Err(ProgramError::InvalidInstructionData);
}Now you can update the state according to the contract's logic:
// Read the data PDA of our example contract.
let mut state_data = data_account.data.borrow_mut();
let state =
try_from_bytes_mut::<State>(*state_data).map_err(|_| ProgramError::InvalidAccountData)?;
if state.price_feed != data.feeds[0].feed_id.0 {
return Err(ProgramError::InvalidInstructionData);
}
if data.channel_id != Channel::RealTime.id() {
return Err(ProgramError::InvalidInstructionData);
}
if data.timestamp_us.0 <= state.latest_timestamp {
return Err(ProgramError::AccountAlreadyInitialized);
}
let PayloadPropertyValue::Price(Some(price)) = data.feeds[0].properties[0] else {
return Err(ProgramError::InvalidInstructionData);
};
state.latest_price = price.into_inner().into();
state.latest_timestamp = data.timestamp_us.0;Pyth Pro/Lazer also provides pyth_lazer_protocol Rust crate, which allows applications to parse the price updates off-chain.
Subscribe to Pyth Pro to receive Price Updates
Pyth Pro/Lazer provides a websocket endpoint to receive price updates. Moreover, Pyth Pro/Lazer also provides a typescript SDK to subscribe to the websocket endpoint.
Consult How to subscribe to prices for a complete step-by-step guide.
Include the price updates into smart contract transactions
Now that you have the price updates, and your smart contract is able to parse the price updates, you can include the price updates into the smart contract transactions by passing the price updates received from the previous step as an argument to the update_price method of your smart contract.
In order to execute signature verification, you need to include an instruction for the built-in Solana ed25519 program in your transaction.
In Rust, you can leverage helpers provided in the pyth_lazer_solana_contract crate:
// Instruction #0 will be ed25519 instruction;
// Instruction #1 will be our contract instruction.
let instruction_index = 1;
// Total offset of Pyth Lazer update within the instruction data;
// 1 byte is the instruction type.
let message_offset = (size_of::<UpdateArgs>() + 1).try_into().unwrap();
let ed25519_args = pyth_lazer_solana_contract::Ed25519SignatureOffsets::new(
&message,
instruction_index,
message_offset,
);
let mut tx = Transaction::new_with_payer(
&[
Instruction::new_with_bytes(
solana_program::ed25519_program::ID,
&pyth_lazer_solana_contract::ed25519_program_args(&[ed25519_args]),
vec![],
),
Instruction::new_with_bytes(
pyth_lazer_solana_example::ID,
&update_data,
vec![
AccountMeta::new(payer.pubkey(), true),
AccountMeta::new(data_pda_key, false),
AccountMeta::new(pyth_lazer_solana_contract::ID, false),
AccountMeta::new_readonly(pyth_lazer_solana_contract::STORAGE_ID, false),
AccountMeta::new(treasury, false),
AccountMeta::new_readonly(system_program::ID, false),
AccountMeta::new_readonly(sysvar::instructions::ID, false),
],
),
],
Some(&payer.pubkey()),
);Pyth Core contracts
This section explains how Solana programs read Pyth prices from the Pyth Core contracts.
All Pyth Core contracts were upgraded on August 26, 2026 and kept their interface. Their price updates come from the upgraded Hermes, which collects prices signed by the Pyth Pro routers. How the upgraded Pyth Core works has the details.
Use the upgraded Solana addresses for the Pyth Core contracts.
Install Pyth SDKs
Pyth provides two SDKs for Solana applications to cover the on- and off-chain portions of the integration:
Rust SDK
The pyth-solana-receiver-sdk crate can be used to consume Pyth prices inside Solana programs written in Rust.
Add this crate to the dependencies section of your Cargo.toml file:
[dependencies]
pyth-solana-receiver-sdk = { version = "x.y.z", features = ["pro-compatible"] } # get the latest version from https://crates.io/crates/pyth-solana-receiver-sdkThe pro-compatible feature points the crate at the upgraded Pyth Core programs. Without it, the crate uses the programs from before the upgrade.
Anchor Compatibility
The pro-compatible feature needs pyth-solana-receiver-sdk 1.2.0 or later.
Version 1.2.0 works with Anchor v0.32.1, and 2.0.0 with Anchor v1.0.
Typescript SDK
Pyth provides two Typescript packages, @pythnetwork/hermes-client and @pythnetwork/pyth-solana-receiver, for fetching Pyth prices and submitting them to the blockchain respectively. Add these packages to your off-chain dependencies:
npm install --save @pythnetwork/hermes-client @pythnetwork/pyth-solana-receiverWrite Contract Code
Add the following code to your Solana program to read Pyth prices.
Pyth prices are posted to price update accounts that can be passed to any instruction that needs price data.
For developers using Anchor, simply add an Account<'info, PriceUpdateV2> field to the Context struct:
use pyth_solana_receiver_sdk::price_update::{PriceUpdateV2};
#[derive(Accounts)]
#[instruction()]
pub struct Sample<'info> {
#[account(mut)]
pub payer: Signer<'info>,
// Add this account to any instruction Context that needs price data.
pub price_update: Account<'info, PriceUpdateV2>,
}Price Update Ownership
Users must ensure that the account passed to their instruction is owned by the Pyth Pull Oracle program.
Using Anchor with the Account<'info, PriceUpdateV2> type will automatically perform this check.
However, it is the developer's responsibility to perform this check if they are not using Anchor.
Next, update the instruction logic to read the price from the price update account:
pub fn sample(ctx: Context<Sample>) -> Result<()> {
let price_update = &mut ctx.accounts.price_update;
// get_price_no_older_than will fail if the price update is more than 30 seconds old
let maximum_age: u64 = 30;
// get_price_no_older_than will fail if the price update is for a different price feed.
// This string is the id of the BTC/USD feed. See https://docs.pyth.network/price-feeds/price-feeds for all available IDs.
let feed_id: [u8; 32] = get_feed_id_from_hex("0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43")?;
let price = price_update.get_price_no_older_than(&Clock::get()?, maximum_age, &feed_id)?;
// Sample output:
// The price is (7160106530699 ± 5129162301) * 10^-8
msg!("The price is ({} ± {}) * 10^{}", price.price, price.conf, price.exponent);
Ok(())
}Validate Price Update Accounts
Users must validate the price update for the appropriate price
feed and timestamp. PriceUpdateV2 guarantees that the account contains
a verified price for some price feed at some point in time. There are
various methods on this struct (such as get_price_no_older_than) that users
can use to implement the necessary checks.
If you choose the price feed account integration (see below), you can use an account address check to validate the price feed ID.
Write Frontend Code
There are two different paths to the frontend integration of Pyth prices on Solana. Developers can choose to use two different types of accounts:
- Price feed accounts hold a sequence of prices for a specific price feed ID that always moves forward in time. These accounts have a fixed address that your program can depend on. The Pyth Data Association maintains a set of price feed accounts that are continuously updated. Such accounts are a good fit for applications that always want to consume the most recent price.
- Price update accounts are ephemeral accounts that anyone can create, overwrite, and close. These accounts are a good fit for applications that want to consume prices for a specific timestamp.
Both price feed accounts and price update accounts work identically from the perspective of the on-chain program. However, the frontend integration differs slightly between the two. Both options are explained in the sections below, and developers should pick the one that is best suited for their use case.
Price Feed Accounts
For developers using price feed accounts, the frontend code needs to pass the relevant price feed account address to the transaction.
Price feed accounts are program-derived addresses and thus the account ID for any price feed can be derived automatically.
The PythSolanaReceiver class provides a method for deriving this information:
import {
PRO_COMPATIBLE_PUSH_ORACLE_PROGRAM_ID,
PRO_COMPATIBLE_RECEIVER_PROGRAM_ID,
PRO_COMPATIBLE_WORMHOLE_PROGRAM_ID,
PythSolanaReceiver,
} from "@pythnetwork/pyth-solana-receiver";
// You will need a Connection from @solana/web3.js and a Wallet from @coral-xyz/anchor to create
// the receiver.
const connection: Connection;
const wallet: Wallet;
// The SDK defaults to the programs from before the upgrade, so pass the upgraded ones.
const pythSolanaReceiver = new PythSolanaReceiver({
connection,
pushOracleProgramId: PRO_COMPATIBLE_PUSH_ORACLE_PROGRAM_ID,
receiverProgramId: PRO_COMPATIBLE_RECEIVER_PROGRAM_ID,
wallet,
wormholeProgramId: PRO_COMPATIBLE_WORMHOLE_PROGRAM_ID,
});
// There are up to 2^16 different accounts for any given price feed id.
// The 0 value below is the shard id that indicates which of these accounts you would like to use.
// However, you may choose to use a different shard to prevent Solana congestion on another app from affecting your app.
const solUsdPriceFeedAccount = pythSolanaReceiver
.getPriceFeedAccountAddress(0, SOL_PRICE_FEED_ID)
.toBase58();Sponsored Updates
The Price Feed Accounts integration requires a process that continuously updates each price feed. The Pyth Data Association sponsors price updates for a subset of commonly used price feeds on shard 0. See Sponsored Feeds for the feeds and their account addresses.
Anyone can update a price feed. See Run Price Pusher to run the Price Pusher for reliability or to update feed/shard pairs outside the default schedule.
Price Update Accounts
To use price update accounts, the frontend code needs to perform two different tasks:
- Fetch price updates from Hermes
- Post the price updates to Solana and invoke your application logic
Fetch price updates
Use HermesClient from @pythnetwork/hermes-client to fetch Pyth price updates from Hermes. The new endpoint pyth.dourolabs.app/hermes is the upgraded backend (recommended); the current hermes.pyth.network keeps working and requires an API key since August 26, 2026:
import { HermesClient } from "@pythnetwork/hermes-client";
const priceServiceConnection = new HermesClient(
"https://pyth.dourolabs.app/hermes",
{ accessToken: process.env.PYTH_API_KEY },
);
// Hermes provides other methods for retrieving price updates. See the
// Hermes-compatible API reference for more information:
// https://docs.pyth.network/layout-preview/api-reference/hermes/latest-price-updates
const priceUpdateData = (
await priceServiceConnection.getLatestPriceUpdates(
["0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43"],
{ encoding: "base64" },
)
).binary.data;
// Price updates are strings of base64-encoded binary data. Example:
// ["UE5BVQEAAAADuAEAAAADDQJoIZzihVp30/71MXDexqiJDjGpEUEcHj/D06pRiYTPPlRPZKKiVFaZdqiGZ7vvGBj1kK69fKRncCKHXlGyS0LcAQPREzVlm7AcY+X0/hCupv8M5KMhEgOCxq5LxCCKQjL/xwm4jTCJrwkHn/PBaoirnlbHMjxAlwJaFF4RGeE1KRDeAASQIFjfbbobiq2OcSRnK3Ny1NY2Zd012ahIhoWpqxbv4UqDZXLh+bPm4Q14SfO/llRTOmqG6O4v+nfjZa7WNjgxAAZFANqAEooKvijK+476YBITKono+vJ4bSQHUiXOaiGs/zcI6WYGRM0xc6ov2mfCaTuH7mx3ElaLyPGvRX4D9DyHAAf+dPg+NEepwMJI1qPLRcTy0xoz2Yq0k2MuL87gCBsYVlS31atGsQcrXfHr4xcTKKyEUh1tIaOfRbPdX1cvs4D6AAj2/vuKpAgYWhd2gtqHsQLV1vMgq8SKH8wiOmhaMZ06GAQSM1pYLpHZRhYaUQbrUJAeqEeX+qMMqQFMPSSUnEKNAAmCE98NUYbHuEoxJGMDybTGCyDEXCmaNM0q6GT0qrbSmT6NF50yz9CE30WWHNOZzFtK2rCyBYFH2aAp6lQ1JKfmAQpW/wUaOhSdwGiEPWvpY3FWL077i0c4auXQjSQNaDD0cBnmvJTS5R3KxK5aunuUvVAT1mHTnpKHIzNKyu7ICM2zAQvrIFfWRFjVE0zRCvoAcvMpmpS7atWu8VgvklpZh9Qt9xYSO2Yq/asgNsMSQaowXiU0MfjggS+UJ8yWaOpUg18vAAxAMuUlOjNzFj6oPES850YNu2k7PM7AGL8Gb/8+HshkfjG0GsNR8H8/vB8v/iEcaScxQFXwtLT0OSgjWMa0ByknAA7PScKUEP8N7iJKYv6lmEs26DZnxzdpGVZRGqbbC0mxyjY0HqsT0rv2wNvy3MbAtABDMsLumII00cRCKBsZXGlKARCC0NzsKnduLsgGfqxYL4yuf910DKrRp5j+fKLmF2QiB2yVT90ja0782/u6BZZUGRMoA/AWl1qvswBtnlSkHcWEABIp74UFLiiA+MBBvBzhLBxSTKXldiLJ75+U/eqK/ej6qT+I+6S1pzT/ntXdpD25jmQhjtsYEqs/rmgs5U2p4AVRAGYULPcAAAAAABrhAfrtrFhR4yubI7X5QRqMK6xKrj7U3XuBHdGnLqSqcQAAAAAC8IR2AUFVV1YAAAAAAAf6dUkAACcQMZv+5jfvAe6sflX1cL7xu9WWQ9UBAFUA5i32yLSoX+GmfbRNwS3l2zMPesZrctxliv7fD0pBW0MAAAaBiqrXwAAAAADYu55y////+AAAAABmFCz3AAAAAGYULPYAAAaFH6MbAAAAAADcBIdMCre0t06ngCnw+N4IkFpZVqOz9YuwKL+UFdt13ZBtay0YZnkw7QGoaTDCLlsNK1tk1F/qgMyOcYozjOTj41XriIpEPeG2HPYl+u0CKolGlCsz1IDu4w2lyh6LWVaMkEybGz7ih4H2RqCj6BVu182ZqsZgJx9ghzKImAo4cIlWzRTwpm4daAqHa4JEyimFDpFt6UeqvS5TNu2F8W+X+edeiph20EulTI7sx38jwhq5Yc0Mf2ElvFgToGQ806Vs2HynuLwh9OIuTTZh"]
console.log(priceUpdateData);Consult Price Updates for the Pyth Core Contracts for more information on fetching price updates from Hermes.
Post price updates
Finally, post the price update to the Pyth program on Solana.
This step will create the price update account that your application reads from.
Applications typically combine posting the price update and invoking their application into a sequence of transactions.
The PythSolanaReceiver class in @pythnetwork/pyth-solana-receiver provides a convenient transaction builder to help with this process:
import {
PRO_COMPATIBLE_PUSH_ORACLE_PROGRAM_ID,
PRO_COMPATIBLE_RECEIVER_PROGRAM_ID,
PRO_COMPATIBLE_WORMHOLE_PROGRAM_ID,
PythSolanaReceiver,
} from "@pythnetwork/pyth-solana-receiver";
// You will need a Connection from @solana/web3.js and a Wallet from @coral-xyz/anchor to create
// the receiver.
const connection: Connection;
const wallet: Wallet;
// The SDK defaults to the programs from before the upgrade, so pass the upgraded ones.
const pythSolanaReceiver = new PythSolanaReceiver({
connection,
pushOracleProgramId: PRO_COMPATIBLE_PUSH_ORACLE_PROGRAM_ID,
receiverProgramId: PRO_COMPATIBLE_RECEIVER_PROGRAM_ID,
wallet,
wormholeProgramId: PRO_COMPATIBLE_WORMHOLE_PROGRAM_ID,
});
// Set closeUpdateAccounts: true if you want to delete the price update account at
// the end of the transaction to reclaim rent.
const transactionBuilder = pythSolanaReceiver.newTransactionBuilder({
closeUpdateAccounts: false,
});
await transactionBuilder.addPostPriceUpdates(priceUpdateData);
// Use this function to add your application-specific instructions to the builder
await transactionBuilder.addPriceConsumerInstructions(
async (
getPriceUpdateAccount: (priceFeedId: string) => PublicKey,
): Promise<InstructionWithEphemeralSigners[]> => {
// Generate instructions here that use the price updates posted above.
// getPriceUpdateAccount(<price feed id>) will give you the account for each price update.
return [];
},
);
// Send the instructions in the builder in 1 or more transactions.
// The builder will pack the instructions into transactions automatically.
await pythSolanaReceiver.provider.sendAll(
await transactionBuilder.buildVersionedTransactions({
computeUnitPriceMicroLamports: 50000,
}),
{ skipPreflight: true },
);The SDK documentation contains more information about interacting with the Pyth solana receiver contract, including working examples.
Batch Price Updates
Posting and verifying price updates currently requires multiple transactions
on Solana. If your usecase requires a single transaction, you can reduce the
verification level of the posted price updates by replacing
addPostPriceUpdates by addPostPartiallyVerifiedPriceUpdates in the
transaction builder. Please read the
VerificationLevel
docs to understand more about the data integrity tradeoffs when using
partially verified price updates.
Additional Resources
For the Pyth Pro contracts:
- Price Feed IDs: Pyth Terminal lists every supported price feed and its ID. To list the feeds from code, see Price Feed IDs.
- Examples: pyth-lazer-example-solana is an example contract that parses and consumes Pyth Pro price updates. pyth-lazer-example-js subscribes to the Pyth Pro websocket.
For the Pyth Core contracts:
- Price Feed IDs: Price Feed IDs lists every Pyth Core price feed and its ID.