本文介绍了如何使用Jito Bundles在Solana上实现多个交易的原子性执行。通过创建一个Rust应用程序,演示了如何将多个交易打包在一起,并添加小费以激励验证者。文章详细介绍了项目设置、代码实现、运行应用以及验证bundle的过程,帮助开发者了解和使用Jito Bundles。
随着 Solana 越来越受欢迎,开发者需要有效的工具来确保他们的交易能够可靠地处理。Jito Bundles 通过将多个交易捆绑在一起,并在同一个区块中原子性地执行,从而满足了这一需求。在本指南中,我将引导你创建一个 Rust 应用程序,使用 Jito Rust SDK 发送 Jito Bundles。
在本指南结束时,你将构建一个可运行的 Rust 应用程序,该应用程序可以:
让我们开始吧!
在我们开始之前,你需要:
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-client
和 solana-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
好的。让我们编写我们的脚本!
接下来,让我们实现我们的主应用程序。
打开 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
函数。将以下行添加到你的 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
注释替换为以下代码:
// 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
注释替换为以下代码:
// 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 — recent block‑hash
let blockhash = solana_rpc.get_latest_blockhash()?;
println!("Latest blockhash: {blockhash}");
我们获取最新的区块哈希,以确保我们的交易有效/新鲜,并且可以由 Solana 网络处理。
将 // 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…");
在此步骤中:
现在我们有一个编码签名交易的向量,我们可以将我们的 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。
最后,我们需要确认我们的 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
目录中创建一个可执行文件。如果你正确地按照我们的步骤操作,你将看不到任何错误。如果确实看到错误,请对照本指南中提供的代码段仔细检查你的代码,并按照终端输出查找任何错误。
在运行应用程序之前,请仔细检查你是否已:
secret.json
.env
文件中使用你的 QuickNode endpoint 设置 SOLANA_RPC
和 JITO_ENDPOINT
要运行应用程序:
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 包含详细的日志记录,因此你可以查看每个步骤的状态。如果遇到任何错误,请检查错误消息以获取有关出错原因的提示。
注意:Jito bundle 浏览器可能需要一分钟才能使用你的 Bundle 更新,因此请等待一分钟左右,然后点击你的链接。
你应该会看到类似这样的内容:
该 Bundle 应按顺序显示所有五个交易。第五个交易应包括带有 SOL 余额转移的小费指令。你可以在上面的示例中看到这一点。你还可以通过点击交易右上角的 ⎘ 图标(或在 Solana 浏览器 中浏览你的钱包地址)单独打开每个交易。你应该看到所有五个交易都具有相同的时间戳和区块:
你应该能够导航到五个交易中的第一个,并在交易详情中看到“lil jit demo transaction # 1”:
你应该能够导航到第 5 笔交易,并在交易详情中看到“lil jit demo transaction # 5”以及我们的小费转移:
这表明:
很酷,对吧?做得好!🎉
Jito Bundles 是 Solana 开发者需要保证交易排序和原子执行的强大工具。在以下情况下,Jito Bundles 特别有价值:
在使用 Jito Bundles 时,请记住,最低小费为 1,000 lamports,但在高需求期间,你可能需要更高的交易费用才能使你的 bundle 获得优先处理 -- 你可以使用我们的 getTipFloor endpoint 获取当前的最小小费。
无论你是构建 DeFi 协议、交易机器人还是复杂的 dApp,捆绑交易的能力都可以显着提高应用程序的可靠性和用户体验。
此示例使用简单的 memo 交易,但你可以扩展它以构建更复杂的应用程序:
问题 | 可能的解决方案 |
---|---|
Bundle 未及时确认 | 增加 POLL_TIMEOUT_SECS 常量 |
Bundle 在链上失败 | 检查交易有效性,增加小费金额或验证帐户余额 |
Bundle 不在浏览器上 | 等待几分钟,让 Jito Explorer 更新 |
如果你有任何反馈或对新主题的请求,请告诉我们。我们很乐意听到你的声音。
- 原文链接: quicknode.com/guides/sol...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!