Yellowstone 与 Vixen:使用 Rust 监控实时 Jupiter 限价单

本文介绍了如何利用 Rust、Yellowstone gRPC 和 Vixen 框架构建实时 Jupiter 限价单监控系统。内容涵盖了从 IDL 生成类型安全解析器、处理交易流、到提取人类可读代币数据的完整技术流程,并详细演示了 Vixen v0.6.1 的宏解析与过滤新特性。

概览

本指南介绍如何使用 Yellowstone gRPCVixen(一个基于 Rust 的 Solana 数据解析工具包)在 Rust 中构建一个实时的 Jupiter 限价单(Limit Order)监控器。该应用程序流式传输已确认的限价单交易,并以人类可读的 Token 数量和符号记录下单、成交和取消订单的操作。在此过程中,本指南将展示 Vixen v0.6.1 的三个特性:通过 proc-macro 实现基于 IDL 的解析器代码生成、用于过滤掉失败交易的 TransactionPrefilter 中的 failed 字段,以及用于可观测性的结构化追踪(tracing)。

TL;DR

  • 使用 Rust、Yellowstone gRPC 和 Vixen 构建实时 Jupiter Limit Order v2 监控器
  • 使用 Vixen 的 include_vixen_parser! proc-macro 直接从 Jupiter Limit Order IDL 生成类型安全的解析器
  • 使用 Vixen v0.6.1 的 TransactionPrefilter 仅流式传输已确认(未失败)的交易
  • 以人类可读的 Token 符号和数量记录下单、成交和取消订单的操作
  • 在 Quicknode 的 Yellowstone gRPC 插件(Solana Mainnet)上运行

你将要做什么

  • 设置一个包含 Vixen v0.6.1 依赖项的 Rust 项目
  • 获取 Jupiter Limit Order v2 IDL 并将其转换为 Codama 格式
  • 使用 Vixen 的 proc-macro 生成类型安全的指令解析器
  • 实现一个处理程序(handler),以人类可读的输出记录限价单事件
  • 构建并运行一个连接到 Quicknode Yellowstone gRPC 端点的 Vixen 运行时(runtime)

你需要准备什么

本指南还使用了以下软件包:

依赖 版本
rustc 1.85.0
yellowstone-vixen 0.6.1
yellowstone-vixen-core 0.6.1
yellowstone-vixen-parser 0.6.1
yellowstone-vixen-proc-macro 0.6.1
yellowstone-vixen-yellowstone-grpc-source 0.6.1
borsh 1
bs58 0.5
chrono 0.4
reqwest 0.12
serde 1
clap 4
rustls 0.23
toml 0.8
tracing 0.1
tracing-subscriber 0.3

什么是 Yellowstone gRPC?

Yellowstone gRPC 是一种基于 Geyser 插件系统构建的、用于 Solana 的高性能数据流解决方案。Yellowstone gRPC 不是通过轮询 RPC 端点获取新数据或管理 WebSocket 重新连接,而是将账户更新、交易和槽(slot)通知作为连续流推送到你的应用程序。与传统方法相比,这提供了更低的延迟和更高的吞吐量。

Quicknode 通过 Yellowstone gRPC 插件 提供 Yellowstone gRPC 服务。你可以从 Quicknode 控制面板 在任何 Solana 端点上启用它。该付费插件为你的应用程序提供专用的 gRPC 端点和身份验证 Token。

关键过滤功能包括:

  • 按程序地址过滤,以仅接收涉及特定程序的交易
  • 过滤掉失败的交易,以便仅处理已确认的链上事件
  • 过滤掉投票(vote)交易以减少噪音

什么是 Vixen?

Vixen 是一个开源的 Rust 框架,用于在 Yellowstone gRPC 之上构建 Solana 数据流水线。它自动处理 gRPC 订阅、交易过滤和反序列化,因此应用程序代码接收的是类型化的 Rust 结构体,而不是原始字节。

Vixen 使用 Parser + Handler 架构来分离数据提取和业务逻辑:

  • Parser:将原始交易数据反序列化为类型化的 Rust 结构体
  • Handler:接收解析后的数据并实现你的业务逻辑
  • Pipeline:将一个解析器连接到一个或多个处理程序
  • Runtime:管理 gRPC 连接、流订阅和流水线执行

根据 Vixen v0.6.1 发布说明,此版本引入了三个本指南将展示的以生产为重点的改进:

  1. 通过 proc-macro 实现基于 IDL 的代码生成:使用 include_vixen_parser! 直接从程序的 IDL 生成类型安全的解析器,消除了手动编写反序列化代码的需要
  2. TransactionPrefilter 中的 failed 字段:从流中排除失败的交易,以便只有确认的链上事件到达你的处理程序
  3. 用于可观测性的 Tracing span:通过 tracing crate 而不是普通的 log/env_logger 提供结构化、可过滤的日志输出

Vixen vs Carbon

VixenCarbon 都是用于解析 Solana 程序数据的 Rust 框架。它们共享相同的核心方法:通过 Yellowstone gRPC 流式传输交易,将指令数据解码为类型化结构体,并在处理程序中处理事件,但在生成解析器的方式和支持的数据源方面有所不同:

特性 Vixen Carbon
Parser 生成 编译时通过 include_vixen_parser! proc-macro 进行 IDL 代码生成 预构建的解码器 crate + 从 IDL 进行 CLI 代码生成
数据源 Yellowstone gRPC Yellowstone gRPC, JITO Shredstream, JSON RPC
内置程序支持 无 —— 从任何 Anchor IDL 生成 60+ 个针对热门程序的预构建解码器
受支持程序的设置 获取 IDL → 转换为 Codama → 调用宏 添加单个 Cargo crate 依赖
可观测性 带有结构化 span 的 tracing crate Prometheus 指标 + 日志
流水线模型 Runtime → Pipeline → Parser → Handler Pipeline → Datasource → Decoder → Processor

有关基于 Carbon 的指南,请参阅 Yellowstone 和 Carbon:解析实时 Solana 程序数据

示例应用:Jupiter 限价单监控器

Jupiter Limit Orders 是一个链上订单簿,允许交易者下单并在市场达到指定价格时自动执行。与即时兑换不同,限价单会一直保留在链上,直到撮合者(在程序的账户中被称为 taker)以请求的价格或更好的价格成交。下单者(maker)也可以随时取消未成交的订单以取回其 Token。

本指南使用 Jupiter Limit Orders 有三个原因:

  1. 所有活动都通过单个 Anchor 程序 (j1o2qRpjcyUwEvwtcfhEQefh773ZgjxcVRry7LDqg5X) 进行,这可以清晰地映射到 Vixen 的单流水线模型。
  2. 该程序发布了链上 IDL,这让我们能够端到端地演示 Vixen 的 IDL 代码生成功能。
  3. 该程序发出三种离散的、语义清晰的指令类型(每个生命周期事件一个),这使得处理程序逻辑易于阅读和扩展。

本指南监控的三种指令是:

  • InitializeOrder:下单者创建一个新的限价单,指定输入/输出 Token 和数量
  • FillOrder:撮合者以请求的价格或更好的价格成交现有订单
  • CancelOrder:下单者取消其未成交的订单并取回其 Token

对于每个事件,处理程序通过 Jupiter 的 Token API 解析人类可读的 Token 符号,并使用交易中 Token 余额元数据的十进制精度格式化原始 Token 数量。

设置项目

创建项目目录和 idls 文件夹来存储 Jupiter Limit Order IDL:

cargo new jupiter-lo-vixen
cd jupiter-lo-vixen
mkdir idls

Cargo.toml 的内容替换为以下内容。所有 Vixen crate 均固定为 v0.6.1:

Cargo.toml

[package]
name = "jupiter-lo-vixen"
version = "0.1.0"
edition = "2021"

[dependencies]
yellowstone-vixen = "0.6.1"
yellowstone-vixen-core = "0.6.1"
yellowstone-vixen-parser = "0.6.1"
yellowstone-vixen-proc-macro = "0.6.1"
yellowstone-vixen-yellowstone-grpc-source = "0.6.1"
borsh = { version = "1", features = ["derive"] }
bs58 = "0.5"
chrono = { version = "0.4", features = ["clock"] }
reqwest = { version = "0.12", features = ["json", "rustls-tls"] }
serde = { version = "1", features = ["derive"] }
clap = { version = "4", features = ["derive"] }
rustls = { version = "0.23", features = ["ring"] }
toml = "0.8"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

请注意,yellowstone-vixen-core 必须是一个直接依赖项(而不仅仅是通过 yellowstone-vixen 传递的间接依赖),因为 proc-macro 生成的代码会通过 crate 名称引用它。

以下是关键依赖项的作用:

  • yellowstone-vixen / yellowstone-vixen-core:Vixen 运行时和核心 trait(Parser、Handler、Pipeline)
  • yellowstone-vixen-proc-macro:用于基于 IDL 的代码生成的 include_vixen_parser!
  • yellowstone-vixen-yellowstone-grpc-source:将 Vixen 运行时连接到 Yellowstone gRPC 端点
  • borsh:Solana 程序使用的序列化格式(生成的解析器所需)
  • bs58:交易签名的 Base58 编码
  • chrono:日志输出的时间戳格式化
  • reqwest:用于从 Jupiter API 获取 Token 符号的 HTTP 客户端
  • clap:用于 --config 标志的命令行参数解析
  • rustls:gRPC 连接所需的 TLS 提供程序
  • tracing / tracing-subscriber:具有基于环境的过滤功能的结构化日志

在项目根目录下创建 Vixen.toml。这是应用程序唯一需要的配置文件:

Vixen.toml

[source]
endpoint = "YOUR_QN_ENDPOINT:10000"
x-token = "YOUR_X_TOKEN"
timeout = 120
commitment-level = "confirmed"
accept-compression = "zstd"

你可以在 Quicknode 控制面板 中你的 Solana Mainnet 端点的 Yellowstone gRPC 插件设置下找到这些值。控制面板显示的端点 URL 格式为 https://your-endpoint-name.solana-mainnet.quiknode.pro/abc123。将 :10000 附加到主机名以用于 endpoint 字段,并将结尾的 Token (abc123) 用作你的 x-token。有关更多详细信息,请参阅 Yellowstone gRPC 文档

从 Limit Order IDL 生成解析器

你不必为每个指令编写手动反序列化代码,而是提供 Codama 格式的程序 IDL,include_vixen_parser! 宏会在编译时生成一个完整的类型安全解析器。本节将引导你完成完整流程,以便你可以将其应用于任何 Anchor 程序。

获取 Jupiter Limit Order v2 IDL:

Vixen 仓库已经包含了一个现成的 Jupiter Limit Order v2 的 Codama IDL,位于 tests/idls/lo_v2.json。你可以直接将其复制到你的 idls/ 文件夹中,然后直接跳到调用 Proc-Macro。下面的步骤将引导你完成完整的“获取并转换”流程,以便你可以将相同的过程应用于尚未提供 Codama IDL 的任何 Anchor 程序。

使用 Anchor CLI 获取 Jupiter Limit Order v2 程序的链上 IDL。将 YOUR_QN_RPC_URL 替换为你的 Quicknode Solana Mainnet RPC 端点:

anchor idl fetch j1o2qRpjcyUwEvwtcfhEQefh773ZgjxcVRry7LDqg5X \
  --provider.cluster YOUR_QN_RPC_URL \
  -o idls/lo_v2_raw.json

IDL 包含完整的程序接口定义:指令名称、账户结构、参数类型和程序地址。这是 Vixen 用来生成类型化 Rust 代码的内容。

一个 Anchor IDL 的陷阱

当你使用 anchor idl 命令下载 IDL 时,它带有一个小的不一致性,这会阻碍 Codama 的转换器。在 initialize_order 指令中,一个 PDA 种子引用了名为 unique_id 的参数,但在该 IDL 中,unique_id 实际上嵌套在 params 结构体中,而不是顶级参数。Codama 在顶级查找它,找不到,然后报错。

当你将 Codama 与其他程序的 IDL(其 IDL 在技术上是有效的 Anchor 输出,但路径引用与实际参数结构不匹配)一起使用时,可能会遇到此类问题。在使用尚未具备 Codama 格式 IDL 的 IDL 与 Vixen 配合时,你可能必须手动排除此类故障。

要修复此问题,请打开 idls/lo_v2_raw.json 并在 initialize_order 指令的账户种子中找到此行(大约在第 621 行):

idls/lo_v2_raw.json

{
    "kind": "arg",
    "path": "unique_id"
}

将其更改为:

idls/lo_v2_raw.json

{
    "kind": "arg",
    "path": "params.unique_id"
}

转换为 Codama 格式:

将根项目文件夹初始化为 Node.js 项目,以便我们可以运行 Codama 命令:

npm init -y
npm install -g @codama/cli
npm install @codama/nodes-from-anchor

@codama/nodes-from-anchor 是在转换过程中理解 Anchor IDL 格式所必需的。

将原始 Anchor IDL 转换为 Codama 格式:

codama convert idls/lo_v2_raw.json idls/lo_v2.json

这个一次性的转换步骤为任何 Anchor 程序解锁了 Vixen 的编译时代码生成。

调用 Proc-Macro

创建包含以下内容的 src/parser.rs

src/parser.rs

use yellowstone_vixen_proc_macro::include_vixen_parser;

include_vixen_parser!("idls/lo_v2.json");

这个单一的宏调用在编译时生成:

  • 一个包含所有生成类型的 limit_order2 模块
  • 一个类型化的 Instructions 枚举,其变体对应 IDL 中的每个指令(InitializeOrderFillOrderCancelOrder 等)
  • 每个指令的类型化账户和参数结构体
  • 一个实现 Vixen 解析器 trait 的 InstructionParser
  • 一个配置了三个过滤器的 TransactionPrefilter:程序地址(仅限 Jupiter LO v2)、failed: Some(false) (v0.6.1 中的新功能) 以排除失败的交易,以及 vote: Some(false) 以排除投票交易

实现监控器

Vixen v0.6.1 使用 tracing crate 进行结构化、可过滤的日志输出,取代了 Vixen 早期版本中使用的旧的 log/env_logger 模式。

tracing 订阅器遵循 RUST_LOG 环境变量,因此你可以在运行时控制详细程度,而无需重新编译:

  • RUST_LOG=info:显示处理程序输出和 Vixen 生命周期事件
  • RUST_LOG=debug:添加 gRPC 连接详细信息和流订阅活动
  • RUST_LOG=warn:仅显示警告和错误

初始化发生在 main() 中任何其他代码运行之前,确保所有 Vixen 内部组件和你的处理程序代码都能从结构化日志中受益。

实现限价单处理程序

创建 src/handlers.rs。该处理程序从生成的解析器接收解析后的指令,并记录三个订单事件:下单、成交和取消。

该处理程序包含辅助函数,可从 Jupiter 的 Token API 获取人类可读的 Token 符号,并将其缓存以避免重复查找。它还从交易的 Token 余额元数据中提取十进制精度,以人类可读的形式显示金额(例如,1.5 USDC 而不是 1500000)。

src/handlers.rs

use std::{collections::HashMap, sync::Mutex};

use yellowstone_vixen::vixen_core::instruction::InstructionUpdate;

use crate::parser::limit_order2;

fn fmt_amount(raw: u64, decimals: u32) -> String {
    if decimals == 0 {
        return raw.to_string();
    }
    let s = format!("{:.prec$}", raw as f64 / 10f64.powi(decimals as i32), prec = decimals as usize);
    s.trim_end_matches('0').trim_end_matches('.').to_string()
}

fn fmt_token(amount: &str, symbol: &str, mint: &str) -> String {
    if symbol == mint {
        format!("{amount} {mint}")
    } else {
        format!("{amount} {symbol} ({mint})")
    }
}

async fn fetch_symbol(mint: &str) -> String {
    #[derive(serde::Deserialize)]
    struct Token {
        id: String,
        symbol: String,
    }

    let url = format!("https://api.jup.ag/tokens/v2/search?query={mint}");
    let fallback = mint.to_string();

    let Ok(resp) = reqwest::get(&url).await else {
        return fallback;
    };
    let Ok(tokens) = resp.json::<Vec<Token>>().await else {
        return fallback;
    };
    tokens
        .into_iter()
        .find(|t| t.id == mint)
        .map(|t| t.symbol)
        .unwrap_or(fallback)
}

#[derive(Debug, Default)]
pub struct LimitOrderHandler {
    symbol_cache: Mutex<HashMap<String, String>>,
}

impl LimitOrderHandler {
    async fn get_symbol(&self, mint: &str) -> String {
        {
            let cache = self.symbol_cache.lock().unwrap();
            if let Some(sym) = cache.get(mint) {
                return sym.clone();
            }
        }
        let symbol = fetch_symbol(mint).await;
        self.symbol_cache
            .lock()
            .unwrap()
            .insert(mint.to_string(), symbol.clone());
        symbol
    }
}

impl yellowstone_vixen::Handler<limit_order2::Instructions, InstructionUpdate>
    for LimitOrderHandler
{
    async fn handle(
        &self,
        value: &limit_order2::Instructions,
        raw: &InstructionUpdate,
    ) -> yellowstone_vixen::HandlerResult<()> {
        use limit_order2::instruction::Instruction;

        let sig = bs58::encode(&raw.shared.signature).into_string();
        let ts = chrono::Utc::now().format("%Y-%m-%dT%H:%M:%S%.6fZ");

        let pre = &raw.shared.pre_token_balances;
        let post = &raw.shared.post_token_balances;
        let find_decimals = |mint: &str| {
            pre.iter()
                .chain(post.iter())
                .find(|b| b.mint == mint)
                .and_then(|b| b.ui_token_amount.as_ref())
                .map(|u| u.decimals)
        };

        match &value.instruction {
            Instruction::InitializeOrder { accounts, args } => {
                let input_mint_str = accounts.input_mint.to_string();
                let output_mint_str = accounts.output_mint.to_string();

                let input_symbol = self.get_symbol(&input_mint_str).await;
                let output_symbol = self.get_symbol(&output_mint_str).await;

                let making_amount = find_decimals(&input_mint_str)
                    .map(|d| fmt_amount(args.making_amount, d))
                    .unwrap_or_else(|| args.making_amount.to_string());
                let taking_amount = find_decimals(&output_mint_str)
                    .map(|d| fmt_amount(args.taking_amount, d))
                    .unwrap_or_else(|| args.taking_amount.to_string());

                let expired_at = args.expired_at
                    .and_then(|t| chrono::DateTime::from_timestamp(t, 0))
                    .map(|dt| dt.format("%Y-%m-%dT%H:%M:%SZ").to_string())
                    .unwrap_or_else(|| "None".to_string());

                let making = fmt_token(&making_amount, &input_symbol, &input_mint_str);
                let taking = fmt_token(&taking_amount, &output_symbol, &output_mint_str);
                tracing::info!(
                    tx = %sig,
                    maker = %accounts.maker,
                    making_amount = %making,
                    taking_amount = %taking,
                    expired_at = %expired_at,
                    "New limit order placed - {ts}",
                );
            },
            Instruction::FillOrder { accounts, args } => {
                let input_mint_str = accounts.input_mint.to_string();
                let output_mint_str = accounts.output_mint.to_string();

                let input_symbol = self.get_symbol(&input_mint_str).await;

                let input_amount = find_decimals(&input_mint_str)
                    .map(|d| fmt_amount(args.input_amount, d))
                    .unwrap_or_else(|| args.input_amount.to_string());

                let input = fmt_token(&input_amount, &input_symbol, &input_mint_str);
                tracing::info!(
                    tx = %sig,
                    taker = %accounts.taker,
                    output_mint = %output_mint_str,
                    input_amount = %input,
                    "Limit order filled - {ts}",
                );
            },
            Instruction::CancelOrder { accounts, .. } => {
                tracing::info!(
                    tx = %sig,
                    maker = %accounts.maker,
                    order = %accounts.order,
                    "Limit order cancelled - {ts}",
                );
            },
            _ => {},
        }

        Ok(())
    }
}

处理程序为生成的 limit_order2::Instructions 类型实现了 Vixen 的 Handler trait。它对三种指令变体进行模式匹配:

  • InitializeOrder:记录下单者的地址、带有人类可读数量的输入/输出 Token,以及订单的过期时间戳
  • FillOrder:记录撮合者的地址、输出 Mint,以及正在成交的输入金额
  • CancelOrder:记录下单者的地址和正在取消的订单账户
  • _(通配符):静默忽略所有其他指令(费用更新、管理操作等)

该处理程序还包括用户友好的格式化,例如 Token 数量的十进制转换以及通过 Jupiter 的 Token API 进行的代码符号查找。对于超高性能应用程序,这些站外 API 调用可能会影响性能。

构建 Vixen 运行时

将生成的 src/main.rs 替换为以下代码。这会将一切联系在一起:tracing 初始化、TLS 加密提供程序、配置加载,以及带有指令流水线的 Vixen 运行时。

src/main.rs

use std::path::PathBuf;

use clap::Parser as _;
use yellowstone_vixen::Pipeline;
use yellowstone_vixen_yellowstone_grpc_source::YellowstoneGrpcSource;

mod handlers;
mod parser;

use handlers::LimitOrderHandler;
use parser::limit_order2;

#[derive(clap::Parser)]
#[command(version, author, about = "Monitor Jupiter Limit Order v2 events via Yellowstone Vixen")]
struct Opts {
    #[arg(long, short)]
    config: PathBuf,
}

fn main() {
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
        )
        .init();

    rustls::crypto::ring::default_provider()
        .install_default()
        .expect("Failed to install rustls crypto provider");

    let Opts { config } = Opts::parse();
    let config = std::fs::read_to_string(config).expect("Error reading config file");
    let config = toml::from_str(&config).expect("Error parsing config");

    yellowstone_vixen::Runtime::<YellowstoneGrpcSource>::builder()
        .instruction(Pipeline::new(limit_order2::InstructionParser, [LimitOrderHandler::default()]))
        .build(config)
        .run();
}

运行时设置执行四个步骤:

  1. Tracing:使用 EnvFilter 初始化 tracing-subscriber,以便 RUST_LOG 环境变量在运行时控制日志详细程度,而无需重新编译
  2. TLS:安装 rustls 加密提供程序,这是 Quicknode 的 gRPC TLS 连接所必需的
  3. Config:读取并解析 Vixen.toml 以获取端点、Token 和流设置
  4. Runtime:使用单个指令 Pipeline 构建 Vixen Runtime,该 Pipeline 将生成的 InstructionParser 连接到 LimitOrderHandler,然后开始流式传输

Pipeline::new 调用将解析器连接到处理程序。生成的 InstructionParser 处理订阅过滤(程序地址、失败交易、投票交易)和反序列化。你的处理程序仅接收 Jupiter Limit Order v2 程序的已解析、类型化的指令数据。

构建并运行应用程序

构建项目:

cargo build

使用配置标志运行它。设置 RUST_LOG=info 以查看处理程序输出和 Vixen 生命周期事件:

RUST_LOG=info cargo run -- --config ./Vixen.toml

预期输出

连接后,你将看到 Vixen 生命周期日志行,随后是 Mainnet 上发生的限价单事件。以下是三种事件类型的示例输出:

2026-04-08T14:32:01.123456Z  INFO jupiter_lo_vixen: New limit order placed - 2026-04-08T14:32:01.123456Z tx=5UjQ...abc maker=7xKm...def making_amount="100 USDC (EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v)" taking_amount="1.5 SOL (So11111111111111111111111111111111111111112)" expired_at=2026-04-15T00:00:00Z
2026-04-08T14:33:15.654321Z  INFO jupiter_lo_vixen: Limit order filled - 2026-04-08T14:33:15.654321Z tx=3Kpx...ghi taker=9mRv...jkl output_mint=So11111111111111111111111111111111111111112 input_amount="100 USDC (EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v)"
2026-04-08T14:35:42.789012Z  INFO jupiter_lo_vixen: Limit order cancelled - 2026-04-08T14:35:42.789012Z tx=8FnY...mno maker=7xKm...def order=4vTq...pqr

每个条目都包含一个 ISO 8601 时间戳、交易签名以及该事件类型的相关账户和金额。Token 数量以人类可读的形式显示,并带有从 Jupiter 的 Token API 解析的代码符号。

扩展你的监控器

你构建的架构非常灵活。以下是一些扩展它的方法:

  • 监控不同的 Jupiter 程序:换入 Jupiter DCA、Perps 或任何其他 Anchor 程序的 IDL。流水线结构保持不变;只有 IDL 文件和处理程序逻辑会改变。
  • 同时观看多个程序:Vixen v0.6.1 支持多个程序订阅。添加第二个指令流水线,在同一个运行时中同时监控 Limit Order v1 和 v2。
  • 添加账户监控:除了指令监控外,添加账户流水线以跟踪特定限价单账户状态的变化。
  • 将事件持久化到数据库:扩展处理程序,将解析后的事件写入 PostgreSQL、ClickHouse 或其他数据存储库进行历史分析。

常见问题解答

什么是 Yellowstone gRPC 以及它如何实现 Solana 上的实时监控?

Yellowstone gRPC 是一种用于流式传输 Solana 区块链数据(包括交易和账户更新)的高性能 gRPC 接口。它允许 Rust 应用程序订阅特定程序(如 Jupiter),从而在不轮询的情况下获得低延迟的限价单种子(feed)。

在 Solana 开发背景下,什么是 Vixen?

Vixen 是一个开源的 Rust 框架,用于在 Yellowstone gRPC 之上构建类型化的数据流水线。它自动处理 gRPC 连接、流过滤和指令反序列化,因此你可以针对类型化的 Rust 结构体编写业务逻辑,而不是原始字节。它的核心抽象是 Parser(解码指令)、Handler(处理指令)和 Pipeline(在托管的 Runtime 中连接前两者)。

我可以使用 Vixen 监控 Jupiter Limit Orders 以外的程序吗?

是的。Vixen 的 proc-macro 适用于任何发布了 IDL 的 Anchor 程序。使用 Anchor CLI 获取 IDL,将其转换为 Codama 格式,并将其传递给 include_vixen_parser!。生成的解析器和流水线结构保持不变。只有 IDL 文件和你的处理程序逻辑会改变。

为什么 TransactionPrefilter 会排除失败的交易?

Vixen v0.6.1 在其 TransactionPrefilter 中引入了一个 failed 字段。将其设置为排除失败交易可确保只有确认、成功执行的交易到达你的处理程序。对于限价单监控器,这可以防止记录从未在链上实际执行的失败成交尝试等事件。

我需要 Quicknode 账户才能使用 Vixen 吗?

Vixen 本身是一个开源框架。但是,它需要一个 Yellowstone gRPC 端点来流式传输数据。Quicknode 的 Yellowstone gRPC 插件为 Solana Mainnet 提供了一个托管的 gRPC 端点,你可以在控制面板的任何 Quicknode Solana 端点上启用它。

什么是 Codama,为什么需要转换步骤?

Codama 是一种 IDL 格式,它提供比 Anchor 原生 IDL 格式更丰富的类型信息。Vixen 的 proc-macro 需要 Codama 格式来生成准确的类型化结构体和枚举。codama convert 命令桥接了这两种格式。这个一次性的转换步骤为任何 Anchor 程序解锁了 Vixen 的编译时代码生成。

总结

你已经构建了一个实时的 Jupiter 限价单监控器,它通过 Yellowstone gRPC 流式传输已确认的交易,使用 Vixen 的 IDL 代码生成将其解析为类型化的 Rust 结构体,并以人类可读的 Token 输出记录下单、成交和取消订单的操作。从这里开始,你可以通过换入任何 Anchor 程序的 IDL,将相同的模式应用于该程序,使 Vixen 成为构建 Solana 数据流水线的通用基础。

资源

有疑问?加入 Quicknode Discord 或关注 @quicknode 获取更新。

我们 ❤️ 反馈!

如果你有任何反馈或对新话题的需求,请告诉我们。我们很乐意听取你的意见。

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

0 条评论

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