mcpField
J
jito-bundles

jito-bundles

github.com/agiprolabs開発456439 回インストール8 週間前に更新
リポジトリ

jito-bundles について

jito-bundlesは、Solana開発者向けに、最大5件のトランザクションを原子的なJito bundleにまとめる方法を解説するスキルです。送信、状態確認、チップ戦略、着地率の改善、資金の安全確認を扱います。

主な機能

  1. 01原子的なトランザクションbundleの構築と送信
  2. 02bundleと処理中ステータスの確認
  3. 03Jitoのチップアカウント取得とチップ設定
  4. 04複数リージョン送信と再試行による着地率の改善
  5. 05デモモードとシミュレーション確認の利用

活用シーン

  1. 01流動性の低いトークン交換をサンドイッチ攻撃から保護
  2. 02複数段階の裁定取引を原子的に実行
  3. 03競争の激しい清算やNFTミントのトランザクション送信
  4. 04実資金を使う前のbundleシミュレーション

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/agiprolabs/claude-trading-skills --skill jito-bundles

インストール後、Claude Code で/jito-bundlesを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
best_practices.md
bundle_api.md
tip_strategies.md
scripts/
build_bundle.py
check_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,0000.000001 - 0.00001
通常のスワップ(標準)10,000 - 50,0000.00001 - 0.00005
競争性の高いアクション(アービトラージ、清算)50,000 - 500,0000.00005 - 0.0005
非常に競争性の高いアクション(NFT ミント、MEV)500,000 - 5,000,0000.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 を使用してください

着地率の最適化

バンドルの着地確率を最大化する戦略:

  1. 複数リージョンへの送信:同じバンドルを複数のブロックエンジンに同時に送信します。現在のリーダーに最初に到達したものが成功します。

  2. 最新のブロックハッシュ:構築の直前に、confirmed コミットメントで getLatestBlockhash を使用します。古いブロックハッシュは、バンドルがドロップされる最大の原因です。

  3. バックオフを伴う再試行:バンドルが2~3秒以内に着地しない場合は、最新のブロックハッシュで再構築して再送信します。同じブロックハッシュで再送信しないでください。

  4. 適切なチップ:チップが少なすぎるバンドルは優先度が下がります。ネットワークのチップ分布を監視し、緊急度に応じて50パーセンタイル以上のチップを設定してください。

  5. バンドルサイズの最小化:トランザクション数が少ないほどシミュレーション時間が短くなり、着地率が高まります。可能な場合は、単一トランザクションのバンドルを使用してください。

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 モードではモックレスポンスを使用します

コメント