jito-bundles について
jito-bundlesは、Solana開発者向けに、最大5件のトランザクションを原子的なJito bundleにまとめる方法を解説するスキルです。送信、状態確認、チップ戦略、着地率の改善、資金の安全確認を扱います。
主な機能
- 01原子的なトランザクションbundleの構築と送信
- 02bundleと処理中ステータスの確認
- 03Jitoのチップアカウント取得とチップ設定
- 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 バンドル送信
Jito バンドルを使うと、最大 5 件の Solana トランザクションをアトミックに送信できます。すべて同じスロットに含まれるか、どれも含まれないかのいずれかです。これは、Solana における MEV 保護と競争力のあるトランザクション実行の主要な仕組みです。Solana バリデーターの約 85% 以上が Jito 改変版クライアントを実行しているため、バンドルは信頼性が高く、フロントランに耐性のある実行の標準となっています。
実行スキル — 安全に関する警告:バンドルの送信では、チップとして実際の SOL を消費します。必ず最初に
--demoモードでテストしてください。明示的な確認なしに、実資金でバンドルを送信しないでください。すべてのスクリプトと例では、シミュレーション/ドライランをデフォルトにしてください。
バンドルを使用する場面
| シナリオ | バンドルを使用する? | 理由 |
|---|---|---|
| 流動性の低いトークンのスワップ | はい | サンドイッチ攻撃を防ぎます |
| 複数ステップのアービトラージ | はい | アトミック実行により部分約定を防ぎます |
| 清算 | はい | 競争性が高く、チップによって優先順位が決まります |
| 単純な SOL の送金 | いいえ | 優先手数料のほうが安価で、十分です |
| 時間に左右されないスワップ | 場合によります | バンドルにはチップが必要です。優先手数料で十分な場合があります |
| NFT ミント/競争性の高いアクション | はい | スロット内の順序を保証します |
主な概念
バンドルの構成
Jito バンドルは、1~5 件の base58 エンコードされた署名済みトランザクションを含む JSON-RPC リクエストです。トランザクションは、単一のスロット内で順番に、かつアトミックに実行されます。
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 送金です。チップによって、バリデーターがバンドルを含めるよう促します。
# 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 で動的に取得します。負荷を分散するため、アカウントをローテーションしてください。
ブロックエンジンのエンドポイント
Jito は、地理的に分散したブロックエンジンを運用しています。インフラに最も近いものを選択してください。
| 地域 | エンドポイント |
|---|---|
| ニューヨーク | https://mainnet.block-engine.jito.wtf |
| アムステルダム | https://amsterdam.block-engine.jito.wtf |
| フランクフルト | https://frankfurt.block-engine.jito.wtf |
| 東京 | https://tokyo.block-engine.jito.wtf |
すべてのエンドポイントは、ポート 443 で HTTPS 経由の JSON-RPC を受け付けます。/api/v1/bundles パスでバンドル操作を処理します。
API メソッド
sendBundle
最大 5 件のトランザクションを含むバンドルを送信します。
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
送信したバンドルの着地ステータスを確認します(1 リクエストあたり最大 5 件のバンドル 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
まだ着地していない(処理中の)バンドルのステータスを確認します。
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"
バンドル構築パターン
保護されたスワップにおける一般的なバンドルの例:
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 |
| 緊急(このスロットに必ず含める必要がある) | 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) | バンドルが 2 スロット以内に着地しませんでした | 新しいブロックハッシュで再試行してください。チップの増額も検討してください |
Transaction simulation failed | バンドル内のトランザクションがオンチェーンで失敗します | 各トランザクションを個別にシミュレーションして、失敗するものを特定してください |
Bundle already processed | バンドル ID が重複しています | 再試行時には想定される動作です。代わりにステータスを確認してください |
Rate limited | ブロックエンジンへのリクエストが多すぎます | 間隔を空けてください。ブロックエンジンのエンドポイント間でローテーションしてください |
Invalid transaction | トランザクションの形式が不正か、署名されていません | すべてのトランザクションに署名され、base58 エンコードされていることを確認してください |
Blockhash not found | ブロックハッシュが古くなっています | finalized コミットメントで getLatestBlockhash を使用してください |
着地率の最適化
バンドルの着地確率を最大化する戦略:
-
複数リージョンへの送信:同じバンドルを複数のブロックエンジンに同時に送信します。現在のリーダーに最初に到達したものが成功します。
-
最新のブロックハッシュ:構築の直前に、
confirmedコミットメントでgetLatestBlockhashを使用します。古いブロックハッシュは、バンドルがドロップされる最大の原因です。 -
バックオフを伴う再試行:バンドルが2~3秒以内に着地しない場合は、最新のブロックハッシュで再構築して再送信します。同じブロックハッシュで再送信しないでください。
-
適切なチップ:チップが少なすぎるバンドルは優先度が下がります。ネットワークのチップ分布を監視し、緊急度に応じて50パーセンタイル以上のチップを設定してください。
-
バンドルサイズの最小化:トランザクション数が少ないほどシミュレーション時間が短くなり、着地率が高まります。可能な場合は、単一トランザクションのバンドルを使用してください。
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
安全チェックリスト(実行)
実際の資金を使用してバンドルを送信する前に:
-
simulateTransactionで各トランザクションを個別にシミュレーションした - チップの金額が妥当であることを確認した(誤ってSOLではなくlamportsを指定していない)
- ブロックハッシュが新しいことを確認した(60秒以内)
- すべてのトランザクションに適切に署名されていることを確認した
- ウォレット残高がすべてのトランザクションコストとチップをカバーできることを確認した
- 最初にdevnetまたは--demoモードでテストした
- 誤って過払いしないよう、チップの上限を設定した
ファイル
リファレンス
references/bundle_api.md— リクエスト/レスポンスのスキーマとエラーコードを含む、完全なJSON-RPC APIリファレンスreferences/tip_strategies.md— チップ計算戦略、動的なチップ設定、コスト最適化references/best_practices.md— バンドル構築パターン、着地率の最適化、よくある落とし穴
スクリプト
scripts/build_bundle.py— チップ命令を含むバンドル構築。--demoモードでは構築のみ行い、送信しませんscripts/check_bundle_status.py— バンドルのステータス確認とチップアカウントの取得。--demoモードではモックレスポンスを使用します