本文介绍了如何使用 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
Pyth 网络是一个高保真、低延迟的预言机网络,可将链下金融数据与区块链生态系统桥接起来。由于区块链是确定性和封闭的系统(对于所有节点上的相同输入产生相同的输出,没有外部影响),因此它们无法原生访问链下信息。Pyth 通过从各种来源收集实时数据并使其在链上可用来解决此问题,从而使智能合约能够访问加密货币、股票、外汇和其他资产的金融市场数据。这种能力对于 DeFi 应用程序、交易平台以及任何需要准确、实时金融数据的区块链应用程序至关重要。
Pyth 网络通过三个核心组件运行:发布者、链上程序和消费者。超过 120 家信誉良好的第一方数据提供商(包括交易所和做市商)将定价数据发布到链上预言机程序。这些预言机智能合约聚合和处理来自多个来源的数据,从而为每个资产创建一个单一的价格馈送,该馈送可在 100 多个区块链上使用。每个价格馈送都包含一个量化数据不确定性的置信区间。由于不同的市场实体报告同一资产的不同价格,Pyth 会计算置信水平。一个紧密的区间表示来源非常一致(高置信度),而一个更宽的区间表示更多的差异或更少的流动性(较低的置信度)。此指标可帮助开发人员了解他们所使用价格数据的可靠性。
## 下载并安装 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)
## 具体安装 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)
## 配置 Solana 以使用 Eclipse 测试网
solana config set --url https://testnet.dev2.eclipsenetwork.xyz/
## 验证配置
solana config get
## 应显示:RPC URL: https://testnet.dev2.eclipsenetwork.xyz/
## 生成新钱包(保存助记词!)
solana-keygen new --outfile mywallet.json
## 设置为默认钱包(新生成的钱包文件或预先存在的钱包文件)
solana config set --keypair ./mywallet.json
## 检查你的钱包地址
solana address
## 示例输出:4saf89xtUYFmqiwZU7BH7RX6udiXejjFiMDyyPAVyeEE
## 检查余额(最初为 0)
solana balance
## 创建项目目录
mkdir banana-ripeness-checker
cd banana-ripeness-checker
## 初始化为 Rust 库(不是二进制文件)
cargo init --lib
## 验证结构
ls -la
## 应该显示:
## .
## ├── Cargo.toml
## └── src/
## └── lib.rs
## 在你的编辑器中打开 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" # 零拷贝字节操作
## 打开 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(())
}
## 构建程序
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
## 创建客户端目录
mkdir banana-cli
cd banana-cli
## 初始化 npm 项目
npm init -y
## 创建源目录
mkdir src
## 创建 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 文件
}
}
## 编辑 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",
}
}
## 从 package.json 安装
npm install
## 创建主 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!
## 将你的钱包复制到CLI目录
cp ../banana-ripeness-checker/mywallet.json ./mywallet.json
## 验证它是否存在
ls -la mywallet.json
## 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
## 执行应用程序
npm run banana
## 预期输出:
## 1. ASCII 艺术欢迎屏幕
## 2. “按任意键检查你的香蕉...”
## 3. 从 Pyth 获取 ETH 价格
## 4. 将交易发送到 Eclipse
## 5. 显示香蕉状态的智能合约响应
成功实施香蕉成熟度检查器后,请考虑探索:
如果你对新主题有任何反馈或要求,请告诉我们。我们很乐意收到你的来信。
- 原文链接: quicknode.com/guides/sol...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!