如何将 Pyth 与 Eclipse SVM 结合使用

本文介绍了如何使用 Pyth 网络和 Eclipse 测试网创建一个 TypeScript CLI 应用程序,该程序通过 Pyth 网络获取 ETH 价格,并判断香蕉是否成熟。文章详细介绍了 Pyth 网络的工作原理、如何从 Pyth 网络获取价格数据,以及如何部署和交互 Rust 智能合约。

概述

在本指南中,你将学习如何使用 Pyth 和 Eclipse 测试网创建一个 TypeScript CLI 应用程序。此应用程序将根据 ETH 价格确定香蕉是否成熟,ETH 价格将从 Pyth 网络获取。

学习如何通过创建一个检查 ETH 价格并确定香蕉成熟度的 TypeScript CLI 应用程序,将 Pyth 与 Eclipse 测试网集成。

QuickNode

How to use Pyth with Eclipse SVM

QuickNode

Watch on

/

订阅我们的 YouTube 频道以获取更多视频! Subscribe

前提条件

  • 与 Solana 虚拟机 (SVM) 兼容的 Eclipse 钱包
  • Eclipse SVM 钱包中的测试网 ETH
  • TypeScript 和 Rust 编程语言的基础知识

你将学到什么

  • 什么是 Pyth 网络以及它如何工作
  • 如何从 Pyth 网络获取价格数据
  • 如何部署和交互 Rust 智能合约

什么是 Pyth 网络?

Pyth 网络是一个高保真、低延迟的预言机网络,可将链下金融数据与区块链生态系统桥接起来。由于区块链是确定性和封闭的系统(对于所有节点上的相同输入产生相同的输出,没有外部影响),因此它们无法原生访问链下信息。Pyth 通过从各种来源收集实时数据并使其在链上可用来解决此问题,从而使智能合约能够访问加密货币、股票、外汇和其他资产的金融市场数据。这种能力对于 DeFi 应用程序、交易平台以及任何需要准确、实时金融数据的区块链应用程序至关重要。

Pyth 如何运作?

Pyth 网络通过三个核心组件运行:发布者、链上程序和消费者。超过 120 家信誉良好的第一方数据提供商(包括交易所和做市商)将定价数据发布到链上预言机程序。这些预言机智能合约聚合和处理来自多个来源的数据,从而为每个资产创建一个单一的价格馈送,该馈送可在 100 多个区块链上使用。每个价格馈送都包含一个量化数据不确定性的置信区间。由于不同的市场实体报告同一资产的不同价格,Pyth 会计算置信水平。一个紧密的区间表示来源非常一致(高置信度),而一个更宽的区间表示更多的差异或更少的流动性(较低的置信度)。此指标可帮助开发人员了解他们所使用价格数据的可靠性。

应用程序设置

步骤 1:安装系统工具

1.1 安装 Rust
## 下载并安装 Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

## 出现提示时,按 1 进行默认安装

## 获取 Rust 环境
source $HOME/.cargo/env

## 安装并设置特定版本
rustup install 1.75.0
rustup default 1.75.0

## 验证安装
rustc --version
## 预期输出:rustc 1.75.0 (82e1608df 2023-12-21)
1.2 安装 Solana CLI
## 具体安装 Solana 1.18.22
sh -c "$(curl -sSfL https://release.anza.xyz/v1.18.22/install)"

## 将 Solana 添加到 PATH
export PATH="/home/$USER/.local/share/solana/install/active_release/bin:$PATH"

## 使 PATH 永久生效(添加到你的 shell 配置)
echo 'export PATH="/home/$USER/.local/share/solana/install/active_release/bin:$PATH"' >> ~/.bashrc

## 或者对于 zsh 用户:
echo 'export PATH="/home/$USER/.local/share/solana/install/active_release/bin:$PATH"' >> ~/.zshrc

## 重新加载 shell 配置
source ~/.bashrc  # 或 source ~/.zshrc

## 验证安装
solana --version
## 预期输出:solana-cli 1.18.22 (src:9efdd74b; feat:4215500110, client:Agave)

步骤 2:配置 Solana 以用于 Eclipse

2.1 设置 Eclipse RPC URL
## 配置 Solana 以使用 Eclipse 测试网
solana config set --url https://testnet.dev2.eclipsenetwork.xyz/

## 验证配置
solana config get
## 应显示:RPC URL: https://testnet.dev2.eclipsenetwork.xyz/
2.2 创建并配置钱包
## 生成新钱包(保存助记词!)
solana-keygen new --outfile mywallet.json

## 设置为默认钱包(新生成的钱包文件或预先存在的钱包文件)
solana config set --keypair ./mywallet.json

## 检查你的钱包地址
solana address
## 示例输出:4saf89xtUYFmqiwZU7BH7RX6udiXejjFiMDyyPAVyeEE

## 检查余额(最初为 0)
solana balance

步骤 3:创建智能合约项目

3.1 初始化 Cargo 项目
## 创建项目目录
mkdir banana-ripeness-checker
cd banana-ripeness-checker

## 初始化为 Rust 库(不是二进制文件)
cargo init --lib

## 验证结构
ls -la
## 应该显示:
## .
## ├── Cargo.toml
## └── src/
##     └── lib.rs
3.2 配置 Cargo.toml
## 在你的编辑器中打开 Cargo.toml
nano Cargo.toml

## 或者
code Cargo.toml

将全部内容替换为:

[package]
name = "banana-ripeness-checker"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib", "lib"]  # 构建为 Solana 的动态库
name = "banana_ripeness_checker"

[dependencies]
solana-program = "1.18.22"  # core Solana SDK
borsh = "0.10.3"           # 序列化 (二进制对象表示)
bytemuck = "1.14.0"        # 零拷贝字节操作
3.3 编写智能合约 (lib.rs)
## 打开 lib.rs
nano src/lib.rs

## 或者
code src/lib.rs

将全部内容替换为:

use solana_program::{
    account_info::AccountInfo,
    entrypoint,                    // 用于定义程序入口的宏
    entrypoint::ProgramResult,
    msg,                           // 用于日志记录
    program_error::ProgramError,
    pubkey::Pubkey,
};
use borsh::{BorshDeserialize, BorshSerialize};

// 临时 ID - 部署后更新
solana_program::declare_id!("11111111111111111111111111111111");

// 此宏创建程序入口点
entrypoint!(process_instruction);

// 与 TypeScript 客户端匹配的数据结构
##[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct PriceData {
    pub price: i64,      // 带有小数的 ETH 价格
    pub decimals: i32,   // 小数位数
}

pub fn process_instruction(
    _program_id: &Pubkey,
    _accounts: &[AccountInfo],
    instruction_data: &[u8],  // 我们序列化的价格数据
) -> ProgramResult {
    msg!("🍌 香蕉成熟度检查器启动中...");

    // 从字节反序列化价格数据
    let price_data = PriceData::try_from_slice(instruction_data)
        .map_err(|_| ProgramError::InvalidInstructionData)?;

    // 转换为美元(例如,255816500000 且有 8 个小数位 = $2558.16)
    let eth_price_usd = price_data.price / 10_i64.pow(price_data.decimals as u32);

    msg!("🍌 ETH 价格:${}", eth_price_usd);
    msg!("原始价格:{} 具有 {} 位小数", price_data.price, price_data.decimals);

    const RIPENESS_THRESHOLD: i64 = 3000;

    // 核心业务逻辑:香蕉是否成熟?
    if eth_price_usd >= RIPENESS_THRESHOLD {
        msg!("🟢 你的香蕉成熟了!ETH 高于 $3000!");
        msg!("🎉 是时候做香蕉面包了!");
    } else {
        msg!("🟡 你的香蕉还没有成熟。ETH 低于 $3000。");
        msg!("⏰ 继续等待,它很快就会成熟!");
    }

    Ok(())
}
3.4 构建和部署智能合约
## 构建程序
cargo build-sbf

## 部署并保存程序 ID
solana program deploy target/deploy/banana_ripeness_checker.so

## 你将看到如下输出:
## Program Id: 6VrSSGDWZ1KjWZuzAAJTssKyu2unLCcy7xyqkCDaYQAe

## 重要提示:复制你的程序 ID!

## 使用你的程序 ID 更新 lib.rs
## 使用你的实际程序 ID 编辑 declare_id! 行
nano src/lib.rs

## 使用更新的 ID 重新构建
cargo build-sbf

## 重新部署到同一地址
solana program deploy target/deploy/banana_ripeness_checker.so --program-id YOUR_PROGRAM_ID

步骤 4:创建 TypeScript 客户端

4.1 初始化 NPM 项目
## 创建客户端目录
mkdir banana-cli
cd banana-cli

## 初始化 npm 项目
npm init -y

## 创建源目录
mkdir src
4.2 创建 TypeScript 配置 (tsconfig.json)
## 创建 tsconfig.json
nano tsconfig.json

## 或者
code tsconfig.json

添加以下内容:

{
  "compilerOptions": {
    "target": "es2020",              // 现代 JS 功能
    "module": "commonjs",            // ts-node 所需
    "lib": ["es2020"],               // 可用的 JS API
    "outDir": "./dist",              // 编译后的输出
    "rootDir": "./src",              // 源位置
    "strict": true,                  // 类型安全
    "esModuleInterop": true,         // 导入兼容性
    "skipLibCheck": true,            // 更快的构建
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true        // 导入 JSON 文件
  }
}
4.3 更新 package.json 脚本
## 编辑 package.json
nano package.json

## 或者
code package.json

更新 scripts 部分:

{
  "name": "banana-cli",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "start": "ts-node --transpile-only src/banana-checker.ts",
    "build": "tsc",
    "banana": "npm start"  // 友好的别名
  },
  "dependencies": {
    "@pythnetwork/hermes-client": "^2.0.0",  // Pyth 价格馈送
    "@solana/web3.js": "1.78.0",             // 区块链交互
    "rpc-websockets": "7.10.0",              // 需要完全匹配的版本!
    "borsh": "^2.0.0",                       // 必须与 Rust 端匹配
    "chalk": "^4.1.2",                       // 终端颜色
    "figlet": "^1.8.1",                      // ASCII 艺术
  },
  "devDependencies": {
    "@types/node": "^18.0.0",                // TypeScript 类型
    "typescript": "^5.0.0",
    "ts-node": "^10.9.2",                    // 直接 TS 执行
    "@types/figlet": "^1.7.0",
  }
}
4.4 安装 NPM 包
## 从 package.json 安装
npm install
4.5 创建 TypeScript 客户端 (banana-checker.ts)
## 创建主 TypeScript 文件
nano src/banana-checker.ts

## 或者
code src/banana-checker.ts

添加完整的 TypeScript 代码:

import 'rpc-websockets/dist/lib/client';

import {
  Connection,
  PublicKey,
  Keypair,
  Transaction,
  TransactionInstruction
} from '@solana/web3.js';

import { HermesClient } from '@pythnetwork/hermes-client'; // 用于获取预言机价格数据的 Pyth 网络 Hermes 客户端

import * as fs from 'fs';
import chalk from 'chalk';      // 终端颜色样式
import figlet from 'figlet';    // ASCII 艺术生成

const ECLIPSE_RPC = 'ECLIPSE_TESTNET_RPC_URL'; // Eclipse 测试网 RPC 端点,公共端点:https://testnet.dev2.eclipsenetwork.xyz/
const HERMES_URL = 'HERMES_API_URL'; // Pyth Hermes API URL,公共端点:https://api.pyth.network/hermes

const ETH_USD_FEED = '0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace'; // Pyth ETH/USD 价格馈送 ID,在此处查找价格馈送:https://www.pyth.network/developers/price-feed-ids
const YOUR_PROGRAM_ID = 'YOUR_DEPLOYED_PROGRAM_ID_HERE'; // 更新此内容!

class PriceData {
  price: bigint;    // 使用 bigint 处理大数字,避免精度损失
  decimals: number; // 价格的小数位数

  constructor(fields: { price: bigint; decimals: number }) {
    this.price = fields.price;
    this.decimals = fields.decimals;
  }
}

class BananaRipenessChecker {

  private connection: Connection;
  private wallet: Keypair;
  private hermesClient: HermesClient;

  constructor(walletPath: string) {
    const walletData = JSON.parse(fs.readFileSync(walletPath, 'utf8')); // 从 JSON 文件加载钱包
    this.wallet = Keypair.fromSecretKey(new Uint8Array(walletData));

    this.connection = new Connection(ECLIPSE_RPC, 'confirmed');

    this.hermesClient = new HermesClient(HERMES_URL);
  }

  async checkBananaRipeness(): Promise<void> {
    try {
      // 步骤 1:从 Pyth 网络获取 ETH 价格
      console.log(chalk.blue('📡 从 Pyth 网络获取 ETH 价格...'));

      const priceUpdates = await this.hermesClient.getLatestPriceUpdates([ETH_USD_FEED]);

      // 验证响应结构
      if (!priceUpdates || !Array.isArray(priceUpdates.parsed)) {
        throw new Error('未从 Hermes 解析任何价格更新。');
      }

      const feedId = ETH_USD_FEED.startsWith("0x") ? ETH_USD_FEED.slice(2) : ETH_USD_FEED;

      // 在响应中找到我们的特定价格馈送
      const priceUpdate = priceUpdates.parsed.find(p => p.id === feedId);

      if (!priceUpdate || !priceUpdate.price) {
        throw new Error('未从 Hermes 收到有效的 ETH 价格更新。');
      }

      // 步骤 2:处理价格数据
      const { price, expo } = priceUpdate.price;
      const priceValue = BigInt(price);        // 转换为 BigInt 以获得精度
      const decimals = Math.abs(expo);         // 将负指数转换为正小数

      // 计算人类可读的价格以供显示
      const displayPrice = Number(priceValue) / Math.pow(10, decimals);
      console.log(chalk.cyan(`💰 当前 ETH 价格:$${displayPrice.toFixed(2)}`));

      // 步骤 3:准备用于智能合约的数据
      const priceDataForContract = new PriceData({
        price: priceValue,
        decimals: decimals
      });

      // 步骤 4:手动序列化 - 对 Rust 兼容性至关重要!
      const buffer = Buffer.alloc(12);

      buffer.writeBigInt64LE(priceDataForContract.price, 0);

      buffer.writeInt32LE(priceDataForContract.decimals, 8);

      const serializedData = buffer;

      // 步骤 5:创建区块链交易
      console.log(chalk.blue('\n🔍 在 Eclipse 区块链上检查香蕉状态...\n'));

      // 为我们的智能合约构建指令
      const instruction = new TransactionInstruction({
        programId: new PublicKey(YOUR_PROGRAM_ID), // 目标程序
        keys: [],                                   // 此简单程序不需要任何帐户
        data: serializedData                        // 我们序列化的价格数据
      });

      // 将指令包装在交易中
      const transaction = new Transaction().add(instruction);

      // 步骤 6:发送交易并等待确认
      try {
        // 将交易发送到区块链
        // skipPreflight: false = 首先运行模拟以捕获错误
        const signature = await this.connection.sendTransaction(
          transaction,
          [this.wallet],           // 签名者数组(仅限我们的钱包)
          { skipPreflight: false }
        );

        // 等待区块链确认
        await this.connection.confirmTransaction(signature, 'confirmed');

        // 步骤 7:检索交易详细信息以读取程序日志
        const txDetails = await this.connection.getTransaction(signature, {
          maxSupportedTransactionVersion: 0
        });

        // 步骤 8:解析并显示智能合约输出
        if (txDetails?.meta?.logMessages) {
          const logs = txDetails.meta.logMessages;

          console.log(chalk.gray('\n📜 智能合约说:\n'));

          // 仅提取我们程序的日志(删除系统消息)
          logs.forEach(log => {
            if (log.includes('Program log:')) {
              // 删除“Program log:”前缀以仅显示我们的消息
              const message = log.replace('Program log: ', '');
              console.log(message);
            }
          });
        }

      } catch (error: any) {
        console.error(chalk.red('❌ 检查成熟度时出错:'), error.message || error);
      }

      // 步骤 9:显示钱包信息
      console.log(chalk.gray(`\n📍 你的钱包:${this.wallet.publicKey.toBase58()}`));

      // 获取并显示余额(Eclipse 使用 ETH,而不是 SOL)
      const balance = await this.connection.getBalance(this.wallet.publicKey);
      console.log(chalk.gray(`💰 余额:${balance / 1e9} ETH\n`));

    } catch (error: any) {
      console.error(chalk.red('❌ 错误:'), error.message || error);
    }
  }

  async showWelcome(): Promise<void> {
    console.clear();
    console.log('\n');

    // 使用 figlet 生成 ASCII 艺术标题
    const title = figlet.textSync('Banana Checker', {
      font: 'Standard',
      horizontalLayout: 'default',
      verticalLayout: 'default'
    });

    // 以香蕉黄色显示标题
    console.log(chalk.yellow(title));

    console.log(
      chalk.bold.hex('#7142CF')('         Pyth') +
      chalk.white(' × ') +
      chalk.bold.hex('#a1fe9f')('Eclipse')
    );

    // 简短描述
    console.log(chalk.cyan('\n📡 实时 ETH/USD 预言机数据\n'));
  }
}

async function main() {

  const checker = new BananaRipenessChecker('./mywallet.json');

  await checker.showWelcome();

  console.log(chalk.blue('按任意键检查你的香蕉...'));

  await new Promise(resolve => process.stdin.once('data', resolve));

  await checker.checkBananaRipeness();

  console.log(chalk.gray('按 Ctrl+C 退出'));
}

if (require.main === module) {
  main().catch(console.error);
}

⚠️ 重要提示:使用你实际部署的程序 ID 更新 YOUR_PROGRAM_ID

4.6 复制钱包文件
## 将你的钱包复制到CLI目录
cp ../banana-ripeness-checker/mywallet.json ./mywallet.json

## 验证它是否存在
ls -la mywallet.json

步骤 5:运行应用程序

5.1 最终清单
## 1. 验证你是否在 banana-cli 目录中
pwd
## 应显示:.../banana-cli

## 2. 检查所有文件是否存在
ls -la
## 应显示:
## - package.json
## - tsconfig.json
## - mywallet.json
## - src/banana-checker.ts
## - node_modules/

## 3. 验证程序 ID 是否已更新
grep "YOUR_PROGRAM_ID" src/banana-checker.ts
## 不应显示占位符 - 应显示你的实际程序 ID
5.2 运行香蕉检查器!
## 执行应用程序
npm run banana

## 预期输出:
## 1. ASCII 艺术欢迎屏幕
## 2. “按任意键检查你的香蕉...”
## 3. 从 Pyth 获取 ETH 价格
## 4. 将交易发送到 Eclipse
## 5. 显示香蕉状态的智能合约响应

后续步骤

成功实施香蕉成熟度检查器后,请考虑探索:

  • 在你的 Rust 程序中直接集成 Pyth Solana 接收器 SDK。
  • 你的 CLI 应用程序的其他功能,例如用于价格阈值的用户输入。
我们 ❤️ 反馈!

如果你对新主题有任何反馈或要求,请告诉我们。我们很乐意收到你的来信。

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

0 条评论

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