Solana 60 天课程

2025年02月27日更新 89 人订阅
原价: ¥ 53 限时优惠
专栏简介 开始 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 网络交互所需的依赖项。我们还将使用 Anchor 安装中包含的 @solana/web3.js

yarn add @switchboard-xyz/on-demand @switchboard-xyz/common

我们的脚本执行 5 个步骤:

  1. 定义四个 Job 以读取 SOL/USD 价格:一个从 Pyth 的链上预言机读取,三个从 REST API 读取。
  2. 使用聚合规则(maxStalenessminimumSamples 等)配置 Feed。
  3. 生成一个新的 Feed 账户密钥对,以创建唯一的链上地址来存储 Feed 的价格数据。
  4. 使用 Crossbar(Switchboard 用于上传到 IPFS 的服务)在链下存储 Job 定义。这避免了链上存储成本。Crossbar 返回 IPFS 内容哈希,预言机将使用该哈希来获取存储的 Job 定义。我们将在 feed 更新 部分看到预言机如何使用此哈希。
  5. 构建并发送 Feed 初始化交易,以在链上创建 Feed。
import { PublicKey } from "@solana/web3.js";
import * as sb from "@switchboard-xyz/on-demand";
import { AnchorUtils, PullFeed } from "@switchboard-xyz/on-demand";
import { CrossbarClient, decodeString } from "@switchboard-xyz/common";
import {
  buildCoinbaseJob,
  buildBinanceJob,
  buildPythJob,
  buildBybitJob,
  TX_CONFIG,
} from "./utils";

const crossbarClient = new CrossbarClient(
  "https://crossbar.switchboard.xyz",
  /* verbose= */ true
);

// 1. 定义四个 FEED JOB 以读取 SOL/USD 价格
const FEED_JOBS = [
  // Pyth 预言机传递 SOL/USD 价格 Feed 公钥
  buildPythJob("H6ARHf6YXhGYeQfUzQNGk6rDNnLBQKrenN712K4AQJEG"),

  // Web API 端点
  buildCoinbaseJob("SOL-USD"),
  buildBinanceJob("SOLUSDT"),
  buildBybitJob("SOLUSDT"),
];

(async function main() {
   // 从 Solana CLI 配置 (~/.config/solana/id.json) 加载钱包和 RPC 连接
   // 然后获取网络的默认预言机队列
  const { keypair, connection, program } = await AnchorUtils.loadEnv();
  const queueAccount = await sb.getDefaultQueue(connection.rpcEndpoint);
  const queue = queueAccount.pubkey;

  // 2. FEED 配置
  const conf: any = {
    name: "SOL-USD Price Feed", // feed 名称 (最大 32 字节)
    queue: new PublicKey(queue), // 绑定的预言机队列
    maxVariance: 1.0, // 允许提交和 Jobs 之间 1% 的方差
    minResponses: 3, // 允许的 Jobs 响应的最小数量
    numSignatures: 3, // 每次更新获取的签名数量
    minSampleSize: 3, // 采样结果的最小响应数量
    maxStaleness: 100, // 采样响应的最大陈旧 slots 数量
  };

  // 3. 生成 FEED 密钥对
  console.log("正在初始化新的数据 Feed");
  const [pullFeed, feedKp] = PullFeed.generate(program!);

  // 4. 将 JOB 定义存储在 IPFS 上
  conf.feedHash = decodeString(
    (await crossbarClient.store(queue.toString(), FEED_JOBS)).feedHash
  );

   // 5. 构建并发送初始化交易
   //    包含 FEED 配置
  const initTx = await sb.asV0Tx({
    connection,
    ixs: [await pullFeed.initIx(conf)],
    payer: keypair.publicKey,
    signers: [keypair, feedKp],
    computeUnitPrice: 75_000,
    computeUnitLimitMultiple: 1.3,
  });

  console.log("正在发送初始化交易");
  const sig = await connection.sendTransaction(initTx, TX_CONFIG);
  await connection.confirmTransaction(sig, "confirmed");
  console.log(`Feed ${feedKp.publicKey} 已初始化: ${sig}`);

})();

让我们解释一下上述代码中 Switchboard 特定的关键部分:

Oracle queues

脚本使用 sb.getDefaultQueue() 检索你网络的默认预言机队列。队列是 Switchboard 用于协调预言机节点的机制。当你将 Feed 绑定到队列时,你是在告诉 Switchboard 网络哪个预言机池可以满足你的 Feed 的更新请求。每个队列都有自己注册的预言机集、奖励参数和操作规则。在 Devnet 上,这会返回所有开发者共享的公共测试队列。

Feed 配置

在上述代码中,Feed 配置是用于将 Jobs 结果聚合成一个单一、可信值的规则集。这种信任来自我们配置中的多层验证:

  • minResponses:我们要求每次更新至少有 3 个成功的响应,并且在计算结果时至少采样 3 个提交。这与我们链上调用 get_value 中的 min_samples = 3 相符。
  • maxVariance:我们将其设置为 1.0,这意味着如果一个源报告的价格与其他源相差超过 1%,则可以将其视为不一致而丢弃。
  • maxStaleness:这确保了数据的时效性,对应于我们链上程序中的 max_stale_slots 值。

使用我们之前安装的 JavaScript 运行时 bun 运行脚本:

bun run scripts/initializeFeeds.ts

结果将包含程序中 Feed 账户的公钥,如下所示:

公钥截图

这个 Feed 现在在 Devnet 上是实时的,但它仍然是空的。在下一节中,我们将使用这个公钥开始用数据填充它。

2/2 Feed 更新

现在我们的 Feed 账户已初始化,我们需要一个过程来持续用新数据填充它。Feed 更新是确保 Feed 账户始终包含最新数据的过程。

我们将创建一个脚本 runfeeds.ts,它运行一个无限循环。在每次迭代中,它使用 Feed 公钥从 Switchboard 网络请求我们 Jobs 的最新价格。来自所有预言机的数据被聚合,然后脚本发送一个交易,将经过验证的结果存储到我们的链上 Feed 账户中。

import * as sb from "@switchboard-xyz/on-demand";
import { CrossbarClient } from "@switchboard-xyz/common";
import yargs from "yargs";
import { TX_CONFIG, sleep } from "./utils";
import { PublicKey } from "@solana/web3.js";

/// 解析命令行参数 - 需要 Feed 账户公钥 '--feed'
const argv = yargs(process.argv).options({ feed: { required: true } })
  .argv as any;

  console.log(`正在使用 Feed: ${argv.feed}`);

// 主函数被包裹在一个立即执行的异步函数表达式 (IIFE) 中。
(async function main() {
  // 从本地环境加载钱包密钥对、RPC 连接和程序
  const { keypair, connection, program } = await sb.AnchorUtils.loadEnv();

  // 为指定网络(devnet 或 mainnet)加载默认的 Switchboard 队列账户。
  const queue = await sb.Queue.loadDefault(program!);

  // 初始化 'PullFeed' 对象以与
  // 终端中指定的链上数据 Feed 公钥进行交互。
  const feedAccount = new sb.PullFeed(program!, argv.feed!);

  // 连接到 Switchboard 的链下基础设施
  const crossbar = new CrossbarClient("https://crossbar.switchboard.xyz");
  const gateway = await queue.fetchGatewayFromCrossbar(crossbar as any);

  // 缓存地址查找表以减少交易大小
  await feedAccount.preHeatLuts();
  let runCount = 0;

  console.log("正在启动 Feed 更新程序。");

  // 启动一个无限循环以持续更新数据 Feed。
  while (true) {
    try {
      console.log(`\n--- 更新 #${++runCount} ---`);

      // 请求新的预言机数据。网关从 Feed 账户读取 feedHash,
      // 从 IPFS 检索 Job 定义,将其分发给预言机,并返回
      // 它们的聚合响应
      const [pullIx, responses, _ok, luts] = await feedAccount.fetchUpdateIx({
        gateway: gateway.gatewayUrl,
        crossbarClient: crossbar as any,
      });

      // 检查预言机错误
      let hasError = false;
      for (const response of responses) {
        const shortErr = response.shortError();
        if (shortErr) {
          console.log(`预言机响应错误: ${shortErr}`);
          hasError = true;
        }
      }

      // 如果发生错误或没有返回指令,则跳过更新
      if (hasError || !pullIx || pullIx.length === 0) {
        console.log("由于预言机错误或未生成指令,跳过更新。");
        await sleep(5000); // 如果有错误,等待更长时间再重试。
        continue;
      }

      // 将更新指令组装成一个版本化交易 (v0)。
      const tx = await sb.asV0Tx({
        connection,
        ixs: [...pullIx!],
        signers: [keypair], // 支付者的密钥对必须签署交易。
        computeUnitPrice: 200_000, // 设置优先级费用以更快地处理交易。
        computeUnitLimitMultiple: 1.3, // 为计算单元限制添加缓冲区以防止失败。
        lookupTables: luts, // 包含预热的 LUT。
      });

      // 将交易发送到链上以更新 Feed
      const sig = await connection.sendTransaction(tx, TX_CONFIG);
      console.log(`✅ 交易已发送: https://explorer.solana.com/tx/${sig}?cluster=devnet`);

      console.log("等待确认...");
      await connection.confirmTransaction(sig, "confirmed");
      console.log("✅ 交易已确认!");

    } catch (e) {
        console.error("❌ 主循环中发生错误:", e);
    } finally {
        // 在开始下一个更新周期之前暂停执行几秒钟。
        await sleep(5000);
    }
  }
})();

IPFS 内容哈希的用法

当调用 feedAccount.fetchUpdateIx() 时:

  1. 它从 Solana 读取 Feed 账户数据,其中包含 feedHash(初始化期间存储的 IPFS 内容哈希)。
  2. 它向网关发送一个包含此 feedHash 的请求。
  3. 网关(和预言机)使用 feedHash 从 IPFS 检索 Job 定义。
  4. 预言机执行这些 Jobs(从 Pyth、Coinbase、Binance、Bybit 获取数据)。
  5. 预言机将其结果返回给 Switchboard 网关,网关对其进行聚合。
  6. 如果交易成功,fetchUpdateIx() 返回将聚合的预言机数据写入 Feed 账户的指令 (pullIx)。

我们将使用 Feed 公钥作为命令行参数来运行脚本,如下所示:

bun scripts/runfeeds.ts --feed GgGVgSLWAyL9Xf4fGaAQQCkmWetBjX7PCNz8kTK97DKB

结果将如下所示:

从 Switchboard 获取数据的脚本输出

我们将让这个脚本持续运行,以确保我们始终接收到最新数据。

读取 SOL/USD 的价格

现在程序已部署,Feed 也在持续更新,让我们编写一个脚本来读取 SOL/USD 的价格。

以下脚本执行以下操作:

  1. 定义 Feed 账户:你通过我们之前的 Feed 初始化脚本 (GgGVgSLWAyL9Xf4fGaAQQCkmWetBjX7PCNz8kTK97DKB) 提供创建的 Feed 账户公钥。
  2. 构建指令:Switchboard 的 .readPrice() 方法指的是链上的 read_price 方法。.accounts({ feed }) 步骤将 Feed 账户绑定到指令。
  3. 构建交易:将指令包装在带有签名和计算限制的版本化交易中。
  4. 模拟交易:首先模拟交易以预览日志并在花费交易费用之前捕获错误。你的链上程序中的 msg! 输出会在此处显示,脚本会捕获并显示它。
  5. 将交易发送到 Solana 网络:最后,脚本将交易发送到链上,并打印一个链接以在 Solana Explorer 中查看它。
import { PublicKey } from "@solana/web3.js";
import * as sb from "@switchboard-xyz/on-demand";
import * as anchor from "@coral-xyz/anchor";
import { TX_CONFIG } from "./utils";

(async function main() {
  try {
    console.log("正在从 Switchboard 获取 SOL/USD 价格...");

    // 从本地环境加载密钥对和连接
    const { keypair, connection } = await sb.AnchorUtils.loadEnv();

    // 创建 Anchor Provider 并附加上去
    const provider = new anchor.AnchorProvider(
      connection,
      new anchor.Wallet(keypair),
      { commitment: "confirmed" }
    );
    anchor.setProvider(provider);

    // 使用 Anchor 的工作区加载已部署的程序
    const program = anchor.workspace.switchOnDemandPriceFeed;

    // ====== 1. 定义 FEED 账户 ======
    // 替换为你的 Feed 账户地址,来自 initializeFeeds.ts 的输出
    const feedAccount = new PublicKey("GgGVgSLWAyL9Xf4fGaAQQCkmWetBjX7PCNz8kTK97DKB");

    // 2. **构建指令**
    // 构建指令以调用链上的 'read_price' 方法
    const ix = await program.methods
      .readPrice()
      .accounts({
        feed: feedAccount,
      })
      .instruction();

    // ====== 3. 构建交易 ======
    // 包装在一个版本化交易中,由支付者签名
    const tx = await sb.asV0Tx({
      connection,
      ixs: [ix],
      payer: keypair.publicKey,
      signers: [keypair],
      computeUnitPrice: 200_000,
      computeUnitLimitMultiple: 1.3,
    });

    // ====== 4. 交易模拟 ======
    // 模拟交易以捕获日志(包括我们的价格输出)
    const sim = await connection.simulateTransaction(tx, TX_CONFIG);

    if (sim.value.logs) {
      const priceLog = sim.value.logs.find(log =>
        log.includes("SOL/USD price:"));

      if (priceLog) {
        console.log(`✅ ${priceLog}`);
      } else {
        console.log("所有日志:", sim.value.logs);
      }
    }

    // ====== 5. 将交易发送到 Solana 网络 ======
    // 发送实际交易并记录其签名链接
    const sig = await connection.sendTransaction(tx, TX_CONFIG);
    console.log(`📝 交易: https://explorer.solana.com/tx/${sig}?cluster=devnet`);

  } catch (error) {
    console.error("❌ 获取价格时出错:", error);
  }
})();

现在,在一个终端中激活 runfeeds.ts,打开第二个终端并运行读取脚本:

bun run scripts/showPrice.ts

你应该会看到直接从你的链上程序记录的 SOL/USD 价格,这证实了整个数据管道正在正常工作。

终端中显示的 SOL/USD 价格

在我们的示例中,我们只是获取价格并显示它。你可以构建任何使用 Switchboard 链上数据的应用程序。

结论

我们学习了如何与 Switchboard 预言机工作流交互,以从多个来源获取可靠数据并在链上使用。此过程涉及的步骤是:

  • 编写你的链上程序,该程序应与来自 Switchboard Feed 账户的数据交互。
  • 在链下初始化和配置 Switchboard Feed,这会在链上生成一个 Feed 账户公钥,你的应用程序 Feed 运行器可以使用该公钥持续更新数据。
  • 运行客户端脚本以使用公钥从 Feed 读取数据。

后续练习

创建一个程序来计算价格影响,评估交易滑点将如何影响交易。该函数应利用 Switchboard 价格 Feed 数据。

  • 从 Switchboard 检索当前的 SOL/USD 价格
  • 实现基于交易规模的分层滑点模型
  • 计算滑点后的实际执行价格
  • 报告原始交易规模、当前市场价格、计算出的价格影响和滑点后的有效执行价格。
  • 原文链接: rareskills.io/post/solan...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论