Comparing debridge with pyth

Author

@0xinit

Stars

53

Repository

0xinit/cryptoskills

skills/debridge/SKILL.md

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:

IndexAccountSignerWritableDescription
0BridgeNoYesBridge account for token
1Token MintNoNoSPL Token mint
2Staking WalletNoYesStaking rewards wallet
3Mint AuthorityNoNoToken mint authority
4Chain Support InfoNoNoTarget chain config
5Settings ProgramNoNodeBridge settings
6SPL Token ProgramNoNoToken program
7StateNoNoProtocol state
8deBridge ProgramNoNoMain 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

  1. Use Mainnet Fork: deBridge infrastructure is on mainnet
  2. Mock Remaining Accounts: Create mock accounts for unit tests
  3. 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 ID
  • DEBRIDGE_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

Author

@0xinit

Stars

53

Repository

0xinit/cryptoskills

skills/pyth/SKILL.md

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

ProgramAddressDescription
Solana Receiverrec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJPosts price updates to Solana
Price FeedpythWSnswVUd12oZpeFP8e9CVaEqJg25g1Vtc2biRsTStores price feed data

Deployed on: Solana Mainnet, Devnet, Eclipse Mainnet/Testnet, Sonic networks

Popular Price Feed IDs

AssetHex Feed ID
BTC/USD0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43
ETH/USD0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace
SOL/USD0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d
USDC/USD0xeaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a
USDT/USD0x2b89b9dc8fdf9f34709a5b106b472f0f39bb6ca9ce04b0fd7f2e971688e2e53b

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:

FieldTypeDescription
pricei64Price value in fixed-point format
confu64Confidence interval (standard deviation)
expoi32Exponent for scaling (e.g., -8 means divide by 10^8)
publish_timei64Unix 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_price and ema_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:

AspectPyth Solana (this skill)Pyth EVM (pyth-evm skill)
Contract interfaceAnchor CPI to Pyth programSolidity IPyth interface
Price updatePull from Pyth accumulator accountSubmit bytes[] via updatePriceFeeds
Contract addressSingle Pyth program on SolanaVaries per EVM chain
Gas/computeCompute 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 chains
  • redstone — Another pull oracle for EVM chains

AI Skill Finder

Ask me what skills you need

What are you building?

Tell me what you're working on and I'll find the best agent skills for you.