Rust Solana数据预言机库pyth-sdk-solana的使用,实现区块链智能合约的高精度价格数据获取

Rust Solana数据预言机库pyth-sdk-solana的使用,实现区块链智能合约的高精度价格数据获取

Pyth Network Solana SDK

这个库提供了从Solana网络上的Pyth Network预言机读取价格数据的工具。它还包含几个链下示例程序。

安装

在您的Cargo.toml中添加依赖:

[dependencies]
pyth-sdk-solana="<version>"

使用

Pyth Network将其价格数据存储在各种类型的Solana账户中:

  • 价格账户存储产品的当前价格
  • 产品账户存储产品的元数据,如符号(例如"BTC/USD")
  • 映射账户存储所有Pyth账户的列表

大多数SDK用户只需要访问价格账户的内容;其他两种账户类型是预言机的实现细节。

应用程序可以通过两种不同的方式获取这些账户的内容:

  • 链上程序应将这些账户传递给需要价格数据的指令
  • 链下程序可以使用Solana RPC客户端访问这些账户

链上使用

链上应用程序应将相关的Pyth Network价格账户传递给消费它的Solana指令。这个价格账户将在Solana指令的代码中表示为AccountInfoload_price_feed_from_account_info函数将从AccountInfo构造一个PriceFeed结构体:

use pyth_sdk_solana::{load_price_feed_from_account_info, PriceFeed};

const STALENESS_THRESHOLD : u64 = 60; // 陈旧性阈值(秒)
let price_account_info: AccountInfo = ...;
let price_feed: PriceFeed = load_price_feed_from_account_info( &price_account_info ).unwrap();
let current_timestamp = Clock::get()?.unix_timestamp;
let current_price: Price = price_feed.get_price_no_older_than(current_timestamp, STALENESS_THRESHOLD).unwrap();
msg!("price: ({} +- {}) x 10^{}", current_price.price, current_price.conf, current_price.expo);

load_price_feed_from_account_info返回的PriceFeed对象包含产品所有当前可用的定价信息。这个结构体还有一些用于操作和组合价格的有用函数。

get_price_no_older_than函数接受一个以秒为单位的age参数。如果当前链上聚合数据比current_timestamp - age更旧,get_price_no_older_than将返回None

注意:您的应用程序在使用传入的价格账户之前还应验证其地址。否则,攻击者可能会传入不同的账户并将价格设置为任意值。

链下使用

链下应用程序可以使用Solana RPC客户端读取Pyth Network价格账户的当前值。此客户端将返回账户内容作为Account结构体。load_price_f feed_from_account函数将从Account构造一个PriceFeed结构体:

use pyth_sdk_solana::{load_price_feed_from_account, PriceFeed};

const STALENESS_THRESHOLD : u64 = 60; // 陈旧性阈值(秒)
let current_time = SystemTime::now()
    .duration_since(UNIX_EPOCH)
    .unwrap()
    .as_secs() as i64;

let price_key: Pubkey = ...;
let mut price_account: Account = clnt.get_account(&price_key).unwrap();
let price_feed: PriceFeed = load_price_feed_from_account( &price_key, &mut price_account ).unwrap();
let current_price: Price = price_feed.get_price_no_older_than(current_time, STALENESS_THRESHOLD).unwrap();
println!("price: ({} +- {}) x 10^{}", current_price.price, current_price.conf, current_price.expo);

完整示例代码

基于上述内容,这里提供一个扩展的完整链下获取价格数据的示例,包含多个交易对查询和错误处理:

use solana_client::rpc_client::RpcClient;
use solana_sdk::pubkey::Pubkey;
use pyth_sdk_solana::{load_price_feed_from_account, PriceFeed};
use std::time::{SystemTime, UNIX_EPOCH};

// 定义Pyth网络价格账户地址
const PYTH_PRICE_ACCOUNTS: &[(&str, &str)] = &[
    ("BTC/USD", "HovQMDrbAgAYPCmhvVS7Xpss7KPSB5k4xQfL7n2Uz1hX"),
    ("ETH/USD", "JBu1AL4obBqCV5ojS3BAK3rUu1K14J4wyDDDvT3xJquX"),
    ("SOL/USD", "H6ARHf6YXhGYeQfUzQNGk6rDNnLBQKrenN712K4AQJEG"),
];

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 配置Solana RPC客户端
    let rpc_url = "https://api.mainnet-beta.solana.com";
    let client = RpcClient::new(rpc_url);
    
    // 获取当前时间戳
    let current_time = SystemTime::now()
        .duration_since(UNIX_EPOCH)?
        .as_secs() as i64;
    
    // 设置陈旧性阈值(60秒)
    const STALENESS_THRESHOLD: u64 = 60;

    // 查询所有定义的价格对
    for (symbol, account) in PYTH_PRICE_ACCOUNTS {
        match get_price_data(&client, account, current_time, STALENESS_THRESHOLD) {
            Ok(price_feed) => {
                println!("\n{} 价格数据:", symbol);
                print_price_info(&price_feed, current_time)?;
            }
            Err(e) => eprintln!("获取 {} 价格失败: {}", symbol, e),
        }
    }
    
    Ok(())
}

// 获取指定价格账户的数据
fn get_price_data(
    client: &RpcClient,
    account: &str,
    current_time: i64,
    staleness_threshold: u64,
) -> Result<PriceFeed, Box<dyn std::error::Error>> {
    let price_account = Pubkey::try_from(account)?;
    let mut account_data = client.get_account(&price_account)?;
    
    let price_feed = load_price_feed_from_account(&price_account, &mut account_data)?;
    
    // 验证价格数据是否足够新
    price_feed
        .get_price_no_older_than(current_time, staleness_threshold)
        .ok_or("价格数据已过期")?;
    
    Ok(price_feed)
}

// 打印价格信息
fn print_price_info(price_feed: &PriceFeed, current_time: i64) -> Result<(), Box<dyn std::error::Error>> {
    let current_price = price_feed.get_current_price().ok_or("无法获取当前价格")?;
    
    println!("当前价格: ({} ± {}) x 10^{}", 
        current_price.price, 
        current_price.conf, 
        current_price.expo);
    println!("发布时间: {}", current_price.publish_time);
    println!("延迟: {} 秒", current_time - current_price.publish_time);
    
    // 计算实际价格值(考虑指数)
    let price_value = current_price.price as f64 * 10_f64.powf(current_price.expo as f64);
    let conf_value = current_price.conf as f64 * 10_f64.powf(current_price.expo as f64);
    println!("实际价格: {:.2} ± {:.2}", price_value, conf_value);
    
    Ok(())
}

开发

这个库可以为您本地平台或BPF(Solana程序使用)构建。使用cargo build/cargo test进行本地构建和测试。使用cargo build-bpf/cargo test-bpf为Solana构建BPF;这些命令需要您安装Solana CLI工具。


1 回复

Rust Solana数据预言机库pyth-sdk-solana使用指南

概述

pyth-sdk-solana是一个用于在Solana区块链上访问Pyth网络价格数据的Rust库。Pyth是一个去中心化的预言机网络,提供高精度、实时的金融市场数据。这个SDK允许智能合约开发者获取各种资产(如加密货币、股票、外汇等)的价格信息。

安装

在Cargo.toml中添加依赖:

[dependencies]
pyth-sdk-solana = "0.10.0"

基本使用方法

1. 获取价格数据

use pyth_sdk_solana::{load_price_account, PriceAccount};

// 假设price_account_data是从Solana账户获取的二进制数据
let price_account: PriceAccount = load_price_account(&price_account_data)
    .expect("Failed to load price account");

// 获取当前价格
let current_price = price_account.get_current_price().unwrap();
println!("Current price: {}", current_price.price);
println!("Confidence interval: {}", current_price.conf);

2. 获取产品信息

use pyth_sdk-solana::{load_product_account, ProductAccount};

// 假设product_account_data是从Solana账户获取的二进制数据
let product_account: ProductAccount = load_product_account(&product_account_data)
    .expect("Failed to load product account");

// 获取产品属性
println!("Product symbol: {:?}", product_account.symbol());
println!("Product asset_type: {:?}", product_account.asset_type());

3. 在Solana智能合约中使用

use solana_program::{
    account_info::AccountInfo,
    entrypoint,
    entrypoint::ProgramResult,
    msg,
    pubkey::Pubkey,
};
use pyth_sdk_solana::{load_price_account, PriceAccount};

entrypoint!(process_instruction);

fn process_instruction(
    _program_id: &Pubkey,
    accounts: &[AccountInfo],
    _instruction_data: &[u8],
) -> ProgramResult {
    // 假设第一个账户是Pyth价格账户
    let price_account_info = &accounts[0];
    let price_account_data = price_account_info.try_borrow_data()?;
    
    let price_account: PriceAccount = load_price_account(&price_account_data)
        .map_err(|e| {
            msg!("Failed to load price account: {:?}", e);
            ProgramError::InvalidAccountData
        })?;
    
    let current_price = price_account.get_current_price().ok_or_else(|| {
        msg!("No current price available");
        ProgramError::Custom(1
    })?;
    
    msg!("Current price: {}", current_price.price);
    msg!("Confidence interval: {}", current_price.conf);
    
    Ok(())
}

高级功能

1. 处理EMA(指数移动平均)价格

let ema_price = price_account.get_ema_price().unwrap();
println!("EMA price: {}", ema_price.price);
println!("EMA confidence: {}", ema_price.conf);

2. 检查价格状态

use pyth_sdk_solana::PriceStatus;

match price_account.get_current_price() {
    Some(price) if price_account.get_price_status() == PriceStatus::Trading => {
        // 价格可用且正在交易中
        println!("Valid trading price: {}", price.price);
    }
    _ => {
        println!("Price not available or not trading");
    }
}

3. 获取价格更新时间

let timestamp = price_account.get_timestamp();
println!("Last price update: {}", timestamp);

最佳实践

  1. 验证账户所有权:在使用Pyth价格数据前,验证价格账户的所有权是否为Pyth程序
use pyth_sdk_solana::PYTH_PROGRAM_ID;

if price_account_info.owner != &PYTH_PROGRAM_ID {
    return Err(ProgramError::InvalidAccountOwner);
}
  1. 检查价格新鲜度:确保价格数据是最新的
let current_timestamp = Clock::get()?.unix_timestamp;
if current_timestamp - price_account.get_timestamp() > 60 {
    // 价格数据超过60秒未更新
    return Err(ProgramError::Custom(2));
}
  1. 处理价格精度:Pyth价格通常有多个小数位
let price = current_price.price as f64 * 10f64.powf(-price_account.get_exponent() as f64);
println!("Adjusted price: {}", price);

完整示例:价格获取与交易验证合约

use solana_program::{
    account_info::{AccountInfo, next_account_info},
    entrypoint,
    entrypoint::ProgramResult,
    msg,
    program_error::ProgramError,
    pubkey::Pubkey,
    sysvar::clock::Clock,
    sysvar::Sysvar,
};
use pyth_sdk_solana::{load_price_account, PriceAccount, PriceStatus, PYTH_PROGRAM_ID};

entrypoint!(process_instruction);

fn process_instruction(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    instruction_data: &[u8],
) -> ProgramResult {
    // 解析账户
    let accounts_iter = &mut accounts.iter();
    let price_account_info = next_account_info(accounts_iter)?;
    let user_account_info = next_account_info(accounts_iter)?;
    
    // 1. 验证Pyth价格账户所有权
    if price_account_info.owner != &PYTH_PROGRAM_ID {
        msg!("错误:价格账户所有者不匹配");
        return Err(ProgramError::InvalidAccountOwner);
    }
    
    // 2. 加载价格账户数据
    let price_account_data = price_account_info.try_borrow_data()?;
    let price_account: PriceAccount = load_price_account(&price_account_data)
        .map_err(|e| {
            msg!("加载价格账户失败: {:?}", e);
            ProgramError::InvalidAccountData
        })?;
    
    // 3. 检查价格状态
    if price_account.get_price_status() != PriceStatus::Trading {
        msg!("错误:价格当前不可交易");
        return Err(ProgramError::Custom(1));
    }
    
    // 4. 检查价格新鲜度(30秒内)
    let current_timestamp = Clock::get()?.unix_timestamp;
    if current_timestamp - price_account.get_timestamp() > 30 {
        msg!("错误:价格数据已过期");
        return Err(ProgramError::Custom(2));
    }
    
    // 5. 获取当前价格和精度调整
    let current_price = price_account.get_current_price().ok_or_else(|| {
        msg!("错误:无当前价格数据");
        ProgramError::Custom(3)
    })?;
    
    let exponent = price_account.get_exponent();
    let adjusted_price = current_price.price as f64 * 10f64.powf(-exponent as f64);
    
    // 6. 示例交易逻辑:检查价格是否在预期范围内
    let expected_min_price = 50.0; // 最小预期价格
    if adjusted_price < expected_min_price {
        msg!("错误:价格低于最小值 {}", expected_min_price);
        return Err(ProgramError::Custom(4));
    }
    
    // 7. 记录交易信息
    msg!("成功执行交易:");
    msg!("资产价格: {}", adjusted_price);
    msg!("用户账户: {}", user_account_info.key);
    msg!("更新时间: {}", price_account.get_timestamp());
    
    Ok(())
}

注意事项

  1. Pyth价格账户需要定期更新,确保使用最新的价格数据
  2. 价格数据有置信区间(confidence),处理重要交易时应考虑这一点
  3. 不同资产的价格账户有不同的精度(exponent),使用时需要调整
  4. 在生产环境中,应该添加更多的错误处理和边界检查

通过pyth-sdk-solana库,开发者可以轻松地将高精度的金融市场数据集成到Solana智能合约中,为DeFi应用提供可靠的价格信息。

回到顶部