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指令的代码中表示为AccountInfo
。load_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工具。
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);
最佳实践
- 验证账户所有权:在使用Pyth价格数据前,验证价格账户的所有权是否为Pyth程序
use pyth_sdk_solana::PYTH_PROGRAM_ID;
if price_account_info.owner != &PYTH_PROGRAM_ID {
return Err(ProgramError::InvalidAccountOwner);
}
- 检查价格新鲜度:确保价格数据是最新的
let current_timestamp = Clock::get()?.unix_timestamp;
if current_timestamp - price_account.get_timestamp() > 60 {
// 价格数据超过60秒未更新
return Err(ProgramError::Custom(2));
}
- 处理价格精度: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(())
}
注意事项
- Pyth价格账户需要定期更新,确保使用最新的价格数据
- 价格数据有置信区间(confidence),处理重要交易时应考虑这一点
- 不同资产的价格账户有不同的精度(exponent),使用时需要调整
- 在生产环境中,应该添加更多的错误处理和边界检查
通过pyth-sdk-solana库,开发者可以轻松地将高精度的金融市场数据集成到Solana智能合约中,为DeFi应用提供可靠的价格信息。