Solana 60 天课程

2025年02月27日更新 89 人订阅
原价: ¥ 66 限时优惠
专栏简介 开始 Solana - 安装与故障排除 Solana 和 Rust 中的算术与基本类型 Solana Anchor 程序 IDL Solana中的Require、Revert和自定义错误 Solana程序是可升级的,并且没有构造函数 Solidity开发者的Rust基础 Rust不寻常的语法 Rust 函数式过程宏 Rust 结构体与属性式和自定义派生宏 Rust 和 Solana 中的可见性与“继承” Solana时钟及其他“区块”变量 Solana 系统变量详解 Solana 日志、“事件”与交易历史 Tx.origin、msg.sender 和 onlyOwner 在 Solana 中:识别调用者 Solana 计算单元与交易费用介绍 在 Solana 和 Anchor 中初始化账户 Solana 计数器教程:在账户中读写数据 使用 Solana web3 js 和 Anchor 读取账户数据 在Solana中创建“映射”和“嵌套映射” Solana中的存储成本、最大存储容量和账户调整 在 Solana 中读取账户余额的 Anchor 方法:address(account).balance 功能修饰符(view、pure、payable)和回退函数在 Solana 中不存在的原因 在 Solana 上实现 SOL 转账及构建支付分配器 使用不同签名者修改账户 PDA(程序派生地址)与 Solana 中的密钥对账户 理解 Solana 中的账户所有权:从PDA中转移SOL Anchor 中的 Init if needed 与重初始化攻击 Solana 中的多重调用:批量交易与交易大小限制 Solana 中的所有者与权限 在Solana中删除和关闭账户与程序 在 Anchor 中:不同类型的账户 在链上读取另一个锚点程序账户数据 在 Anchor 中的跨程序调用(CPI) SPL Token 的运作方式 使用 Anchor 和 Web3.js 转移 SPL Token Solana 教程 - 如何实现 Token 出售 基础银行教程 Metaplex Token 元数据工作原理 使用Metaplex实施代币元数据 使用 LiteSVM 进行时间旅行测试 Solana Token-2022 标准规范 生息代币第一部分 计息代币第二部分 Solana 指令自省 Solana 中的 Ed25519 签名验证 Solana - Switchboard 预言机使用 原生Solana:程序入口与执行 原生 Solana :读取账户数据 原生 Solana :Borsh 序列化 原生 Solana:使用 invoke 和 invoke signed 进行跨程序调用 原生 Solana :创建存储账户 (一) 原生 Solana:创建存储账户 二 原生 Solana: 函数分发 原生 Solana:关键安全检查 Rust 程序到 SBF 编译 sBPF 虚拟机和指令集介绍 跟踪 sBPF 指令执行和计算成本 Solana 程序执行与输入序列化 指令处理器和运行时设置 sBPF 内存布局和寄存器约定 使用 sBPF 汇编读取 Solana 指令输入 Solana 系统调用:sBPF 汇编中的日志记录

Solana - Switchboard 预言机使用

本文详细介绍了如何在 Solana 区块链上使用去中心化预言机网络 Switchboard 来获取链下数据,特别是 SOL/USD 的价格。它涵盖了 Solana 智能合约的编写、Switchboard 价格喂价的初始化与配置、以及客户端脚本如何更新和读取链上价格数据。

链上程序无法直接访问链下数据。它们依赖预言机获取资产价格、事件结果或 API 响应等信息。没有这些预言机,程序的范围将仅限于已存储在链上的状态。

Switchboard 是一个多链去中心化预言机网络,最初构建于 Solana 之上,旨在为智能合约提供可靠的链下数据,例如价格、天气和事件数据。在本教程中,我们将构建一个 Solana 程序,该程序从 Switchboard 读取当前的 SOL/USD 价格。

本教程将完成三件事:

  1. 构建并部署一个从 Switchboard 读取数据的 Solana 程序。
  2. 为我们的程序初始化和配置一个新的 Switchboard 价格源。
  3. 编写一个客户端脚本,与我们在 Devnet 上的程序进行交互以显示价格。

在我们构建 Solana 程序之前,让我们先了解 Switchboard 的工作原理。

Switchboard 的工作原理

Switchboard 使用以下 4 个关键组件来允许 Solana 程序读取链下数据:

  1. Jobs:Switchboard 中的一个 Job 是一系列按顺序执行的任务集合。每个任务执行一个特定操作。例如,一个任务可以从外部 API 端点获取数据,而另一个任务解析响应。
  2. Data Sources:这些是链下数据来源,例如 Binance、Coinbase 和 Pyth。Switchboard 的 Jobs 从这些来源获取数据。
  3. Oracles:这些是执行你的 Jobs 的 Switchboard 网络节点。每个预言机从所有配置的数据源获取数据,对其进行聚合,并将一个 i128 值提交到链上的 Feed。
  4. Feed:Feed 是存储预言机提交结果的链上账户。当你的程序读取 Feed 时,它会接收到由单个预言机结果产生的聚合值,Switchboard SDK 会将其转换为 Decimal 类型以进行十进制运算。

总而言之,以上组件的工作原理如下:

  • 你定义 Jobs 来指定从哪里获取数据(数据源)。
  • 预言机节点执行这些 Jobs,并将结果提交到链上的 Feed 账户中。
  • Feed 将预言机提交的结果聚合为一个 i128 值。
  • 你的程序从 Feed 读取数据。

在本教程中,我们将学习这些过程是如何工作的。让我们开始实施价格源。

先决条件

要跟进本教程,你需要一个正常运行的 Solana 开发环境,并安装了以下工具:

  • Solana CLI 和 Anchor:编写、构建和部署 Solana 程序所需。如果你尚未设置,请阅读本系列的第一篇文章
  • Bun:作为运行客户端脚本的包管理器。在你的终端上运行 curl -fsSL https://bun.sh/install | bash 命令来安装 bun。

我们将使用独立的脚本而不是单元测试来与已部署的程序进行交互。这是因为价格源会在 Devnet 上更新,我们希望展示实时预言机数据如何流入链上逻辑。

通过在终端运行以下命令,将 Solana 集群设置为 Devnet:

solana config set --url https://api.devnet.solana.com

你还需要一些 Devnet 上的 SOL 来支付交易费用。你可以使用 solana airdrop 从水龙头请求测试 SOL:

solana airdrop 2 # 请求 2 个 Devnet SOL。

你每次只能在 Devnet 水龙头请求 2 个 SOL。你可以先请求 2 个,等待片刻后再请求另一个,因为它有速率限制。

Solana Slots

在 Solana 中,时间以 slot 为单位衡量,slot 是网络时间中链条前进的顺序间隔;slot 号随着网络进程而增加,并被用作链上事件排序的简单时钟。这与以太坊的 Block number (block.number) 相似,仅因为它们都代表时间的推移和事件的排序。

请记住 slot 这个概念,Switchboard 用它来衡量数据的陈旧性,我们将在本文后面看到它的用法。

程序设置

我们将首先创建一个 Anchor 项目,该项目定义将从 Switchboard 拉取价格数据的 Solana 程序。

anchor init switchboard-demo
cd switchboard-demo

更新你的 Anchor.toml 提供程序中的 cluster 字段以使用 Devnet,因为我们将在 Devnet 上工作:

[provider]
cluster = "Devnet"
wallet = "~/.config/solana/id.json"

接下来,我们将 switchboard-on-demand crate 添加到 programs/switchboard-demon/src/Cargo.toml 文件的依赖项部分。这是我们将使用的 Switchboard crate。

[dependencies]
anchor-lang = "0.31.1"
switchboard-on-demand = "0.5.3"

programs/switchboard-demo/src/lib.rs 内部,我们编写一个程序,它:

  1. 将 Switchboard 按需 Feed 账户数据读取为原始字节。
  2. 将 Feed 的原始字节解析为 switchboard-on-demand crate 中的 PullFeedAccountData 结构。
  3. 在解析后的 Feed 上调用 get_value 方法,并传入以下参数以验证和提取最新价格:
    • max_stale_slots:设置自 Feed 账户上次更新以来的 Solana slot 最大数量。如果 Feed 比此值更旧,feed.get_value 将失败。
    • min_samples:设置有效价格所需的预言机提交的最小数量。
    • only_positive:当为 true 时,拒绝非正值(≤ 0)。适用于价格或数量必须始终为正的情况。
  4. 使用 msg! 记录 SOL/USD 价格。
use anchor_lang::prelude::*;
use switchboard_on_demand::{
    on_demand::accounts::pull_feed::PullFeedAccountData,
    prelude::rust_decimal,
};
use rust_decimal::Decimal;

declare_id!("iSYBH57FJPsqKnVxz8pyqPvCLEBH63y95Vgk346utR2");

#[program]
pub mod switch_on_demand_price_feed {
    use super::*;

    pub fn read_price(ctx: Context<ReadPrice>) -> Result<()> {
        // 步骤 1:读取 FEED 账户的原始二进制数据(字节)
        let data_slice = ctx.accounts.feed.data.borrow();

        // 步骤 2:解析 FEED 账户数据
        let feed = PullFeedAccountData::parse(data_slice).unwrap();

        // 步骤 3:检索 FEED 值,带有 SLOT、采样约束,
        //         以及确定接收值是正数还是负数的参数
        let price: Decimal = feed.get_value(
            &Clock::get()?,
            /*max_stale_slots=*/ 100,
            /*min_samples=*/ 3,
            /*only_positive=*/ true,
        ).unwrap();

        // 步骤 4:使用 `msg!` 记录 SOL/USD 价格。
        msg!("SOL/USD price: {}", price);
        Ok(())
    }
}

#[derive(Accounts)]
pub struct ReadPrice<'info> {
    /// CHECK: 这是一个 Switchboard 链上 Feed (PullFeedAccount)
    pub feed: AccountInfo<'info>,
}

请注意第 3 步中关于 slot 陈旧性和采样约束的注释。每次 Switchboard 在链上写入新值时,它都会记录 slot 号。当你调用 feed.get_value(&Clock::get()?, max_stale_slots, ...) 时,Switchboard 会将当前 slot 与 Feed 的上次更新 slot 进行比较。

如果差异超过 max_stale_slots(在我们的代码中为 100),get_value 将返回错误。指令将失败,交易将被拒绝。

此外,min_samples 参数确保我们聚合了足够多的预言机响应以提高准确性。在我们的示例中,我们将其设置为 3,这意味着结果必须包含至少 3 个预言机响应的数据。在本文后面讨论链下初始化时,我们将看到如何配置这些预言机。

接下来,通过运行以下命令构建并部署程序:

anchor build && anchor deploy

成功部署将返回程序 Id 和签名,如下所示:

显示成功部署的终端

我们的链上程序现已部署,可以记录 SOL/USD 的价格,但我们还不能使用它,因为虽然程序已准备好从 feed 账户读取价格,但它还没有一个特定的 Feed 可供读取。接下来,我们将通过设置链下 Feed 来解决这个问题。

链下 Feed 设置

回想一下,Feed 是存储预言机提交结果的链上账户。

设置 Feed 涉及两个步骤,我们将在接下来介绍:

  1. Feed 配置和初始化
  2. Feed 更新

1/2 Feed 配置和初始化

Feed 配置定义了你的预言机的数据源和聚合规则(例如链上的 max_stale_slots)。你指定要查询哪些外部 API 或链上预言机、需要多少响应以及源之间可接受的差异。此配置在任何数据上链之前以 JavaScript 对象形式存在。

Feed 初始化将配置转换为实际的链上账户。初始化交易存储 Feed 的元数据,将其绑定到预言机节点池(在 Switchboard 中称为“oracle queue”),并生成程序在请求价格数据时必须引用的公钥。

与 Chainlink 预言机不同,Switchboard Feed 不会自动更新。它使用拉取模型。你的程序必须通过链下脚本触发一个 Job 来获取新数据并更新链上 Feed。

每个 Job 都包含:

  1. Tasks 数组:一个包含按顺序执行操作的任务列表。
  2. Task 类型Tasks 数组中不同的任务类型,例如:
    • httpTask:从 URL 获取 Feed 数据。
    • jsonParseTask:使用路径查询从 httpTask 返回的 JSON 响应中提取特定值。

以下示例展示了一个包含两个任务的 Job:一个从 Coinbase API 获取汇率数据,另一个解析响应。

来自 Coinbase 的数据截图

通常,你会自己实现这些 Jobs,每个 Job 使用一个函数。但是,为了简单起见,在此示例中我们不会手动实现获取逻辑。

Switchboard 团队已经提供了一个公共的 utils.ts 文件,其中包含常见的 Job 获取实现。我们将改用这些。打开 GitHub 上的 utils 文件,复制内容,并将其粘贴到 /scripts/utils.ts 中。

设置多个数据源

上图显示了一个只有一个源的任务。在真实程序中,你需要多个源,这意味着你将有多个 Jobs。多个源可以防止单点故障,并让 Feed 通过方差检查过滤掉异常值。

创建初始化脚本

/scripts 文件夹中创建一个 initializeFeeds.ts 文件,并运行以下命令来安装与 Switchboard 网络交...

剩余50%的内容订阅专栏后可查看

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论