{"content":{"title":"如何在 Solana 上使用 Pyth 获取价格数据","body":"> 几个python库最近引入了影响本指南和Solana Playground使用的突破性更改。我们正在努力更新本指南以反映这些变化。同时， 你可以从python文档中看到一个示例实现。\r\n\r\n## 概述\r\n\r\nPyth 是一个预言机，它以简单易用的方式将实时数据费用带到链上。Pyth 直接与 **[第一方数据发布者](https://pyth.network/publishers)** 合作，将金融资产定价数据带到链上。Pyth 聚合了加密货币、股票、外汇货币和商品的数据。通过与众多数据提供者合作，Pyth 能够提供高度的数据完整性和安全性。在本指南中，我们将学习如何在你的 Solana 程序中从 Pyth 获取价格数据。\r\n\r\n#### 你将做什么\r\n\r\n在本指南中，你将学习如何使用 Pyth SDK 将实时定价数据集成到你的程序中。你将：\r\n\r\n1. 学习 Pyth 协议的基础知识\r\n2. 构建一个简单的 Solana 程序，该程序请求并记录来自 Pyth 的价格源\r\n3. 在 Solana 的 devnet 上测试该程序\r\n\r\n#### 你需要什么\r\n\r\n- 使用 Anchor 进行 Solana 编程的经验（完成我们的 [计数器程序指南](https://learnblockchain.cn/article/10707)）\r\n- [Solana 基础知识](https://www.quicknode.com/guides/solana-development/solana-fundamentals-reference-guide) 的基本了解\r\n- 基本的 Rust 经验\r\n- 一个现代的网络浏览器（Chrome、Firefox、Safari、Edge）以使用 [Solana Playground](https://beta.solpg.io/)\r\n- Devnet SOL（部署到 Devnet 大约需要 5 SOL）。查看我们的 [关于空投 Devnet SOL 的指南](https://www.quicknode.com/guides/solana-development/getting-started/a-complete-guide-to-airdropping-test-sol-on-solana/)。\r\n\r\n#### 本指南中使用的依赖项\r\n\r\n在本指南中，我们将使用 Solana Playground。我们的测试是在 [以下依赖项](https://github.com/solana-playground/solana-playground)（2023 年 5 月 30 日）下进行的：\r\n\r\n| 依赖项 | 版本 |\r\n| --- | --- |\r\n| anchor-lang | 0.27.0 |\r\n| solana-program | 1.14.17 |\r\n| pyth-sdk-solana | 0.7.0 |\r\n\r\n## Pyth 是如何工作的？\r\n\r\nPyth 是一个数据预言机，它将各种资产类别的价格源带到 Solana 区块链上。“Pyth 价格更新在 Pythnet 上创建，并通过 Wormhole 网络（一个跨链消息协议）流式传输到链下。这些更新被签名，因此 Pyth 链上程序可以验证其真实性。” \\*(来源：[Pyth 文档](https://docs.pyth.network/pythnet-price-feeds/on-demand)) [Pythnet](https://docs.pyth.network/design-overview/pythnet) 是一个由 Solana 区块链驱动的私有集群。由于 Pyth 使用 Wormhole，它可以将数据带到多个区块链。\r\n\r\n对于 Solana，资产和定价数据通过 Solana 账户存储并在链上广播。Pyth 中存在三种主要类型的账户：\r\n\r\n1. **产品（元数据）账户**：这些账户存储有关资产的信息（例如，符号、资产类型、描述）。\r\n2. **价格账户**：这些账户存储资产的价格数据、价格的置信区间以及上次更新的时间。\r\n3. **映射账户**：这些账户将产品账户映射到价格账户。\r\n\r\n今天的练习将主要关注价格账户。有关其他账户类型的更多信息，请查看 [Pyth 文档](https://docs.pyth.network/design-overview/account-structure)。\r\n\r\n## 启动一个新的 Anchor 项目\r\n\r\nAnchor 新手？\r\n\r\nAnchor 是一个流行的开发框架，用于在 Solana 上构建程序。\r\n要开始使用，请查看我们的 [Anchor 入门指南](https://www.quicknode.com/guides/solana-development/anchor/how-to-write-your-first-anchor-program-in-solana-part-1/)。\r\n\r\n> 我们将使用 Solana Playground 来加速我们的开发。Solana Playground 是一个基于 Web 的 IDE，允许你编写、部署和测试 Solana 程序。如果你更喜欢使用自己的本地 Anchor 项目，只需确保在你的 `Cargo.toml` 文件中添加 `pyth-sdk-solana = \"0.7.1\"`。\r\n\r\n前往 [beta.solpg.io](https://beta.solpg.io/) 并点击“➕”创建一个新项目：\r\n\r\n- 选择“Anchor (Rust)”\r\n- 将其命名为“Pyth Demo”并点击“创建”\r\n\r\n![创建一个新的 Anchor 项目](https://img.learnblockchain.cn/2025/03/01/create-858a2b4f8ddfeeb83278dc346d760c37.png)\r\n\r\n## 创建你的程序\r\n\r\n让我们创建一个新程序，该程序将从 Pyth 获取价格源并将其记录到 Solana 程序日志中。打开 `src>lib.rs`，它应该已经预填充了一个简单的程序。继续删除默认内容。\r\n\r\n让我们从导入我们程序所需的依赖项开始。我们将需要以下依赖项：\r\n\r\n```rust\r\nuse anchor_lang::prelude::*;\r\nuse pyth_sdk_solana::{load_price_feed_from_account_info};\r\nuse std::str::FromStr;\r\n\r\n// 这是你程序的公钥，它会在你构建项目时自动更新。\r\ndeclare_id!(\"11111111111111111111111111111111\");\r\n```\r\n\r\n除了 Anchor 依赖项外，我们还需要导入 Pyth SDK 的 `load_price_feed_from_account_info` 和 Rust 标准库中的 `FromStr` 特性（这将允许我们将字符串地址转换为公钥）。\r\n\r\nSolana Playground 将使用默认的 `declare_id!` 字段来定义你的程序 ID。这将在你构建程序时自动更新。\r\n\r\n我们还将定义两个常量：\r\n\r\n1. 一个价格源地址（这将是我们想要获取的价格源的地址）。所有可用的源列表可以在 [这里](https://pyth.network/developers/price-feed-ids#solana-devnet) 找到（确保从下拉菜单中选择“Solana Devnet”）。对于此演示，我们将使用 Solana devnet 上的 BTC/USD 价格源，`HovQMDrbAgAYPCmHVSrezcSmkMtXSSUsLDFANExrZh2J`。\r\n2. 一个“陈旧阈值”，用于确定价格源是否陈旧（即，如果价格源在过去 60 秒内未更新，我们将认为它陈旧）\r\n\r\n在你的导入下方添加以下声明：\r\n\r\n```rust\r\nconst BTC_USDC_FEED: &str = \"HovQMDrbAgAYPCmHVSrezcSmkMtXSSUsLDFANExrZh2J\";\r\nconst STALENESS_THRESHOLD: u64 = 60; // 陈旧阈值，单位为秒\r\n```\r\n\r\n你可以随意尝试不同的价格源和陈旧阈值，但在我们的示例中，我们将寻找在过去 60 秒内创建的 BTC/USD 价格源。\r\n\r\n## 创建一个价格源结构体\r\n\r\n让我们为我们的价格源指令定义一个结构体。该结构体将定义我们必须传递给我们的程序以获取价格源的账户。在你的常量下方添加：\r\n\r\n```rust\r\n#[derive(Accounts)]\r\npub struct FetchBitcoinPrice<'info> {\r\n    #[account(mut)]\r\n    pub signer: Signer<'info>,\r\n    #[account(address = Pubkey::from_str(BTC_USDC_FEED).unwrap() @ FeedError::InvalidPriceFeed)]\r\n    pub price_feed: AccountInfo<'info>,\r\n}\r\n\r\n#[error_code]\r\npub enum FeedError {\r\n    #[msg(\"Invalid Price Feed\")]\r\n    InvalidPriceFeed,\r\n}\r\n```\r\n\r\n我们定义了一个名为 `FetchBitcoinPrice` 的结构体，它将有两个账户：\r\n\r\n1. `signer`：这将是签署交易并支付交易费用的账户。\r\n2. `price_feed`：这是我们想要获取的价格源账户。我们使用 Anchor 的 `address` 约束（了解更多关于 Anchor 约束的信息，请查看我们的指南 [这里](https://www.quicknode.com/guides/solana-development/anchor/how-to-use-constraints-in-anchor/)）。我们使用 `Pubkey::from_str` 函数将我们的 `BTC_USDC_FEED` 字符串转换为公钥。我们还定义了一个错误代码 `InvalidPriceFeed`，如果价格源账户无效，将抛出该错误。\r\n\r\n## 创建一个价格源指令\r\n\r\n让我们定义我们的程序并创建一个函数来获取价格源。在你的结构体下方添加：\r\n\r\n```rust\r\n#[program]\r\nmod hello_pyth {\r\n    use super::*;\r\n    pub fn fetch_btc_price(ctx: Context<FetchBitcoinPrice>) -> Result<()> {\r\n        // 1-获取最新价格\r\n\r\n        // 2-格式化显示值，四舍五入到最接近的美元\r\n\r\n        // 3-记录结果\r\n\r\n        Ok(())\r\n    }\r\n}\r\n```\r\n\r\n我们定义了一个名为 `hello_pyth` 的程序和一个名为 `fetch_btc_price` 的函数，该函数将接受类型为 `FetchBitcoinPrice` 的 `Context` 并返回一个 `Result`（要么是 `Ok`，要么是 `Err`）。\r\n\r\n### 获取最新价格\r\n\r\n为了获取最新价格，我们将价格源地址 `&ctx.accounts.price_feed` 传递给 Pyth 的 `load_price_feed_from_account_info()` 函数：\r\n\r\n```rust\r\n        // 1-获取最新价格\r\n        let price_account_info = &ctx.accounts.price_feed;\r\n        let price_feed = load_price_feed_from_account_info( &price_account_info ).unwrap();\r\n        let current_timestamp = Clock::get()?.unix_timestamp;\r\n        let current_price = price_feed.get_price_no_older_than(current_timestamp, STALENESS_THRESHOLD).unwrap();\r\n```\r\n\r\n我们还使用 `Clock` 结构体获取当前时间戳，并定义一个 `current_price` 变量，只要它不比我们之前指定的陈旧阈值更旧，就会获取价格。\r\n\r\n### 格式化显示值\r\n\r\nPyth 定价数据存储为 64 位有符号整数和 32 位有符号指数。\r\n\r\n**详细信息**\r\n\r\nPyth 的 **Price** 结构体\r\n以下是 Pyth 的 **Price** 结构体的定义（[来源](https://github.com/pyth-network/pyth-sdk-rs/blob/ecbe342568b36c58cdf4b34187c2c3777f1689af/pyth-sdk/src/price.rs#L56)）：\r\n\r\n```rust\r\npub struct Price {\r\n    /// 价格。\r\n    #[serde(with = \"utils::as_string\")] // 确保转换为 json 时的准确性。\r\n    #[schemars(with = \"String\")]\r\n    pub price:        i64,\r\n    /// 置信区间。\r\n    #[serde(with = \"utils::as_string\")]\r\n    #[schemars(with = \"String\")]\r\n    pub conf:         u64,\r\n    /// 指数。\r\n    pub expo:         i32,\r\n    /// 发布时间。\r\n    pub publish_time: UnixTimestamp,\r\n}\r\n```\r\n\r\n为了以人类可读的格式记录价格（例如，27400 而不是 27400000000000），我们需要将 `price` 和置信区间（`conf`）转换为 `u64`，然后将其除以小数位数（我们也必须将其转换为 `u64`）。由于 Pyth 将其存储为负数，我们必须在除法之前将其转换为正数。将以下内容添加到你的 `fetch_btc_price` 函数中：\r\n\r\n```rust\r\n        // 2-格式化显示值，四舍五入到最接近的美元\r\n        let display_price = u64::try_from(current_price.price).unwrap() / 10u64.pow(u32::try_from(-current_price.expo).unwrap());\r\n        let display_confidence = u64::try_from(current_price.conf).unwrap() / 10u64.pow(u32::try_from(-current_price.expo).unwrap());\r\n```\r\n\r\n最后，我们将使用 `msg!` 将结果记录到 Solana 的程序日志中。你的最终指令应该如下所示：\r\n\r\n```rust\r\n#[program]\r\nmod hello_pyth {\r\n    use super::*;\r\n    pub fn fetch_btc_price(ctx: Context<FetchBitcoinPrice>) -> Result<()> {\r\n        // 1-获取最新价格\r\n        let price_account_info = &ctx.accounts.price_feed;\r\n        let price_feed = load_price_feed_from_account_info( &price_account_info ).unwrap();\r\n        let current_timestamp = Clock::get()?.unix_timestamp;\r\n        let current_price = price_feed.get_price_no_older_than(current_timestamp, STALENESS_THRESHOLD).unwrap();\r\n\r\n        // 2-格式化显示值，四舍五入到最接近的美元\r\n        let display_price = u64::try_from(current_price.price).unwrap() / 10u64.pow(u32::try_from(-current_price.expo).unwrap());\r\n        let display_confidence = u64::try_from(current_price.conf).unwrap() / 10u64.pow(u32::try_from(-current_price.expo).unwrap());\r\n\r\n        // 3-记录结果\r\n        msg!(\"BTC/USD 价格: ({} +- {})\", display_price, display_confidence);\r\n        Ok(())\r\n    }\r\n}\r\n```\r\n\r\n干得漂亮！让我们测试一下。\r\n\r\n## 构建程序\r\n\r\n点击左侧菜单中的 **🔧 Build** 来编译你的程序。你应该在控制台中看到成功消息：\r\n\r\n```\r\nBuilding...\r\nBuild successful. Completed in 3.17s.\r\n```\r\n\r\n如果你按照说明操作，你应该不会遇到任何错误，但如果你遇到了，请尝试按照说明进行调试。如果你需要帮助，请随时在 [Discord](https://discord.gg/quicknode) 上联系我们。我们很乐意帮助你！\r\n\r\n## 部署程序\r\n\r\n由于此项目仅用于演示目的，我们可以使用一个“一次性”钱包。Solana Playground 使得创建一个钱包变得容易。你应该在浏览器窗口的左下角看到一个红色的“未连接”点。点击它：\r\n\r\n![未连接](https://img.learnblockchain.cn/2025/03/01/wallet-940417b010f408130a2cdb29fe4e1ecb.png)\r\n\r\nSolana Playground 将为你生成一个钱包（或者你可以导入自己的钱包）。你可以将其保存以备将来使用，当你准备好时点击继续。一个新的钱包将被初始化并连接到 Solana devnet。你将需要大约 5 个 Devnet SOL 来部署程序（查看我们的 [关于空投 Devnet SOL 的指南](https://www.quicknode.com/guides/solana-development/getting-started/a-complete-guide-to-airdropping-test-sol-on-solana/)）。\r\n\r\n你应该点击左下角的“⚙️”图标以验证你处于“devnet”上。你也可以从下拉列表中选择“custom”，如果你想连接到你的 QuickNode Devnet 端点。\r\n\r\n💡 创建一个 QuickNode 端点\r\n\r\n#### 使用你的 QuickNode 端点连接到 Solana 集群\r\n\r\n要在 Solana 上构建，你需要一个 API 端点来连接网络。你可以使用公共节点或部署和管理你自己的基础设施；但是，如果你希望获得 8 倍更快的响应时间，你可以将繁重的工作交给我们。\r\n\r\n了解为什么超过 50% 的 Solana 项目选择 QuickNode，并在此处注册免费账户 [here](https://www.quicknode.com/signup?utm_source=internal&utm_campaign=guides&utm_content=pyth-price-feeds)。我们将使用一个 Solana Devnet 端点。\r\n\r\n复制 HTTP 提供者链接：![](https://img.learnblockchain.cn/2025/03/01/generic-a54ddc5df6f933228eabe0e03cf9d8fa.png)\r\n\r\n当你准备好时，点击页面左侧的工具图标 🛠，然后点击“Deploy”。这将需要几分钟，但当它完成时，你应该会看到一个成功消息。\r\n\r\n## 测试程序\r\n\r\n现在你的程序已经在链上了，让我们测试一下。点击页面左侧的“🧪”图标。Solana Playground 构建了一个易于使用的界面来测试程序。通过点击切换箭头展开 `fetchBtcPrice` 指令：\r\n\r\n![测试程序](https://img.learnblockchain.cn/2025/03/01/test-46c94cc91d4db52fc03b79dc6a549573.png)\r\n\r\n选择 `My address` 作为签名者，并粘贴你在程序中使用的相同 Pyth 价格源（我们使用了 `HovQMDrbAgAYPCmHVSrezcSmkMtXSSUsLDFANExrZh2J`）。\r\n\r\n点击“Test”将交易发送到 devnet 集群。你应该在控制台中看到成功消息：\r\n\r\n```\r\nTesting 'fetchBtcPrice'...\r\n✅  Test 'fetchBtcPrice' passed.\r\n```\r\n\r\n你还会看到一个带有 Solana Explorer 链接的通知弹出窗口。打开它。如果通知在你点击之前消失了，你可以再次测试你的指令，或者在 Solana Explorer 中通过搜索你的程序 ID 来找到交易（例如，[explorer.solana.com/address/YOUR\\_PROGRAM\\_ID?cluster=devnet](https://explorer.solana.com/address/YOUR_PROGRAM_ID?cluster=devnet) ）。你的程序 ID 可以在 🛠️ 工具菜单中的“Program ID”和 `lib.rs` 中的 `declare_id` 语句中找到。\r\n\r\n当你在 Solana Explorer 中打开交易时，你应该看到一个“Program Instructions Logs”部分，其中包括 BTC 的美元价格：\r\n\r\n![Solana Explorer](https://img.learnblockchain.cn/2025/03/01/explorer-b2b2dfb9663707edbe1c134ab4292edd.png)\r\n\r\n干得漂亮！\r\n\r\n你可以在 [Solana Playground](https://beta.solpg.io/64767462d6ebe745da2043ad) 上访问我们的完整代码。\r\n\r\n## 总结\r\n\r\n你刚刚构建了一个程序，该程序从 Pyth 获取 BTC 的价格并将其记录到 Solana 的程序日志中。你可以使用相同的模式创建从 Pyth 获取任何数据的程序，并将其作为构建更复杂程序的起点。\r\n\r\n有疑问或想法想分享吗？请在 [Discord](https://discord.gg/quicknode) 或 [Twitter](https://twitter.com/QuickNode) 上给我们留言！\r\n\r\n#### 我们 ❤️ 反馈！\r\n\r\n[让我们知道](https://airtable.com/shrKKKP7O1Uw3ZcUB?prefill_Guide+Name=How%20to%20Use%20Pyth%20for%20Price%20Feeds%20on%20Solana) 如果你有任何反馈或新主题的请求。我们很乐意听取你的意见。\r\n\r\n## 资源\r\n\r\n- [Pyth](https://pyth.network/)\r\n- [Pyth 文档](https://docs.pyth.network/)\r\n- [Pyth 源（Solana Devnet）](https://pyth.network/developers/price-feed-ids#solana-devnet)\r\n- Pyth Rust SDK（ [GitHub](https://github.com/pyth-network/pyth-sdk-rs) [Crate](https://crates.io/crates/pyth-sdk-solana)）\r\n\r\n>- 原文链接： [quicknode.com/guides/sol...](https://www.quicknode.com/guides/solana-development/3rd-party-integrations/pyth-price-feeds)\r\n>- 登链社区 AI 助手，为大家转译优秀英文文章，如有翻译不通的地方，还请包涵～"},"author":{"user":"https://learnblockchain.cn/people/25306","address":null},"history":null,"timestamp":1740795949,"version":1}