Comparing debridge with pyth
debridge
View full →Author
@0xinit
Stars
53
Repository
0xinit/cryptoskills
deBridge Solana SDK Development Guide
A comprehensive guide for building Solana programs with the deBridge Solana SDK - enabling decentralized cross-chain transfers of arbitrary messages and value between blockchains.
Overview
deBridge is a cross-chain infrastructure protocol enabling:
- Cross-Chain Transfers: Bridge assets between Solana and 20+ EVM chains
- Message Passing: Send arbitrary messages across blockchains
- External Calls: Execute smart contract calls on destination chains
- Sub-Second Settlement: ~2 second median settlement time
- Capital Efficiency: Intent-based architecture with 4bps lowest spreads
Key Features
- 26+ security audits (Halborn, Zokyo, Ackee Blockchain)
- $200K bug bounty on Immunefi
- 100% uptime since launch
- Zero security incidents
Quick Start
Installation
Add the SDK to your Anchor/Solana program:
cargo add --git ssh://git@github.com/debridge-finance/debridge-solana-sdk.git debridge-solana-sdk
Or add to Cargo.toml:
[dependencies]
debridge-solana-sdk = { git = "ssh://git@github.com/debridge-finance/debridge-solana-sdk.git" }
Basic Setup (Anchor)
use anchor_lang::prelude::*;
use debridge_solana_sdk::prelude::*;
declare_id!("YourProgramId11111111111111111111111111111");
#[program]
pub mod my_bridge_program {
use super::*;
pub fn send_cross_chain(
ctx: Context<SendCrossChain>,
target_chain_id: [u8; 32],
receiver: Vec<u8>,
amount: u64,
) -> Result<()> {
// Invoke deBridge send
debridge_sending::invoke_debridge_send(
debridge_sending::SendIx {
target_chain_id,
receiver,
is_use_asset_fee: false, // Use native SOL for fees
amount,
submission_params: None,
referral_code: None,
},
ctx.remaining_accounts,
)?;
Ok(())
}
}
#[derive(Accounts)]
pub struct SendCrossChain<'info> {
#[account(mut)]
pub sender: Signer<'info>,
// Additional accounts passed via remaining_accounts
}
Core Concepts
1. Chain IDs
deBridge uses 32-byte chain identifiers for all supported networks:
use debridge_solana_sdk::chain_ids::*;
// Solana
let solana = SOLANA_CHAIN_ID; // Solana mainnet
// EVM Chains
let ethereum = ETHEREUM_CHAIN_ID; // Chain ID: 1
let polygon = POLYGON_CHAIN_ID; // Chain ID: 137
let bnb = BNB_CHAIN_CHAIN_ID; // Chain ID: 56
let arbitrum = ARBITRUM_CHAIN_ID; // Chain ID: 42161
let avalanche = AVALANCHE_CHAIN_ID; // Chain ID: 43114
let fantom = FANTOM_CHAIN_ID; // Chain ID: 250
let heco = HECO_CHAIN_ID; // Chain ID: 128
2. Program IDs
use debridge_solana_sdk::{DEBRIDGE_ID, SETTINGS_ID};
// Main deBridge program for sending/claiming
let debridge_program = DEBRIDGE_ID;
// Settings and confirmation storage program
let settings_program = SETTINGS_ID;
3. Fee Structure
deBridge supports multiple fee payment methods:
// Native Fee (SOL)
is_use_asset_fee: false // Pay fees in SOL
// Asset Fee
is_use_asset_fee: true // Pay fees in the bridged token
// Fee Constants
const BPS_DENOMINATOR: u64 = 10000; // Basis points divisor
4. Flags
Control transfer behavior with flags:
use debridge_solana_sdk::flags::*;
// Available flags (bit positions)
const UNWRAP_ETH: u8 = 0; // Unwrap to native ETH on destination
const REVERT_IF_EXTERNAL_FAIL: u8 = 1; // Revert if external call fails
const PROXY_WITH_SENDER: u8 = 2; // Include sender in proxy call
const SEND_HASHED_DATA: u8 = 3; // Send data as hash
const DIRECT_WALLET_FLOW: u8 = 31; // Use direct wallet flow
// Setting flags on submission params
let mut flags = [0u8; 32];
flags.set_reserved_flag(UNWRAP_ETH);
flags.set_reserved_flag(REVERT_IF_EXTERNAL_FAIL);
Sending Cross-Chain Transfers
Basic Token Transfer
use debridge_solana_sdk::prelude::*;
pub fn send_tokens(
ctx: Context<SendTokens>,
amount: u64,
) -> Result<()> {
debridge_sending::invoke_debridge_send(
debridge_sending::SendIx {
target_chain_id: chain_ids::ETHEREUM_CHAIN_ID,
receiver: recipient_eth_address.to_vec(),
is_use_asset_fee: false,
amount,
submission_params: None,
referral_code: Some(12345), // Optional referral
},
ctx.remaining_accounts,
)?;
Ok(())
}
Transfer with Fixed Native Fee
pub fn send_with_native_fee(
ctx: Context<Send>,
target_chain_id: [u8; 32],
receiver: Vec<u8>,
amount: u64,
) -> Result<()> {
// Get the fixed fee for the target chain
let fee = debridge_sending::get_chain_native_fix_fee(
&target_chain_id,
ctx.remaining_accounts,
)?;
debridge_sending::invoke_debridge_send(
debridge_sending::SendIx {
target_chain_id,
receiver,
is_use_asset_fee: false,
amount,
submission_params: None,
referral_code: None,
},
ctx.remaining_accounts,
)?;
Ok(())
}
Transfer with Asset Fee
pub fn send_with_asset_fee(
ctx: Context<Send>,
target_chain_id: [u8; 32],
receiver: Vec<u8>,
amount: u64,
) -> Result<()> {
// Check if asset fee is available for this chain
let is_available = debridge_sending::is_asset_fee_available(
&target_chain_id,
ctx.remaining_accounts,
)?;
if !is_available {
return Err(error!(ErrorCode::AssetFeeNotAvailable));
}
debridge_sending::invoke_debridge_send(
debridge_sending::SendIx {
target_chain_id,
receiver,
is_use_asset_fee: true, // Use asset for fees
amount,
submission_params: None,
referral_code: None,
},
ctx.remaining_accounts,
)?;
Ok(())
}
Transfer with Exact Amount
pub fn send_exact_amount(
ctx: Context<Send>,
target_chain_id: [u8; 32],
receiver: Vec<u8>,
exact_receive_amount: u64,
) -> Result<()> {
// Calculate total amount including fees
let total_with_fees = debridge_sending::add_all_fees(
exact_receive_amount,
&target_chain_id,
ctx.remaining_accounts,
)?;
debridge_sending::invoke_debridge_send(
debridge_sending::SendIx {
target_chain_id,
receiver,
is_use_asset_fee: true,
amount: total_with_fees,
submission_params: None,
referral_code: None,
},
ctx.remaining_accounts,
)?;
Ok(())
}
Transfer from PDA (Signed)
pub fn send_from_pda(
ctx: Context<SendFromPda>,
target_chain_id: [u8; 32],
receiver: Vec<u8>,
amount: u64,
pda_seeds: Vec<Vec<u8>>,
) -> Result<()> {
// Use signed variant for PDA-owned tokens
debridge_sending::invoke_debridge_send_signed(
debridge_sending::SendIx {
target_chain_id,
receiver,
is_use_asset_fee: false,
amount,
submission_params: None,
referral_code: None,
},
ctx.remaining_accounts,
&pda_seeds,
)?;
Ok(())
}
Message Passing
Send messages without token transfers:
use debridge_solana_sdk::prelude::*;
pub fn send_message(
ctx: Context<SendMessage>,
target_chain_id: [u8; 32],
receiver: Vec<u8>,
message_data: Vec<u8>,
) -> Result<()> {
// Create submission params with message
let submission_params = debridge_sending::SendSubmissionParamsInput {
execution_fee: 0,
flags: [0u8; 32],
fallback_address: receiver.clone(),
external_call_shortcut: compute_keccak256(&message_data),
};
// Send message (zero amount)
debridge_sending::invoke_send_message(
debridge_sending::SendIx {
target_chain_id,
receiver,
is_use_asset_fee: false,
amount: 0, // No token transfer
submission_params: Some(submission_params),
referral_code: None,
},
ctx.remaining_accounts,
)?;
Ok(())
}
External Calls
Execute smart contract calls on destination chains:
Initialize External Call Buffer
pub fn init_external_call(
ctx: Context<InitExternalCall>,
target_chain_id: [u8; 32],
external_call_data: Vec<u8>,
) -> Result<()> {
let shortcut = compute_keccak256(&external_call_data);
debridge_sending::invoke_init_external_call(
debridge_sending::InitExternalCallIx {
external_call_len: external_call_data.len() as u32,
chain_id: target_chain_id,
external_call_shortcut: shortcut,
external_call: external_call_data,
},
ctx.remaining_accounts,
)?;
Ok(())
}
Send with External Call
pub fn send_with_external_call(
ctx: Context<SendWithExternalCall>,
target_chain_id: [u8; 32],
receiver: Vec<u8>, // Target contract address
amount: u64,
external_call_data: Vec<u8>,
execution_fee: u64, // Fee for executor on destination
) -> Result<()> {
let shortcut = compute_keccak256(&external_call_data);
// Set flags for external call behavior
let mut flags = [0u8; 32];
flags.set_reserved_flag(flags::REVERT_IF_EXTERNAL_FAIL);
let submission_params = debridge_sending::SendSubmissionParamsInput {
execution_fee,
flags,
fallback_address: ctx.accounts.fallback.key().to_bytes().to_vec(),
external_call_shortcut: shortcut,
};
debridge_sending::invoke_debridge_send(
debridge_sending::SendIx {
target_chain_id,
receiver,
is_use_asset_fee: false,
amount,
submission_params: Some(submission_params),
referral_code: None,
},
ctx.remaining_accounts,
)?;
Ok(())
}
Claim Verification
Verify claims on the receiving side:
Validate Incoming Claims
use debridge_solana_sdk::check_claiming::*;
pub fn receive_tokens(ctx: Context<ReceiveTokens>) -> Result<()> {
// Get and validate the parent claim instruction
let claim_ix = ValidatedExecuteExtCallIx::try_from_current_ix()?;
// Validate submission details
let validation = SubmissionAccountValidation {
receiver_validation: Some(ctx.accounts.receiver.key()),
token_mint_validation: Some(ctx.accounts.token_mint.key()),
source_chain_id_validation: Some(chain_ids::ETHEREUM_CHAIN_ID),
..Default::default()
};
claim_ix.validate_submission_account(
&ctx.accounts.submission_account,
&validation,
)?;
// Proceed with claim logic
Ok(())
}
Get Submission Key
pub fn get_claim_info(ctx: Context<ClaimInfo>) -> Result<Pubkey> {
let claim_ix = ValidatedExecuteExtCallIx::try_from_current_ix()?;
let submission_key = claim_ix.get_submission_key()?;
Ok(submission_key)
}
Fee Queries
Get Transfer Fees
// Get base transfer fee (in BPS)
let transfer_fee = debridge_sending::get_transfer_fee(
ctx.remaining_accounts,
)?;
// Get transfer fee for specific chain
let chain_fee = debridge_sending::get_transfer_fee_for_chain(
&target_chain_id,
ctx.remaining_accounts,
)?;
// Get default native fix fee
let default_fee = debridge_sending::get_default_native_fix_fee(
ctx.remaining_accounts,
)?;
// Get chain-specific native fix fee
let native_fee = debridge_sending::get_chain_native_fix_fee(
&target_chain_id,
ctx.remaining_accounts,
)?;
// Get asset fix fee for chain
let asset_fee = debridge_sending::try_get_chain_asset_fix_fee(
&target_chain_id,
ctx.remaining_accounts,
)?;
Calculate Total Amount with Fees
// Add transfer fee to amount
let with_transfer_fee = debridge_sending::add_transfer_fee(
amount,
ctx.remaining_accounts,
)?;
// Add all fees (transfer + execution + asset fees)
let total_amount = debridge_sending::add_all_fees(
amount,
&target_chain_id,
ctx.remaining_accounts,
)?;
Chain Support Queries
// Check if chain is supported
let is_supported = debridge_sending::is_chain_supported(
&target_chain_id,
ctx.remaining_accounts,
)?;
// Get chain support info
let chain_info = debridge_sending::get_chain_support_info(
&target_chain_id,
ctx.remaining_accounts,
)?;
// Check if asset fee is available
let asset_fee_available = debridge_sending::is_asset_fee_available(
&target_chain_id,
ctx.remaining_accounts,
)?;
PDA Derivation
Bridge Account
use debridge_solana_sdk::keys::*;
// Find bridge PDA for a token mint
let (bridge_address, bump) = BridgePubkey::find_bridge_address(&token_mint);
// Create with known bump
let bridge_address = BridgePubkey::create_bridge_address(&token_mint, bump)?;
Chain Support Info
// Find chain support info PDA
let (chain_support_info, bump) = ChainSupportInfoPubkey::find_chain_support_info_address(
&target_chain_id,
);
Asset Fee Info
// Find asset fee info PDA
let (asset_fee_info, bump) = AssetFeeInfoPubkey::find_asset_fee_info_address(
&bridge_pubkey,
&target_chain_id,
);
// Get default bridge fee address
let default_fee = AssetFeeInfoPubkey::default_bridge_fee_address();
External Call Storage
// Find external call storage PDA
let (storage, bump) = ExternalCallStoragePubkey::find_external_call_storage_address(
&shortcut,
&owner,
);
// Find external call meta PDA
let (meta, bump) = ExternalCallMetaPubkey::find_external_call_meta_address(
&storage_account,
);
Required Accounts
The SDK requires specific accounts passed via remaining_accounts. The account order is important:
| Index | Account | Signer | Writable | Description |
|---|---|---|---|---|
| 0 | Bridge | No | Yes | Bridge account for token |
| 1 | Token Mint | No | No | SPL Token mint |
| 2 | Staking Wallet | No | Yes | Staking rewards wallet |
| 3 | Mint Authority | No | No | Token mint authority |
| 4 | Chain Support Info | No | No | Target chain config |
| 5 | Settings Program | No | No | deBridge settings |
| 6 | SPL Token Program | No | No | Token program |
| 7 | State | No | No | Protocol state |
| 8 | deBridge Program | No | No | Main deBridge program |
| ... | Additional accounts | - | - | Varies by operation |
TypeScript Client Integration
Setup
import { Connection, Keypair, PublicKey, Transaction } from '@solana/web3.js';
import { Program, AnchorProvider, Wallet } from '@coral-xyz/anchor';
const connection = new Connection('https://api.mainnet-beta.solana.com');
const wallet = new Wallet(keypair);
const provider = new AnchorProvider(connection, wallet, {});
// deBridge Program IDs
const DEBRIDGE_PROGRAM_ID = new PublicKey('DEbrdGj3HsRsAzx6uH4MKyREKxVAfBydijLUF3ygsFfh');
const SETTINGS_PROGRAM_ID = new PublicKey('DeSetTwWhjZq6Pz9Kfdo1KoS5NqtsM6G8ERbX4SSCSft');
Build Send Transaction
import {
TOKEN_PROGRAM_ID,
getAssociatedTokenAddress
} from '@solana/spl-token';
async function buildSendTransaction(
tokenMint: PublicKey,
amount: bigint,
targetChainId: Uint8Array,
receiver: Uint8Array,
): Promise<Transaction> {
// Derive required PDAs
const [bridge] = PublicKey.findProgramAddressSync(
[Buffer.from('BRIDGE'), tokenMint.toBuffer()],
DEBRIDGE_PROGRAM_ID
);
const [chainSupportInfo] = PublicKey.findProgramAddressSync(
[Buffer.from('CHAIN_SUPPORT_INFO'), targetChainId],
SETTINGS_PROGRAM_ID
);
const [state] = PublicKey.findProgramAddressSync(
[Buffer.from('STATE')],
DEBRIDGE_PROGRAM_ID
);
// Build instruction with remaining accounts
const instruction = await program.methods
.sendViaDebridge(
Array.from(targetChainId),
Array.from(receiver),
new BN(amount.toString()),
)
.remainingAccounts([
{ pubkey: bridge, isSigner: false, isWritable: true },
{ pubkey: tokenMint, isSigner: false, isWritable: false },
// ... additional required accounts
])
.instruction();
return new Transaction().add(instruction);
}
Build External Call Data
import { ethers } from 'ethers';
import { keccak256 } from '@ethersproject/keccak256';
function buildExternalCallData(
targetContract: string,
functionSig: string,
params: any[]
): { data: Uint8Array; shortcut: Uint8Array } {
const iface = new ethers.Interface([functionSig]);
const calldata = iface.encodeFunctionData(
functionSig.split('(')[0].replace('function ', ''),
params
);
const data = ethers.getBytes(calldata);
const shortcut = ethers.getBytes(keccak256(data));
return { data, shortcut };
}
// Example: ERC20 approve call
const { data, shortcut } = buildExternalCallData(
'0xTargetContract...',
'function approve(address spender, uint256 amount)',
['0xSpenderAddress...', ethers.parseEther('1000')]
);
Testing
Anchor Test Setup
# Anchor.toml
[provider]
cluster = "mainnet" # Use mainnet for testing with real deBridge
[programs.mainnet]
my_program = "YourProgramId..."
Run Tests
# Full build and test
cd example_program && anchor build && anchor test
# Test only (skip rebuild)
anchor test --skip-build --skip-deploy
Local Testing Tips
- Use Mainnet Fork: deBridge infrastructure is on mainnet
- Mock Remaining Accounts: Create mock accounts for unit tests
- Test Fee Calculations: Verify fee amounts before sending
Build Features
The SDK supports different environments via Cargo features:
# Production (default) - uses hardcoded program IDs
debridge-solana-sdk = { git = "..." }
# Custom environment - uses env vars
debridge-solana-sdk = { git = "...", features = ["env"] }
Environment variables for custom networks:
DEBRIDGE_PROGRAM_PUBKEY: Custom deBridge program IDDEBRIDGE_SETTINGS_PROGRAM_PUBKEY: Custom settings program ID
Resources
Skill Structure
debridge/
├── SKILL.md # This file
├── resources/
│ ├── sdk-api-reference.md # Complete SDK API reference
│ ├── chain-ids.md # Supported chain identifiers
│ ├── program-ids.md # Program IDs and PDAs
│ └── error-codes.md # Error types and handling
├── examples/
│ ├── basic-transfer/ # Simple cross-chain transfer
│ ├── external-calls/ # External call execution
│ ├── message-passing/ # Message-only transfers
│ └── fee-configurations/ # Fee payment options
└── docs/
└── troubleshooting.md # Common issues and solutions
pyth
View full →Author
@0xinit
Stars
53
Repository
0xinit/cryptoskills
Pyth Network Development Guide
Pyth Network is a decentralized oracle providing real-time price feeds for cryptocurrencies, equities, forex, and commodities. This guide covers integrating Pyth price feeds into Solana applications.
Overview
Pyth Network provides:
- Real-Time Price Feeds - 400ms update frequency with pull oracle model
- Confidence Intervals - Statistical uncertainty bounds for each price
- EMA Prices - Exponential moving average prices (~1 hour window)
- Multi-Asset Support - Crypto, equities, FX, commodities, indices
- On-Chain Integration - CPI for Solana programs
- Off-Chain Integration - HTTP and WebSocket APIs via Hermes
Program IDs
| Program | Address | Description |
|---|---|---|
| Solana Receiver | rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ | Posts price updates to Solana |
| Price Feed | pythWSnswVUd12oZpeFP8e9CVaEqJg25g1Vtc2biRsT | Stores price feed data |
Deployed on: Solana Mainnet, Devnet, Eclipse Mainnet/Testnet, Sonic networks
Popular Price Feed IDs
| Asset | Hex Feed ID |
|---|---|
| BTC/USD | 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43 |
| ETH/USD | 0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace |
| SOL/USD | 0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d |
| USDC/USD | 0xeaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a |
| USDT/USD | 0x2b89b9dc8fdf9f34709a5b106b472f0f39bb6ca9ce04b0fd7f2e971688e2e53b |
Full list: https://pyth.network/developers/price-feed-ids
Quick Start
Installation
# TypeScript/JavaScript
npm install @pythnetwork/hermes-client @pythnetwork/pyth-solana-receiver
# Rust (add to Cargo.toml)
# pyth-solana-receiver-sdk = "0.3.0"
Fetch Price (Off-Chain)
import { HermesClient } from "@pythnetwork/hermes-client";
const client = new HermesClient("https://hermes.pyth.network");
const priceIds = [
"0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43", // BTC/USD
];
const priceUpdates = await client.getLatestPriceUpdates(priceIds);
for (const update of priceUpdates.parsed) {
const price = update.price;
const displayPrice = Number(price.price) * Math.pow(10, price.expo);
console.log(`Price: $${displayPrice.toFixed(2)}`);
console.log(`Confidence: ±${Number(price.conf) * Math.pow(10, price.expo)}`);
}
Use Price On-Chain (Rust/Anchor)
use anchor_lang::prelude::*;
use pyth_solana_receiver_sdk::price_update::PriceUpdateV2;
#[derive(Accounts)]
pub struct UsePrice<'info> {
pub price_update: Account<'info, PriceUpdateV2>,
}
pub fn use_price(ctx: Context<UsePrice>) -> Result<()> {
let price_update = &ctx.accounts.price_update;
let clock = Clock::get()?;
// Get price no older than 60 seconds
let price = price_update.get_price_no_older_than(
&clock,
60, // max age in seconds
)?;
msg!("Price: {} × 10^{}", price.price, price.exponent);
msg!("Confidence: ±{}", price.conf);
Ok(())
}
Core Concepts
Price Structure
Each Pyth price contains:
| Field | Type | Description |
|---|---|---|
price | i64 | Price value in fixed-point format |
conf | u64 | Confidence interval (standard deviation) |
expo | i32 | Exponent for scaling (e.g., -8 means divide by 10^8) |
publish_time | i64 | Unix timestamp of price |
Converting to display price:
const displayPrice = price * Math.pow(10, expo);
// Example: price=19405100, expo=-2 → $194,051.00
Confidence Intervals
Confidence intervals represent the uncertainty in the reported price:
// Price is $50,000 ± $50 means:
// - 68% chance true price is between $49,950 - $50,050
// - Use confidence for risk management
const price = 50000;
const confidence = 50;
// Safe lower bound (conservative)
const safeLowerBound = price - confidence;
// Safe upper bound (conservative)
const safeUpperBound = price + confidence;
Best Practice: Reject prices with confidence > 2% of price:
const maxConfidenceRatio = 0.02; // 2%
const confidenceRatio = confidence / Math.abs(price);
if (confidenceRatio > maxConfidenceRatio) {
throw new Error("Price confidence too wide");
}
EMA Prices
Exponential Moving Average prices smooth out short-term volatility:
- ~1 hour averaging window (5921 Solana slots)
- Weighted by inverse confidence (tight confidence = more weight)
- Good for: liquidations, collateral valuation
- Available as
ema_priceandema_conf
// Use EMA for less volatile applications
const emaPrice = priceUpdate.emaPrice;
const emaConf = priceUpdate.emaConf;
Off-Chain Integration
Hermes Client
Hermes is the recommended way to fetch Pyth prices off-chain.
Public Endpoint: https://hermes.pyth.network
For production, get a dedicated endpoint from a Pyth data provider.
Fetching Latest Prices
import { HermesClient } from "@pythnetwork/hermes-client";
const client = new HermesClient("https://hermes.pyth.network");
// Single price
const btcPrice = await client.getLatestPriceUpdates([
"0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43"
]);
// Multiple prices in one request
const prices = await client.getLatestPriceUpdates([
"0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43", // BTC
"0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace", // ETH
"0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d", // SOL
]);
Streaming Real-Time Updates
import { HermesClient } from "@pythnetwork/hermes-client";
const client = new HermesClient("https://hermes.pyth.network");
const priceIds = [
"0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43"
];
// Subscribe to real-time updates via SSE
const eventSource = await client.getPriceUpdatesStream(priceIds, {
parsed: true,
});
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log("Price update:", data);
};
eventSource.onerror = (error) => {
console.error("Stream error:", error);
eventSource.close();
};
// Close when done
// eventSource.close();
Posting Prices to Solana
import { PythSolanaReceiver } from "@pythnetwork/pyth-solana-receiver";
import { HermesClient } from "@pythnetwork/hermes-client";
import { Connection, Keypair } from "@solana/web3.js";
const connection = new Connection("https://api.mainnet-beta.solana.com");
const wallet = Keypair.fromSecretKey(/* your key */);
const hermesClient = new HermesClient("https://hermes.pyth.network");
const pythReceiver = new PythSolanaReceiver({ connection, wallet });
// Fetch price update data
const priceUpdateData = await hermesClient.getLatestPriceUpdates([
"0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43"
]);
// Build transaction to post price
const transactionBuilder = pythReceiver.newTransactionBuilder();
await transactionBuilder.addPostPriceUpdates(priceUpdateData.binary.data);
// Add your program instruction that uses the price
// transactionBuilder.addInstruction(yourInstruction);
// Send transaction
const transactions = await transactionBuilder.buildVersionedTransactions({
computeUnitPriceMicroLamports: 50000,
});
for (const tx of transactions) {
const sig = await connection.sendTransaction(tx);
console.log("Transaction:", sig);
}
On-Chain Integration (Rust)
Setup
Add to Cargo.toml:
[dependencies]
pyth-solana-receiver-sdk = "0.3.0"
anchor-lang = "0.30.1"
Reading Price in Anchor Program
use anchor_lang::prelude::*;
use pyth_solana_receiver_sdk::price_update::{PriceUpdateV2, get_feed_id_from_hex};
declare_id!("YourProgramId...");
// BTC/USD price feed ID
const BTC_USD_FEED_ID: &str = "0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43";
#[program]
pub mod my_program {
use super::*;
pub fn check_price(ctx: Context<CheckPrice>) -> Result<()> {
let price_update = &ctx.accounts.price_update;
let clock = Clock::get()?;
// Verify this is the correct feed
let feed_id = get_feed_id_from_hex(BTC_USD_FEED_ID)?;
// Get price no older than 60 seconds
let price = price_update.get_price_no_older_than_with_custom_verification(
&clock,
60,
&feed_id,
ctx.accounts.price_update.to_account_info().owner,
)?;
msg!("BTC/USD Price: {} × 10^{}", price.price, price.exponent);
msg!("Confidence: ±{}", price.conf);
Ok(())
}
}
#[derive(Accounts)]
pub struct CheckPrice<'info> {
#[account(
constraint = price_update.to_account_info().owner == &pyth_solana_receiver_sdk::ID
)]
pub price_update: Account<'info, PriceUpdateV2>,
}
Using Price for Calculations
pub fn swap_with_oracle(
ctx: Context<SwapWithOracle>,
amount_in: u64,
) -> Result<()> {
let price_update = &ctx.accounts.price_update;
let clock = Clock::get()?;
// Get price with staleness check
let price = price_update.get_price_no_older_than(&clock, 30)?;
// Validate confidence (max 1% of price)
let conf_ratio = (price.conf as u128 * 10000) / (price.price.unsigned_abs() as u128);
require!(conf_ratio <= 100, ErrorCode::ConfidenceTooWide);
// Convert price to usable format
// price.price is in fixed-point with price.exponent
let price_scaled = if price.exponent >= 0 {
(price.price as u128) * 10_u128.pow(price.exponent as u32)
} else {
(price.price as u128) / 10_u128.pow((-price.exponent) as u32)
};
// Calculate output amount using oracle price
let amount_out = (amount_in as u128)
.checked_mul(price_scaled)
.ok_or(ErrorCode::MathOverflow)?
/ 1_000_000; // Adjust for decimals
msg!("Swap {} -> {} using price {}", amount_in, amount_out, price_scaled);
Ok(())
}
#[error_code]
pub enum ErrorCode {
#[msg("Price confidence interval too wide")]
ConfidenceTooWide,
#[msg("Math overflow")]
MathOverflow,
}
Multiple Price Feeds
#[derive(Accounts)]
pub struct Liquidation<'info> {
#[account(
constraint = collateral_price.to_account_info().owner == &pyth_solana_receiver_sdk::ID
)]
pub collateral_price: Account<'info, PriceUpdateV2>,
#[account(
constraint = debt_price.to_account_info().owner == &pyth_solana_receiver_sdk::ID
)]
pub debt_price: Account<'info, PriceUpdateV2>,
}
pub fn check_liquidation(ctx: Context<Liquidation>) -> Result<bool> {
let clock = Clock::get()?;
let collateral = ctx.accounts.collateral_price
.get_price_no_older_than(&clock, 60)?;
let debt = ctx.accounts.debt_price
.get_price_no_older_than(&clock, 60)?;
// Normalize to same exponent for comparison
let collateral_value = normalize_price(collateral.price, collateral.exponent);
let debt_value = normalize_price(debt.price, debt.exponent);
// Check if undercollateralized
let is_liquidatable = collateral_value < debt_value * 150 / 100; // 150% ratio
Ok(is_liquidatable)
}
fn normalize_price(price: i64, expo: i32) -> i128 {
let target_expo = -8; // Normalize to 8 decimals
let adjustment = expo - target_expo;
if adjustment >= 0 {
(price as i128) * 10_i128.pow(adjustment as u32)
} else {
(price as i128) / 10_i128.pow((-adjustment) as u32)
}
}
Best Practices
1. Always Check Staleness
// Don't use old prices - set appropriate max age
let max_age_seconds = 60;
let price = price_update.get_price_no_older_than(&clock, max_age_seconds)?;
2. Validate Confidence Intervals
// Reject prices with wide confidence (high uncertainty)
const MAX_CONF_BPS: u64 = 200; // 2%
let conf_bps = (price.conf as u128 * 10000) / (price.price.unsigned_abs() as u128);
require!(conf_bps <= MAX_CONF_BPS as u128, ErrorCode::ConfidenceTooWide);
3. Verify Account Ownership
// Always verify the price account is owned by Pyth
#[account(
constraint = price_update.to_account_info().owner == &pyth_solana_receiver_sdk::ID
)]
pub price_update: Account<'info, PriceUpdateV2>,
4. Use EMA for Sensitive Operations
// For liquidations, use EMA to avoid manipulation
let ema_price = price_update.get_ema_price_no_older_than(&clock, 60)?;
5. Handle Price Unavailability
try {
const price = await client.getLatestPriceUpdates([feedId]);
// Use price
} catch (error) {
// Fallback behavior or reject transaction
console.error("Price unavailable:", error);
}
6. Consider Frontrunning
- Adversaries may see price updates before your transaction
- Don't design logic that races against price updates
- Use appropriate slippage tolerances
Price Feed Types
Fixed Price Feed Accounts
- Maintained continuously by Pyth
- Fixed address per feed
- Always has most recent price
- Shared by all users (potential congestion)
Ephemeral Price Update Accounts
- Created per transaction
- Can specify shard ID for parallelization
- Rent can be recovered after use
- Better for high-throughput applications
// Use shard ID to avoid congestion
const transactionBuilder = pythReceiver.newTransactionBuilder({
shardId: Math.floor(Math.random() * 65536), // Random shard
});
Resources
Official Documentation
GitHub Repositories
NPM Packages
Rust Crates
Skill Structure
pyth/
├── SKILL.md # This file
├── resources/
│ ├── program-addresses.md # All program IDs and feed IDs
│ └── api-reference.md # SDK API reference
├── examples/
│ ├── price-feeds/
│ │ ├── fetch-price.ts # Basic price fetching
│ │ └── multiple-prices.ts # Multiple price feeds
│ ├── on-chain/
│ │ ├── anchor-integration.rs # Anchor program example
│ │ └── price-validation.rs # Price validation patterns
│ └── streaming/
│ └── real-time-updates.ts # WebSocket streaming
├── templates/
│ ├── pyth-client.ts # TypeScript client template
│ └── anchor-oracle.rs # Anchor program template
└── docs/
└── troubleshooting.md # Common issues and solutions
Pyth on EVM Chains
This skill covers Pyth integration for Solana applications using Anchor CPI. For EVM chain integration (Ethereum, Arbitrum, Base, Optimism, Polygon, and 50+ other chains), see the pyth-evm skill.
Key differences between Pyth Solana and Pyth EVM:
| Aspect | Pyth Solana (this skill) | Pyth EVM (pyth-evm skill) |
|---|---|---|
| Contract interface | Anchor CPI to Pyth program | Solidity IPyth interface |
| Price update | Pull from Pyth accumulator account | Submit bytes[] via updatePriceFeeds |
| Contract address | Single Pyth program on Solana | Varies per EVM chain |
| Gas/compute | Compute units | ~120-150K gas per feed update |
| SDK | @pythnetwork/pyth-solana-receiver | @pythnetwork/hermes-client v3.1.0 |
Price feed IDs (bytes32) are the same across all chains — a BTC/USD feed ID works on both Solana and Ethereum.
Related Skills
pyth-evm— Pyth oracle integration for EVM chains (Solidity + TypeScript)chainlink— Push oracle alternative on EVM chainsredstone— Another pull oracle for EVM chains