Jito Bundles:使用 Rust 打包 Solana 交易

本文介绍了如何使用Jito Bundles在Solana上实现多个交易的原子性执行。通过创建一个Rust应用程序,演示了如何将多个交易打包在一起,并添加小费以激励验证者。文章详细介绍了项目设置、代码实现、运行应用以及验证bundle的过程,帮助开发者了解和使用Jito Bundles。

Jito Bundles 项目概述

随着 Solana 越来越受欢迎,开发者需要有效的工具来确保他们的交易能够可靠地处理。Jito Bundles 通过将多个交易捆绑在一起,并在同一个区块中原子性地执行,从而满足了这一需求。在本指南中,我将引导你创建一个 Rust 应用程序,使用 Jito Rust SDK 发送 Jito Bundles。

在本指南结束时,你将构建一个可运行的 Rust 应用程序,该应用程序可以:

  • 创建一系列带有 memo 指令的交易
  • 将它们与小费捆绑在一起,以激励验证者
  • 将 bundle 发送到 Jito 的区块引擎
  • 验证 bundle 是否已成功处理

让我们开始吧!

前提条件

在我们开始之前,你需要:

  • 在你的系统上安装 Rust
  • 拥有一个启用了 Solana Mainnet endpoint 的 QuickNode 帐户Lil' JIT add-on
  • 基本熟悉 Solana 开发概念
  • 一个 Solana 密钥对,其中包含少量 SOL(约 0.01 SOL),用于交易费用和小费

了解 Jito Bundles

Jito Bundles 是一项强大的功能,可以在 Solana 上顺序且原子性地执行多个交易。以下是它们的特别之处:

功能 描述
顺序执行 交易按照指定的精确顺序执行
原子执行 bundle 中的所有交易都在同一插槽中执行
全有或全无 如果任何交易失败,则所有交易都不会提交到链上
Bundle 大小 每个 bundle 最多 5 个交易
MEV 保护 帮助保护用户免受抢先交易和其他 MEV 活动的影响

Bundles 对于需要按特定顺序执行多个交易的复杂操作特别有用,例如套利、清算或任何超过 Solana 单笔交易计算预算的多步骤流程。

QuickNode 的 Lil' JIT - JITO Bundles & Transactions Add-on 允许你充分利用 Jito 和 Jito Bundles 的强大功能。让我们看看如何在 Rust 中使用它!

项目设置

让我们从创建一个新的 Rust 项目开始:

cargo new jito-bundle-example

打开项目目录:

cd jito-bundle-example

将你的 Cargo.toml 文件的内容替换为:

[package]
name = "jito-bundle-example"
version = "0.1.0"
edition = "2021"

[dependencies]
tokio            = { version = "1.37", features = ["full"] }
anyhow           = "1"
serde_json       = "1"
base64           = "0.22.1"
jito-sdk-rust    = "0.2.0"
solana-client    = "2.2.7"
solana-sdk       = "2.2.2"
bincode          = "1.3.3"
dotenv           = "0.15.0"

这为我们的项目设置了所有必需的依赖项:

  • tokio - 用于异步运行时
  • anyhow - 用于错误处理
  • base64 - 用于编码/解码交易数据
  • jito-sdk-rust - 用于与 Jito 的区块引擎交互
  • solana-clientsolana-sdk - 用于 Solana 区块链交互
  • bincode - 用于序列化交易
  • dotenv - 用于加载环境变量

环境变量

现在,在项目根目录中创建一个名为 .env 的文件,以存储你的 endpoint:

SOLANA_RPC=https://your-quicknode-endpoint.quiknode.pro/your-api-key
JITO_ENDPOINT=https://your-quicknode-endpoint.quiknode.pro/your-api-key

你的 Jito Endpoint 将与你启用 Lil' JIT add-on 的 Solana RPC endpoint 相同。SOLANA_RPC endpoint 和 JITO_ENDPOINT 通常是相同的,但如果你想分隔 RPC 和 Jito 请求,你可以使用不同的 endpoint。

确保你的 Jito endpoint 没有尾部斜杠。

密钥对设置

你将需要一个包含约 0.01 SOL 的纸钱包来测试此演示(并支付 bundle 小费)。如果你没有,可以使用以下命令创建一个:

solana-keygen new -o secret.json -s --no-bip39-passphrase

你可以通过运行以下命令获取新钱包的地址:

solana address -k secret.json

在继续操作之前,请确保向该钱包发送约 0.01 SOL。你可以通过运行以下命令来验证你的主网余额:

solana balance -um -k secret.json

好的。让我们编写我们的脚本!

代码实现

接下来,让我们实现我们的主应用程序。

定义 Import 语句

打开 src/main.rs 并添加以下内容:

use anyhow::{anyhow, Result};
use base64::{engine::general_purpose, Engine as _};
use jito_sdk_rust::JitoJsonRpcSDK;
use serde_json::json;
use solana_client::rpc_client::RpcClient;
use solana_sdk::{
    instruction::{AccountMeta, Instruction},
    pubkey::Pubkey,
    signature::{Keypair, Signer},
    system_instruction,
    transaction::Transaction,
};
use std::{fs::File, io::BufReader, str::FromStr, env};
use tokio::time::{sleep, Duration};

这些导入引入了我们将在整个应用程序中使用的必要库。

定义常量

接下来,让我们定义一些将在整个代码中使用的常量。将以下行添加到你的 src/main.rs 文件中:

/// ---------- constants ----------------------------------------------------
const NUMBER_TRANSACTIONS: usize = 5;
const MIN_JITO_TIP_LAMPORTS: u64 = 1_000;  // 1_000 lamports ≈ 0.000001 SOL
const POLL_INTERVAL_SECS: u64 = 5;
const POLL_TIMEOUT_SECS: u64 = 60;

这些将帮助管理我们的 bundle 大小、小费金额以及我们轮询 bundle 状态的频率。由于我们只是在这里进行测试,并且不想过度消费小费,因此我们使用 1,000 lamports(0.000001 SOL)的静态小费金额。在生产环境中,你可能需要使用 getTipFloor endpoint 获取当前的最小小费金额,并相应地设置你的小费。

定义辅助函数

接下来,让我们创建几个辅助函数来加载我们的密钥对并轮询 bundle 状态。将以下行添加到你的 src/main.rs 文件中:

/// ---------- helper utilities --------------------------------------------
fn load_keypair(path: &str) -> Result<Keypair> {
    let reader = BufReader::new(File::open(path)?);
    let bytes: Vec<u8> = serde_json::from_reader(reader)?;
    Ok(Keypair::from_bytes(&bytes)?)
}

async fn poll_bundle_status(
    sdk: &JitoJsonRpcSDK,
    bundle_id: &str,
) -> Result<()> {
    let start = tokio::time::Instant::now();
    loop {
        let resp = sdk
            .get_in_flight_bundle_statuses(vec![bundle_id.to_string()])
            .await?;

        let status = resp["result"]["value"][0]["status"]
            .as_str()
            .unwrap_or("Unknown");
        match status {
            "Landed" => return Ok(()),
            "Failed" => return Err(anyhow!("bundle failed on‑chain")),
            _ => {
                if start.elapsed() > Duration::from_secs(POLL_TIMEOUT_SECS) {
                    return Err(anyhow!("bundle not confirmed in time"));
                }
                sleep(Duration::from_secs(POLL_INTERVAL_SECS)).await;
            }
        }
    }
}
  • load_keypair 函数从 JSON 文件加载密钥对(我们将使用它来加载我们的 secret.json 文件)。
  • poll_bundle_status 函数检查我们的 bundle 状态,直到它成功或失败。

Main 函数

最后,让我们创建我们的入口点 main 函数。将以下行添加到你的 src/main.rs 文件中:

/// ---------- main logic ---------------------------------------------------
#[tokio::main]
async fn main() -> Result<()> {
    // STEP 1 — local key & RPC handles

    // STEP 2 — choose a Jito tip account

    // STEP 3 — recent block‑hash

    // STEP 4 — build & sign five transactions

    // STEP 5 — send the bundle

    // STEP 6 — confirm inclusion

    Ok(())
}

这是我们将实现创建和发送 Jito Bundle 逻辑的 main 函数。我们使用 tokio 运行时来处理异步操作。让我们填写这些步骤。

Step 1: 加载密钥对和 RPC 客户端

// STEP 1 注释替换为以下代码:

    // STEP 1 — local key & RPC handles
    let payer = load_keypair("secret.json")?;
    println!("Using wallet: {}", payer.pubkey());

    dotenv::dotenv().ok();
    let jito_endpoint = env::var("JITO_ENDPOINT").expect("JITO_ENDPOINT must be set in .env file");
    let solana_rpc = env::var("SOLANA_RPC").expect("SOLANA_RPC must be set in .env file");

    let solana_rpc = RpcClient::new(solana_rpc.as_str());
    let jito_sdk = JitoJsonRpcSDK::new(jito_endpoint.as_str(), None);

此代码:

  • secret.json 文件加载我们的本地密钥对
  • 使用我们在 .env 文件中设置的 endpoint 初始化 Solana RPC 客户端和 Jito SDK 客户端

Step 2: 获取 Jito 小费帐户

// STEP 2 注释替换为以下代码:

    // STEP 2 — choose a Jito tip account
    let random_tip_account = jito_sdk.get_random_tip_account().await?;
    let jito_tip_account = Pubkey::from_str(&random_tip_account)?;
    println!("Selected tip account: {random_tip_account}");

我们随机选择一个 Jito 的小费帐户来发送我们的小费。这激励验证者将我们的 bundle 包含在他们的区块中。这允许跨多个处理管道并行 bundle 执行和支付。

Step 3: 获取最近的区块哈希

// STEP 3 注释替换为以下代码:

    // STEP 3 — recent block‑hash
    let blockhash = solana_rpc.get_latest_blockhash()?;
    println!("Latest blockhash: {blockhash}");

我们获取最新的区块哈希,以确保我们的交易有效/新鲜,并且可以由 Solana 网络处理。

Step 4: 构建和签名交易

// STEP 4 注释替换为以下代码:

    // STEP 4 — build & sign five transactions
    let memo_program = Pubkey::from_str("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr")?;
    let mut encoded: Vec<String> = Vec::with_capacity(NUMBER_TRANSACTIONS);

    for i in 0..NUMBER_TRANSACTIONS {
        let memo_ix = Instruction::new_with_bytes(
            memo_program,
            format!("lil jit demo transaction # {}", i + 1).as_bytes(),
            vec![AccountMeta::new(payer.pubkey(), true)],
        );

        let mut ixs = vec![memo_ix];

        // For the last transaction, add a tip instruction
        if i == NUMBER_TRANSACTIONS - 1 {
            ixs.push(system_instruction::transfer(
                &payer.pubkey(),
                &jito_tip_account,
                MIN_JITO_TIP_LAMPORTS,
            ));
        }

        let mut tx = Transaction::new_with_payer(&ixs, Some(&payer.pubkey()));
        tx.sign(&[&payer], blockhash);
        let bytes = bincode::serialize(&tx)?;
        encoded.push(general_purpose::STANDARD.encode(bytes));
    }

    println!("Signed and encoded all {NUMBER_TRANSACTIONS} transactions…");

在此步骤中:

  1. 我们创建五个交易(取决于我们在常量中定义的数量),每个交易都有一个唯一的 memo 指令
  2. 对于最后一笔交易,我们添加了一个小费指令来激励验证者
  3. 我们使用我们的付款人密钥对对每一个交易进行签名
  4. 我们序列化并base64编码每个交易,并将每个编码的交易推送到一个向量中

Step 5: 发送 Bundle

现在我们有一个编码签名交易的向量,我们可以将我们的 bundle 发送到 Jito 的区块引擎。

// STEP 5 注释替换为以下代码:

    // STEP 5 — send the bundle
    let params = json!([\
        encoded.clone(),\
        { "encoding": "base64" }\
    ]);
    let resp = jito_sdk.send_bundle(Some(params), None).await?;
    let bundle_id = resp["result"]
        .as_str()
        .ok_or_else(|| anyhow!("no bundle id in response"))?;

    println!("Bundle submitted: {bundle_id}");

我们使用 send_bundle 将我们的交易 bundle 发送到 Jito 的区块引擎,并从响应中提取 bundle ID。

Step 6: 确认包含

最后,我们需要确认我们的 bundle 已包含在一个区块中。将 // STEP 6 注释替换为以下代码:

    // STEP 6 — confirm inclusion
    poll_bundle_status(&jito_sdk, bundle_id).await?;
    println!("Bundle landed! View it at https://explorer.jito.wtf/bundle/{bundle_id}");

    Ok(())

由于我们已经创建了我们的轮询函数,我们所要做的就是使用我们的 Jito SDK 和 bundle ID 调用它。如果 bundle 得到确认,我们会打印一个链接以在 Jito 的浏览器中查看该 bundle。

运行应用程序

构建应用程序

要构建应用程序,请在你的终端中运行以下命令:

cargo build

这将编译你的 Rust 代码并在 target 目录中创建一个可执行文件。如果你正确地按照我们的步骤操作,你将看不到任何错误。如果确实看到错误,请对照本指南中提供的代码段仔细检查你的代码,并按照终端输出查找任何错误。

运行应用程序

在运行应用程序之前,请仔细检查你是否已:

  1. 创建或复制 Solana 密钥对到你的项目目录中的 secret.json
  2. 在你的 .env 文件中使用你的 QuickNode endpoint 设置 SOLANA_RPCJITO_ENDPOINT
  3. 使用少量 SOL(约 0.01 SOL)资助你的密钥对

要运行应用程序:

cargo run

如果成功,你应该会看到类似于以下的输出:

Using wallet: RUA7zS3PXVtW2VCmFGtfv5q6AdusbpVGy8HTjVjBAAxR
Selected tip account: HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe
Latest blockhash: CghMwVqLLdKrdgdCKDbTtRgdRhj9BfCBZxL8n87QNB9C
Signed and encoded all 5 transactions…
Bundle submitted: c4fb0940406d3fead71e29b9f87d2273ab6a743f3da1bf4aeeb73db3521685b0
Bundle landed! View it at https://explorer.jito.wtf/bundle/c4fb0940406d3fead71e29b9f87d2273ab6a743f3da1bf4aeeb73db3521685b0

SDK 包含详细的日志记录,因此你可以查看每个步骤的状态。如果遇到任何错误,请检查错误消息以获取有关出错原因的提示。

验证你的 Bundle

注意:Jito bundle 浏览器可能需要一分钟才能使用你的 Bundle 更新,因此请等待一分钟左右,然后点击你的链接。

你应该会看到类似这样的内容:

通过 Jito 在 QuickNode 上提交的已确认所有交易的成功 Solana 交易 bundle

该 Bundle 应按顺序显示所有五个交易。第五个交易应包括带有 SOL 余额转移的小费指令。你可以在上面的示例中看到这一点。你还可以通过点击交易右上角的 ⎘ 图标(或在 Solana 浏览器 中浏览你的钱包地址)单独打开每个交易。你应该看到所有五个交易都具有相同的时间戳和区块:

通过使用 QuickNode 的 Lil' JIT Add-on 的 Jito Bundle 发送的所有成功 Solana 交易的 Solana 浏览器视图

你应该能够导航到五个交易中的第一个,并在交易详情中看到“lil jit demo transaction # 1”:

第 1 笔交易 - Solana Explorer

你应该能够导航到第 5 笔交易,并在交易详情中看到“lil jit demo transaction # 5”以及我们的小费转移:

第 5 笔交易 - Solana Explorer

这表明:

  • 所有交易都包含在同一区块中
  • 所有交易都按照发送的顺序执行
  • 所有交易都成功
  • 最终交易包括向小费帐户的小费转移

很酷,对吧?做得好!🎉

总结

Jito Bundles 是 Solana 开发者需要保证交易排序和原子执行的强大工具。在以下情况下,Jito Bundles 特别有价值:

  1. 需要顺序执行:当交易执行的顺序至关重要时
  2. 需要原子执行:当多个交易必须全部成功或全部失败时
  3. 需要 MEV 保护:防止抢先交易和其他形式的 MEV
  4. 复杂操作:对于需要超过计算限制的多个交易的操作

在使用 Jito Bundles 时,请记住,最低小费为 1,000 lamports,但在高需求期间,你可能需要更高的交易费用才能使你的 bundle 获得优先处理 -- 你可以使用我们的 getTipFloor endpoint 获取当前的最小小费。

无论你是构建 DeFi 协议、交易机器人还是复杂的 dApp,捆绑交易的能力都可以显着提高应用程序的可靠性和用户体验。

继续构建

此示例使用简单的 memo 交易,但你可以扩展它以构建更复杂的应用程序:

  1. DeFi 套利:捆绑交易以跨多个 DEX 执行套利
  2. NFT 市场运营:将 USDC 兑换为 SOL 与立即购买交易捆绑在一起,以有效地使用 USDC 购买 NFT
  3. 复杂治理行动:捆绑多个治理提案交易

解决常见问题

问题 可能的解决方案
Bundle 未及时确认 增加 POLL_TIMEOUT_SECS 常量
Bundle 在链上失败 检查交易有效性,增加小费金额或验证帐户余额
Bundle 不在浏览器上 等待几分钟,让 Jito Explorer 更新

附加资源

我们 ❤️ 反馈!

如果你有任何反馈或对新主题的请求,请告诉我们。我们很乐意听到你的声音。

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

0 条评论

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