如何在 Solana 上使用 Pyth 获取价格数据

  • QuickNode
  • 发布于 2024-05-20 17:21
  • 阅读 12

本文介绍了如何在Solana程序中使用Pyth SDK获取实时价格数据,详细步骤包括创建Anchor项目、定义价格数据结构、获取并格式化价格数据,并最终在Solana的devnet上测试程序。

几个python库最近引入了影响本指南和Solana Playground使用的突破性更改。我们正在努力更新本指南以反映这些变化。同时, 你可以从python文档中看到一个示例实现。

概述

Pyth 是一个预言机,它以简单易用的方式将实时数据费用带到链上。Pyth 直接与 第一方数据发布者 合作,将金融资产定价数据带到链上。Pyth 聚合了加密货币、股票、外汇货币和商品的数据。通过与众多数据提供者合作,Pyth 能够提供高度的数据完整性和安全性。在本指南中,我们将学习如何在你的 Solana 程序中从 Pyth 获取价格数据。

你将做什么

在本指南中,你将学习如何使用 Pyth SDK 将实时定价数据集成到你的程序中。你将:

  1. 学习 Pyth 协议的基础知识
  2. 构建一个简单的 Solana 程序,该程序请求并记录来自 Pyth 的价格源
  3. 在 Solana 的 devnet 上测试该程序

你需要什么

本指南中使用的依赖项

在本指南中,我们将使用 Solana Playground。我们的测试是在 以下依赖项(2023 年 5 月 30 日)下进行的:

依赖项 版本
anchor-lang 0.27.0
solana-program 1.14.17
pyth-sdk-solana 0.7.0

Pyth 是如何工作的?

Pyth 是一个数据预言机,它将各种资产类别的价格源带到 Solana 区块链上。“Pyth 价格更新在 Pythnet 上创建,并通过 Wormhole 网络(一个跨链消息协议)流式传输到链下。这些更新被签名,因此 Pyth 链上程序可以验证其真实性。” *(来源:Pyth 文档) Pythnet 是一个由 Solana 区块链驱动的私有集群。由于 Pyth 使用 Wormhole,它可以将数据带到多个区块链。

对于 Solana,资产和定价数据通过 Solana 账户存储并在链上广播。Pyth 中存在三种主要类型的账户:

  1. 产品(元数据)账户:这些账户存储有关资产的信息(例如,符号、资产类型、描述)。
  2. 价格账户:这些账户存储资产的价格数据、价格的置信区间以及上次更新的时间。
  3. 映射账户:这些账户将产品账户映射到价格账户。

今天的练习将主要关注价格账户。有关其他账户类型的更多信息,请查看 Pyth 文档

启动一个新的 Anchor 项目

Anchor 新手?

Anchor 是一个流行的开发框架,用于在 Solana 上构建程序。 要开始使用,请查看我们的 Anchor 入门指南

我们将使用 Solana Playground 来加速我们的开发。Solana Playground 是一个基于 Web 的 IDE,允许你编写、部署和测试 Solana 程序。如果你更喜欢使用自己的本地 Anchor 项目,只需确保在你的 Cargo.toml 文件中添加 pyth-sdk-solana = "0.7.1"

前往 beta.solpg.io 并点击“➕”创建一个新项目:

  • 选择“Anchor (Rust)”
  • 将其命名为“Pyth Demo”并点击“创建”

创建一个新的 Anchor 项目

创建你的程序

让我们创建一个新程序,该程序将从 Pyth 获取价格源并将其记录到 Solana 程序日志中。打开 src>lib.rs,它应该已经预填充了一个简单的程序。继续删除默认内容。

让我们从导入我们程序所需的依赖项开始。我们将需要以下依赖项:

use anchor_lang::prelude::*;
use pyth_sdk_solana::{load_price_feed_from_account_info};
use std::str::FromStr;

// 这是你程序的公钥,它会在你构建项目时自动更新。
declare_id!("11111111111111111111111111111111");

除了 Anchor 依赖项外,我们还需要导入 Pyth SDK 的 load_price_feed_from_account_info 和 Rust 标准库中的 FromStr 特性(这将允许我们将字符串地址转换为公钥)。

Solana Playground 将使用默认的 declare_id! 字段来定义你的程序 ID。这将在你构建程序时自动更新。

我们还将定义两个常量:

  1. 一个价格源地址(这将是我们想要获取的价格源的地址)。所有可用的源列表可以在 这里 找到(确保从下拉菜单中选择“Solana Devnet”)。对于此演示,我们将使用 Solana devnet 上的 BTC/USD 价格源,HovQMDrbAgAYPCmHVSrezcSmkMtXSSUsLDFANExrZh2J
  2. 一个“陈旧阈值”,用于确定价格源是否陈旧(即,如果价格源在过去 60 秒内未更新,我们将认为它陈旧)

在你的导入下方添加以下声明:

const BTC_USDC_FEED: &str = "HovQMDrbAgAYPCmHVSrezcSmkMtXSSUsLDFANExrZh2J";
const STALENESS_THRESHOLD: u64 = 60; // 陈旧阈值,单位为秒

你可以随意尝试不同的价格源和陈旧阈值,但在我们的示例中,我们将寻找在过去 60 秒内创建的 BTC/USD 价格源。

创建一个价格源结构体

让我们为我们的价格源指令定义一个结构体。该结构体将定义我们必须传递给我们的程序以获取价格源的账户。在你的常量下方添加:

#[derive(Accounts)]
pub struct FetchBitcoinPrice<'info> {
    #[account(mut)]
    pub signer: Signer<'info>,
    #[account(address = Pubkey::from_str(BTC_USDC_FEED).unwrap() @ FeedError::InvalidPriceFeed)]
    pub price_feed: AccountInfo<'info>,
}

#[error_code]
pub enum FeedError {
    #[msg("Invalid Price Feed")]
    InvalidPriceFeed,
}

我们定义了一个名为 FetchBitcoinPrice 的结构体,它将有两个账户:

  1. signer:这将是签署交易并支付交易费用的账户。
  2. price_feed:这是我们想要获取的价格源账户。我们使用 Anchor 的 address 约束(了解更多关于 Anchor 约束的信息,请查看我们的指南 这里)。我们使用 Pubkey::from_str 函数将我们的 BTC_USDC_FEED 字符串转换为公钥。我们还定义了一个错误代码 InvalidPriceFeed,如果价格源账户无效,将抛出该错误。

创建一个价格源指令

让我们定义我们的程序并创建一个函数来获取价格源。在你的结构体下方添加:

#[program]
mod hello_pyth {
    use super::*;
    pub fn fetch_btc_price(ctx: Context<FetchBitcoinPrice>) -> Result<()> {
        // 1-获取最新价格

        // 2-格式化显示值,四舍五入到最接近的美元

        // 3-记录结果

        Ok(())
    }
}

我们定义了一个名为 hello_pyth 的程序和一个名为 fetch_btc_price 的函数,该函数将接受类型为 FetchBitcoinPriceContext 并返回一个 Result(要么是 Ok,要么是 Err)。

获取最新价格

为了获取最新价格,我们将价格源地址 &ctx.accounts.price_feed 传递给 Pyth 的 load_price_feed_from_account_info() 函数:

        // 1-获取最新价格
        let price_account_info = &ctx.accounts.price_feed;
        let price_feed = load_price_feed_from_account_info( &price_account_info ).unwrap();
        let current_timestamp = Clock::get()?.unix_timestamp;
        let current_price = price_feed.get_price_no_older_than(current_timestamp, STALENESS_THRESHOLD).unwrap();

我们还使用 Clock 结构体获取当前时间戳,并定义一个 current_price 变量,只要它不比我们之前指定的陈旧阈值更旧,就会获取价格。

格式化显示值

Pyth 定价数据存储为 64 位有符号整数和 32 位有符号指数。

详细信息

Pyth 的 Price 结构体 以下是 Pyth 的 Price 结构体的定义(来源):

pub struct Price {
    /// 价格。
    #[serde(with = "utils::as_string")] // 确保转换为 json 时的准确性。
    #[schemars(with = "String")]
    pub price:        i64,
    /// 置信区间。
    #[serde(with = "utils::as_string")]
    #[schemars(with = "String")]
    pub conf:         u64,
    /// 指数。
    pub expo:         i32,
    /// 发布时间。
    pub publish_time: UnixTimestamp,
}

为了以人类可读的格式记录价格(例如,27400 而不是 27400000000000),我们需要将 price 和置信区间(conf)转换为 u64,然后将其除以小数位数(我们也必须将其转换为 u64)。由于 Pyth 将其存储为负数,我们必须在除法之前将其转换为正数。将以下内容添加到你的 fetch_btc_price 函数中:

        // 2-格式化显示值,四舍五入到最接近的美元
        let display_price = u64::try_from(current_price.price).unwrap() / 10u64.pow(u32::try_from(-current_price.expo).unwrap());
        let display_confidence = u64::try_from(current_price.conf).unwrap() / 10u64.pow(u32::try_from(-current_price.expo).unwrap());

最后,我们将使用 msg! 将结果记录到 Solana 的程序日志中。你的最终指令应该如下所示:

#[program]
mod hello_pyth {
    use super::*;
    pub fn fetch_btc_price(ctx: Context<FetchBitcoinPrice>) -> Result<()> {
        // 1-获取最新价格
        let price_account_info = &ctx.accounts.price_feed;
        let price_feed = load_price_feed_from_account_info( &price_account_info ).unwrap();
        let current_timestamp = Clock::get()?.unix_timestamp;
        let current_price = price_feed.get_price_no_older_than(current_timestamp, STALENESS_THRESHOLD).unwrap();

        // 2-格式化显示值,四舍五入到最接近的美元
        let display_price = u64::try_from(current_price.price).unwrap() / 10u64.pow(u32::try_from(-current_price.expo).unwrap());
        let display_confidence = u64::try_from(current_price.conf).unwrap() / 10u64.pow(u32::try_from(-current_price.expo).unwrap());

        // 3-记录结果
        msg!("BTC/USD 价格: ({} +- {})", display_price, display_confidence);
        Ok(())
    }
}

干得漂亮!让我们测试一下。

构建程序

点击左侧菜单中的 🔧 Build 来编译你的程序。你应该在控制台中看到成功消息:

Building...
Build successful. Completed in 3.17s.

如果你按照说明操作,你应该不会遇到任何错误,但如果你遇到了,请尝试按照说明进行调试。如果你需要帮助,请随时在 Discord 上联系我们。我们很乐意帮助你!

部署程序

由于此项目仅用于演示目的,我们可以使用一个“一次性”钱包。Solana Playground 使得创建一个钱包变得容易。你应该在浏览器窗口的左下角看到一个红色的“未连接”点。点击它:

未连接

Solana Playground 将为你生成一个钱包(或者你可以导入自己的钱包)。你可以将其保存以备将来使用,当你准备好时点击继续。一个新的钱包将被初始化并连接到 Solana devnet。你将需要大约 5 个 Devnet SOL 来部署程序(查看我们的 关于空投 Devnet SOL 的指南)。

你应该点击左下角的“⚙️”图标以验证你处于“devnet”上。你也可以从下拉列表中选择“custom”,如果你想连接到你的 QuickNode Devnet 端点。

💡 创建一个 QuickNode 端点

使用你的 QuickNode 端点连接到 Solana 集群

要在 Solana 上构建,你需要一个 API 端点来连接网络。你可以使用公共节点或部署和管理你自己的基础设施;但是,如果你希望获得 8 倍更快的响应时间,你可以将繁重的工作交给我们。

了解为什么超过 50% 的 Solana 项目选择 QuickNode,并在此处注册免费账户 here。我们将使用一个 Solana Devnet 端点。

复制 HTTP 提供者链接:

当你准备好时,点击页面左侧的工具图标 🛠,然后点击“Deploy”。这将需要几分钟,但当它完成时,你应该会看到一个成功消息。

测试程序

现在你的程序已经在链上了,让我们测试一下。点击页面左侧的“🧪”图标。Solana Playground 构建了一个易于使用的界面来测试程序。通过点击切换箭头展开 fetchBtcPrice 指令:

测试程序

选择 My address 作为签名者,并粘贴你在程序中使用的相同 Pyth 价格源(我们使用了 HovQMDrbAgAYPCmHVSrezcSmkMtXSSUsLDFANExrZh2J)。

点击“Test”将交易发送到 devnet 集群。你应该在控制台中看到成功消息:

Testing 'fetchBtcPrice'...
✅  Test 'fetchBtcPrice' passed.

你还会看到一个带有 Solana Explorer 链接的通知弹出窗口。打开它。如果通知在你点击之前消失了,你可以再次测试你的指令,或者在 Solana Explorer 中通过搜索你的程序 ID 来找到交易(例如,explorer.solana.com/address/YOUR_PROGRAM_ID?cluster=devnet )。你的程序 ID 可以在 🛠️ 工具菜单中的“Program ID”和 lib.rs 中的 declare_id 语句中找到。

当你在 Solana Explorer 中打开交易时,你应该看到一个“Program Instructions Logs”部分,其中包括 BTC 的美元价格:

Solana Explorer

干得漂亮!

你可以在 Solana Playground 上访问我们的完整代码。

总结

你刚刚构建了一个程序,该程序从 Pyth 获取 BTC 的价格并将其记录到 Solana 的程序日志中。你可以使用相同的模式创建从 Pyth 获取任何数据的程序,并将其作为构建更复杂程序的起点。

有疑问或想法想分享吗?请在 DiscordTwitter 上给我们留言!

我们 ❤️ 反馈!

让我们知道 如果你有任何反馈或新主题的请求。我们很乐意听取你的意见。

资源

  • 原文链接: quicknode.com/guides/sol...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
QuickNode
QuickNode
江湖只有他的大名,没有他的介绍。