关于 jito-bundles
jito-bundles 是面向 Solana 开发者的技能,介绍如何将最多 5 笔交易组成原子执行的 Jito bundle。内容涵盖提交、状态检查、小费策略、落块优化和资金安全检查。
主要功能
- 01构建并提交原子交易 bundle
- 02查询 bundle 与进行中状态
- 03获取 Jito 小费账户并设置小费
- 04通过多区域提交和重试优化落块
- 05使用演示模式和模拟检查流程
适用场景
- 01保护低流动性代币交换免受夹击
- 02原子执行多步套利交易
- 03提交竞争性清算或 NFT 铸造交易
- 04在使用真实资金前模拟 bundle
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ agiprolabs/ claude-trading-skills --skill jito-bundles安装后在 Claude Code 中输入/jito-bundles确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/best_practices.mdbundle_api.mdtip_strategies.mdscripts/build_bundle.pycheck_bundle_status.py详细介绍
Solana 的 Jito Bundle 提交
Jito bundle 允许你提交最多 5 笔 Solana 交易,并以原子方式执行——要么全部在同一个 slot 中落链,要么都不落链。这是 Solana 上防护 MEV 和实现竞争性交易执行的主要机制。Solana 验证者中约有 85% 以上运行 Jito 修改版客户端,因此 bundle 已成为可靠、抗抢跑执行的标准方式。
执行技能 — 安全警告:提交 bundle 会消耗真实 SOL 作为小费。务必先使用
--demo模式进行测试。未经明确确认,切勿使用真实资金提交 bundle。所有脚本和示例默认使用模拟/干运行模式。
何时使用 Bundle
| 场景 | 使用 Bundle? | 原因 |
|---|---|---|
| 交易流动性不足的代币 | 是 | 可防止三明治攻击 |
| 多步骤套利 | 是 | 原子执行可防止部分成交 |
| 清算 | 是 | 竞争激烈——小费决定优先级 |
| 简单的 SOL 转账 | 否 | 优先费更便宜,也足以满足需求 |
| 对时效性要求不高的交易 | 可能 | Bundle 需要支付小费;优先费可能就足够 |
| NFT 铸造/竞争性操作 | 是 | 保证在 slot 内的排序 |
核心概念
Bundle 结构
Jito bundle 是一条 JSON-RPC 请求,包含 1-5 笔以 base58 编码的已签名交易。这些交易会在单个 slot 内按顺序以原子方式执行。
Bundle = [Tx1, Tx2, ..., TxN] (N <= 5)
- All transactions must be signed
- Transactions execute in order: Tx1 → Tx2 → ... → TxN
- If ANY transaction fails, the ENTIRE bundle is dropped
- The tip instruction goes in the LAST transaction (last instruction)
- Bundle has ~2 slots (~800ms) to land before expiry
小费机制
小费是转给 Jito 的 8 个小费账户之一的 SOL 转账。小费可激励验证者纳入你的 bundle。
# Tip is a standard SOL transfer instruction
tip_instruction = transfer(
from_pubkey=your_wallet,
to_pubkey=tip_account, # One of 8 Jito tip accounts
lamports=tip_amount # Tip in lamports (1 SOL = 1e9 lamports)
)
# Add as the LAST instruction of the LAST transaction in the bundle
小费账户通过 getTipAccounts 动态获取。轮换使用这些账户,以分散负载。
Block Engine 端点
Jito 运营着分布在不同地理区域的 block engine。请选择距离你的基础设施最近的端点:
| 区域 | 端点 |
|---|---|
| 纽约 | https://mainnet.block-engine.jito.wtf |
| 阿姆斯特丹 | https://amsterdam.block-engine.jito.wtf |
| 法兰克福 | https://frankfurt.block-engine.jito.wtf |
| 东京 | https://tokyo.block-engine.jito.wtf |
所有端点均通过 HTTPS 的 443 端口接受 JSON-RPC 请求。/api/v1/bundles 路径用于处理 bundle 操作。
API 方法
sendBundle
提交最多包含 5 笔交易的 bundle。
import httpx
BLOCK_ENGINE = "https://mainnet.block-engine.jito.wtf"
payload = {
"jsonrpc": "2.0",
"id": 1,
"method": "sendBundle",
"params": [
[tx1_base58, tx2_base58], # List of base58-encoded signed txs
]
}
resp = httpx.post(f"{BLOCK_ENGINE}/api/v1/bundles", json=payload)
data = resp.json()
bundle_id = data["result"] # UUID string
getBundleStatuses
检查已提交 bundle 的落链状态(每次请求最多可查询 5 个 bundle ID)。
payload = {
"jsonrpc": "2.0",
"id": 1,
"method": "getBundleStatuses",
"params": [[bundle_id]]
}
resp = httpx.post(f"{BLOCK_ENGINE}/api/v1/bundles", json=payload)
statuses = resp.json()["result"]["value"]
# Each status: {bundle_id, status, slot, transactions: [{signature, ...}]}
# status: "Invalid", "Pending", "Failed", "Landed"
getTipAccounts
获取当前的 Jito 小费账户列表。
payload = {
"jsonrpc": "2.0",
"id": 1,
"method": "getTipAccounts",
"params": []
}
resp = httpx.post(f"{BLOCK_ENGINE}/api/v1/bundles", json=payload)
tip_accounts = resp.json()["result"] # List of 8 base58 pubkeys
getInflightBundleStatuses
检查尚未落链的 bundle 状态(正在处理中)。
payload = {
"jsonrpc": "2.0",
"id": 1,
"method": "getInflightBundleStatuses",
"params": [[bundle_id]]
}
resp = httpx.post(f"{BLOCK_ENGINE}/api/v1/bundles", json=payload)
# status: "Pending", "Failed", "Landed"
Bundle 构建模式
受保护交易的典型 bundle:
from solders.transaction import VersionedTransaction
from solders.message import MessageV0
from solders.instruction import Instruction
from solders.system_program import transfer, TransferParams
from solders.pubkey import Pubkey
import random
def build_protected_swap_bundle(
swap_ix: Instruction,
payer: Pubkey,
tip_lamports: int,
tip_accounts: list[str],
recent_blockhash: str,
) -> list[VersionedTransaction]:
"""Build a 1-tx bundle: swap + tip in the same transaction.
For simple swaps, a single-transaction bundle is sufficient.
The tip instruction is appended as the last instruction.
"""
# Pick a random tip account
tip_account = Pubkey.from_string(random.choice(tip_accounts))
# Tip instruction
tip_ix = transfer(TransferParams(
from_pubkey=payer,
to_pubkey=tip_account,
lamports=tip_lamports,
))
# Build transaction with swap + tip
msg = MessageV0.try_compile(
payer=payer,
instructions=[swap_ix, tip_ix],
address_lookup_table_accounts=[],
recent_blockhash=recent_blockhash,
)
tx = VersionedTransaction(msg, [keypair])
return [tx]
小费金额指南
| 场景 | 小费范围(lamports) | 小费范围(SOL) |
|---|---|---|
| 普通交易(低紧急度) | 1,000 - 10,000 | 0.000001 - 0.00001 |
| 普通交易(标准) | 10,000 - 50,000 | 0.00001 - 0.00005 |
| 竞争性操作(套利、清算) | 50,000 - 500,000 | 0.00005 - 0.0005 |
| 高度竞争(NFT 铸造、MEV) | 500,000 - 5,000,000 | 0.0005 - 0.005 |
| 紧急(必须在此 slot 落链) | 5,000,000+ | 0.005+ |
根据近期小费水平动态计算小费:
def calculate_dynamic_tip(
base_tip: int = 10_000,
urgency_multiplier: float = 1.0,
recent_tip_percentile_50: int = 15_000,
) -> int:
"""Calculate tip based on urgency and recent network tips.
Args:
base_tip: Minimum tip in lamports.
urgency_multiplier: 1.0 = normal, 2.0 = urgent, 5.0 = critical.
recent_tip_percentile_50: Median tip from recent bundles.
Returns:
Tip amount in lamports.
"""
dynamic_tip = max(base_tip, int(recent_tip_percentile_50 * urgency_multiplier))
# Cap at 0.01 SOL to prevent accidents
return min(dynamic_tip, 10_000_000)
常见错误及修复方法
| 错误 | 原因 | 修复方法 |
|---|---|---|
Bundle dropped (slot expired) | Bundle 未能在 2 个 slot 内落链 | 使用新的 blockhash 重试;考虑提高小费 |
Transaction simulation failed | Bundle 中的一笔交易在链上会失败 | 分别模拟每笔交易,以找出失败的交易 |
Bundle already processed | Bundle ID 重复 | 重试时出现此情况属于预期;改为检查状态 |
Rate limited | 向 block engine 发送的请求过多 | 暂缓请求;在不同的 block engine 端点之间轮换 |
Invalid transaction | 交易格式错误或未签名 | 确认所有交易均已签名并以 base58 编码 |
Blockhash not found | blockhash 已过期 | 使用 finalized 确认级别调用 getLatestBlockhash |
落块率优化
最大限度提高 Bundle 落块概率的策略:
-
多区域提交:同时将同一个 Bundle 发送到多个区块引擎。最先到达当前领导者的 Bundle 将胜出。
-
使用最新区块哈希:在构建前立即使用
getLatestBlockhash,并设置confirmed确认级别。过期的区块哈希是导致 Bundle 丢弃的首要原因。 -
退避重试:如果 Bundle 在 2-3 秒内没有落块,请使用最新的区块哈希重新构建并提交。切勿使用同一个区块哈希重新提交。
-
提供足够的小费:小费不足的 Bundle 会被降低优先级。监控网络的小费分布,并根据紧急程度提供不低于第 50 百分位数的小费。
-
尽量缩小 Bundle:交易越少 = 模拟时间越短 = 落块率越高。尽可能使用单笔交易的 Bundle。
async def submit_with_retry(
bundle_txs: list[str],
endpoints: list[str],
max_retries: int = 3,
) -> str | None:
"""Submit bundle to multiple endpoints with retry logic.
Returns bundle_id if submitted, None if all retries exhausted.
"""
for attempt in range(max_retries):
# Submit to all endpoints in parallel
async with httpx.AsyncClient() as client:
tasks = [
client.post(
f"{ep}/api/v1/bundles",
json={
"jsonrpc": "2.0", "id": 1,
"method": "sendBundle",
"params": [bundle_txs],
},
timeout=5.0,
)
for ep in endpoints
]
# Process first successful response
for resp in asyncio.as_completed(tasks):
result = (await resp).json()
if "result" in result:
return result["result"]
# Wait before retry with fresh blockhash
await asyncio.sleep(0.5 * (attempt + 1))
return None
安全检查清单(执行)
使用真实资金提交任何 Bundle 前:
- 已通过
simulateTransaction单独模拟所有交易 - 已确认小费金额合理(没有误用 SOL 而非 lamports)
- 已确认区块哈希是最新的(创建时间不到 60 秒)
- 已确认所有交易均已正确签名
- 已检查钱包余额足以支付所有交易费用和小费
- 已先在 devnet 或 --demo 模式下测试
- 已设置小费上限,以防止意外多付
文件
参考资料
references/bundle_api.md— 完整的 JSON-RPC API 参考,包含请求/响应架构和错误代码references/tip_strategies.md— 小费计算策略、动态小费和成本优化references/best_practices.md— Bundle 构建模式、落块率优化和常见问题
脚本
scripts/build_bundle.py— 构建带小费指令的 Bundle;--demo模式仅构建,不会提交scripts/check_bundle_status.py— 检查 Bundle 状态并获取小费账户;--demo模式使用模拟响应