閱讀時間 9 分鐘
概覽
Pyth 是一款 Oracle,能以簡單且易於使用的方式,將即時數據費用帶入鏈上。Pyth 直接與第一方數據發布商合作,將金融資產定價數據帶入鏈上。Pyth 彙整加密貨幣、股票、外匯及大宗商品的數據。透過與眾多數據供應商合作,Pyth 能提供高數據完整性與安全性。 在本指南中,我們將學習如何在您的 Solana 程式中從 Pyth 擷取價格資料。
您將負責的工作內容
在本指南中,您將學習如何使用 Pyth SDK 將即時報價資料整合至您的程式中。您將:
- 了解 Pyth 協議的基礎知識
- 建立一個簡單的 Solana 程式,用來向 Pyth 請求價格資料並將其記錄下來
- 在 Solana 的開發網(devnet)上測試程式
您需要準備的物品
- 使用 Anchor 進行 Solana 程式設計的實作經驗(完成我們的計數器程式指南)
- Solana基礎知識
- 具備 Rust 基礎經驗
- 一款現代網路瀏覽器(Chrome、Firefox、Safari、Edge),用於使用Solana Playground
- Devnet SOL(將程式部署至 Devnet 約需 5 SOL)。請參閱我們關於 Devnet SOL 空投的指南。
本指南中使用的依賴項
在本指南中,我們將使用 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 支援拉取式價格更新,使用者可於鏈外提出請求,並將結果提交至鏈上進行驗證。如欲進一步了解 Pyth 當前的價格饋送模型,請參閱Pyth 價格饋送概覽及 拉取式預言機文件。
對於 Solana 而言,資產與定價資料是透過 Solana 帳戶在鏈上儲存並廣播的。Pyth 中存在三種主要類型的帳戶:
- 產品(元數據)帳戶:這些帳戶用於儲存資產相關資訊(例如:代號、資產類型、描述)。
- 價格帳戶:這些帳戶儲存資產的價格資料、價格的信賴區間,以及最後一次更新的時間。
- 帳戶對應:這些帳戶將產品帳戶對應至價格帳戶。
今天的練習將主要聚焦於「價格帳戶」。有關當前 Pyth Core 整合的詳細資訊,請參閱Pyth Core 文件。
啟動新的錨點專案
Anchor 是一個用於在 Solana 上開發程式的熱門開發框架。 若要開始使用,請參閱我們的《Anchor 入門指南》。
我們將使用 Solana Playground 來加速開發進度。Solana Playground 是一款基於網頁的整合開發環境(IDE),可讓您編寫、部署及測試 Solana 程式。如果您偏好使用自己的本機 Anchor 專案,請務必加入
pyth-sdk-solana = "0.7.1"致您的Cargo.toml檔案。
請前往beta.solpg.io,並點擊「➕」建立新專案:
- 選擇「錨(鏽)」
- 將其命名為「Pyth Demo」,然後點擊「建立」

建立您的課程
讓我們建立一個新程式,用來從 Pyth 擷取價格資料,並將其記錄到 Solana 程式日誌中。開啟 src>lib.rs,其中應已預先填入一個簡單的程式。請直接刪除預設內容。
首先,讓我們導入這個程式所需的依賴項。我們需要以下依賴項:
use anchor_lang::prelude::*;
use pyth_sdk_solana::{load_price_feed_from_account_info};
use std::str::FromStr;
// This is your program's public key, and it will update
// automatically when you build the project.
declare_id!("11111111111111111111111111111111");
除了 Anchor 的依賴項之外,我們還需要導入 Pyth SDK 的 從帳戶資訊載入價格資料源 以及 FromStr Rust 標準函式庫中的 trait(這將使我們能夠將字串位址轉換為公鑰)。
Solana Playground 將使用預設值 declare_id! 請在此欄位中定義您的程式 ID。當您編譯程式時,此值將會自動更新。
我們也將定義兩個常數:
- 一個價格資料饋送地址(這將是我們要擷取的價格資料饋送地址)。所有可用的資料饋送清單可於 這裡 (請務必從下拉式選單中選擇「Solana Devnet」)。在此示範中,我們將使用 Solana 開發網上的 BTC/USD 價格資料源,
HovQMDrbAgAYPCmHVSrezcSmkMtXSSUsLDFANExrZh2J. - 一個「過期閾值」,用以判斷價格資料是否已過期(即,若價格資料在過去 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,
}
我們正在定義一個名為 取得比特幣價格 該帳戶將包含兩個帳戶:
簽署人: 這是將用來簽署交易並支付交易手續費的帳戶。價格資料饋送: 這是我們要擷取的價格資料帳戶。我們使用 Anchor地址約束(欲進一步了解 Anchor 約束,請參閱我們的指南, 這裡). 我們使用Pubkey::from_str用於轉換我們的BTC_USDC_FEED字串轉換為公鑰。我們還定義了一個錯誤代碼,InvalidPriceFeed, 若價格饋送帳戶無效,則會拋出此例外。
建立價格資料饋送指示
讓我們定義程式,並建立一個函式來擷取價格資料。在您的結構體下方,加入以下內容:
#[program]
mod hello_pyth {
use super::*;
pub fn fetch_btc_price(ctx: Context<FetchBitcoinPrice>) -> Result<()> {
// 1-Fetch latest price
// 2-Format display values rounded to the nearest dollar
// 3-Log result
Ok(())
}
}
我們正在定義一個名為 hello_pyth 以及一個名為 fetch_btc_price 這將容納一個 上下文 類型為 取得比特幣價格 並傳回一個 結果 (要麼是 好的 或一個 呃).
取得最新價格
為了取得最新價格,我們傳入我們的價格資料饋送地址, &ctx.accounts.price_feed 進入 Pyth load_price_feed_from_account_info() 函式:
// 1-取得最新價格
let 價格帳戶資訊 = &ctx.帳戶.價格資料饋送;
let 價格資料饋送 = 從帳戶資訊載入價格資料( &價格帳戶資訊 ).unwrap();
let 當前時間戳記 = Clock::get()?.unix_timestamp;
let 當前價格 = 價格資料流。取得不早於(當前時間戳記, STALENESS_THRESHOLD).unwrap();
我們也使用 時鐘 使用 struct 取得當前時間戳記,並定義一個 當前價格 一個變數,只要價格未超過我們先前指定的過期閾值,便會擷取該價格。
格式化顯示值
Pyth 的定價資料以 64 位元有符號整數的形式儲存,其中包含一個 32 位元有符號的指數。
以下是 Pyth's 的定義: 價格 struct (來源):
pub struct Price {
/// Price.
#[serde(with = "utils::as_string")] // To ensure accuracy on conversion to json.
#[schemars(with = "String")]
pub price: i64,
/// Confidence interval.
#[serde(with = "utils::as_string")]
#[schemars(with = "String")]
pub conf: u64,
/// Exponent.
pub expo: i32,
/// Publish time.
pub publish_time: UnixTimestamp,
}
為了以人可讀的格式記錄價格(例如 27400 而不是 27400000000000),我們需要將該 價格 以及信賴區間(conf) 轉為一個 u64 然後將其除以小數位數(我們也必須將其轉換為一個 u64). 由於 Python 將此值儲存為負數,因此在進行除法運算前,必須先將其轉換為正數。請在您的程式中加入以下內容: fetch_btc_price 函式:
// 2-將顯示值四捨五入至最接近的美元
let display_price = u64::try_from(當前價格.價格).unwrap() / 10u64.pow(u32::try_from(-當前價格.expo).unwrap());
let display_confidence = u64::try_from(當前價格.conf).unwrap() / 10u64.pow(u32::try_from(-當前價格.expo).unwrap());
最後,我們將使用以下方式將結果記錄到 Solana 的程式日誌中: msg!. 您的最終指令應如下所示:
#[program]
mod hello_pyth {
use super::*;
pub fn fetch_btc_price(ctx: Context<FetchBitcoinPrice>) -> Result<()> {
// 1-Fetch latest price
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-Format display values rounded to nearest dollar
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-Log result
msg!("BTC/USD price: ({} +- {})", display_price, display_confidence);
Ok(())
}
}
做得好!來試試看吧。
編譯程式
點擊左側選單中的🔧 建置,以編譯您的程式。您應該會在控制台看到一則成功訊息:
建築...
建置成功。已完成 於 3.17 秒內
如果您已按照指示操作,應該不會出現任何錯誤;但若仍遇到錯誤,請嘗試依照指示進行除錯。若需要協助,歡迎隨時透過Discord 聯繫我們。我們很樂意提供協助!
部署程式
由於此專案僅供示範之用,我們可以使用一個「一次性」錢包。透過 Solana Playground 即可輕鬆建立一個。您應該會在瀏覽器視窗的左下角看到一個標有「未連線」的紅色圓點。請點擊它:

Solana Playground 將為您生成一個錢包(您也可以導入自己的錢包)。請隨意將其儲存以備日後使用,準備就緒後請點擊「繼續」。系統將建立一個新錢包並連線至 Solana 開發網。您需要約 5 Devnet SOL 才能部署該程式(請參閱我們的《Devnet SOL 空投指南》)。
您應點擊左下角的「⚙️」圖示,以確認您目前處於「devnet」環境。若您想連線至您的 Quicknode Devnet 端點,亦可從下拉式選單中選擇「自訂」。
💡 建立 Quicknode 端點
使用您的 Quicknode 端點連線至 Solana 叢集
若要在 Solana 上進行開發,您需要一個 API 端點來連線至該網路。您可以使用公開節點,或自行部署並管理基礎架構;不過,若您希望將回應時間縮短 8 倍,不妨將繁重的工作交給我們處理。
了解為何在 索拉納 選擇 Quicknode,立即開始免費試用 這裡. 我們將使用一個 索拉納 Devnet 終點。
複製該 HTTP 服務提供者連結:
準備就緒後,請點擊頁面左側的「工具」圖示 🛠,然後點擊「部署」。此過程將耗時數分鐘,完成後您應會看到一則成功訊息。
測試程式
既然您的程式已經部署到鏈上,現在就來測試一下吧。點擊頁面左側的「🧪」圖示。Solana Playground 已建置了一個易於使用的介面,供您測試程式。展開 fetchBtcPrice 點擊切換箭頭以查看說明:

選擇 我的地址 作為簽署者,並貼上您在程式中使用的同一組 Pyth 價格資料源(我們使用的是 HovQMDrbAgAYPCmHVSrezcSmkMtXSSUsLDFANExrZh2J).
點擊「測試」以將交易傳送至 devnet 叢集。您應會在控制台看到一則成功訊息:
測試 'fetchBtcPrice'...
✅ 測試 'fetchBtcPrice' 通過。
您還會看到一個附有 Solana Explorer 連結的通知彈出視窗。請點開它。如果通知在您點擊之前就消失了,您可以重新測試您的指令,或者在 Solana Explorer 中透過搜尋您的程式 ID 來查找該交易(例如: explorer.solana.com/address/YOUR_PROGRAM_ID?cluster=devnet). 您的程式 ID 可在 🛠️「工具」選單中的「程式 ID」選項下找到,亦可在您的 declare_id 陳述於 lib.rs.
當您在 Solana 區塊鏈瀏覽器中開啟該筆交易時,應會看到一個「程式指令日誌」區段,其中包含以美元計價的 BTC 價格:

做得真好!
您可以在Solana Playground 上查看我們的完整程式碼。
總結
您剛才建立了一個程式,用來從 Pyth 擷取 BTC 的價格,並將其記錄到 Solana 的程式日誌中。您可以運用相同的模式,建立從 Pyth 擷取任何資料的程式,並以此作為開發更複雜程式的起點。
若有任何問題或想分享的想法,歡迎透過Discord或Twitter 聯絡我們!
我們 ❤️ 您的回饋!
如果您有任何意見回饋或對新主題的建議,請告訴我們。我們非常樂意聽取您的意見。
資源
- Pyth
- Python 文件
- Pyth Feeds(Solana 開發網)
- Pyth Rust SDK(GitHub Docs.rs)
- Solana 體驗區
