JitoとKoraを使用したガスレストランザクションバンドルの実装方法

最終更新日: 2025-01-09

構築するもの

完全なトランザクションフローガイドでは、Koraを使用してガスレストランザクションを作成する方法を学びました。しかし、単一のトランザクションでは不十分な場合や、Kora支払い命令を含めるために単一のトランザクションに十分なスペースがない場合など、多くのシナリオが存在します。このガイドでは、Koraを使用してトランザクションのバンドルに署名し、Solana Mainnet上でのアトミックな実行のためにJitoのブロックエンジンに送信する方法を示すデモを構築します。Koraサーバーは、Jitoチップとすべてのトランザクション手数料を支払います。

最終的な結果は、動作するJitoバンドルシステムになります:

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
KORA JITO BUNDLE DEMO
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[1/4] Initializing clients
Kora RPC: http://localhost:8080/
Solana RPC: https://api.mainnet-beta.solana.com
[2/4] Setting up keypairs
Sender: BYJVBqQ2xV9GECc84FeoPQy2DpgoonZQFQu97MMWTbBc
Kora signer address: 3Z1Ef7YaxK8oUMoi6exf7wYZjZKWJJsrzJXSt1c3qrDE
[3/4] Creating bundle transactions
Blockhash: 7HZUaMqV...
Tip account: 96gYZGLn...
Transaction 1: Kora Memo "Bundle tx #1"
Transaction 2: Kora Memo "Bundle tx #2"
Transaction 3: Kora Memo "Bundle tx #3"
Transaction 4: Kora Memo "Bundle tx #4" + Jito tip
4 transactions created for bundle
[4/4] Signing and sending bundle
Bundle submitted to Jito block engine
Bundle UUID: 8f4a3b2c-1d5e-6f7a-8b9c-0d1e2f3a4b5c
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
SUCCESS: Bundle confirmed on Solana
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Bundle UUID:
8f4a3b2c-1d5e-6f7a-8b9c-0d1e2f3a4b5c

構築方法は次のとおりです。

前提条件

このチュートリアルを始める前に、以下を確認してください:

Kora v2.2.0ベータ版

重要: このガイドはKora v2.2.0ベータ版を必要とします。リリースはこちらで確認できます。これはプレリリース版であり、バグが含まれる可能性があります。

cargo install kora-cli@2.2.0-beta.7

Jitoバンドルの基本

Solanaでは、トランザクション内のすべての命令はアトミックです。つまり、1つの命令が失敗すると、トランザクション全体が失敗します。バンドルは、最大5つのトランザクションをアトミックかつ順次実行できるツールです。バンドルはチップによってインセンティブ化されており、チップが高いほど優先度が高くなります。

このガイドは、Jitoバンドルに関する基本的な理解と経験があることを前提としています。

プロジェクト構造

このデモのサンプルコードは、 Koraの例にあります:

jito-bundles/
├── client/
│ ├── src/
│ │ └── index.ts # Bundle demo implementation
│ └── package.json
├── server/
│ ├── kora.toml # Kora configuration with bundles enabled
│ └── signers.toml # Signer configuration
└── scripts/
└── start-kora.sh # Server startup script

koraリポジトリをクローンして、jito-bundlesディレクトリに移動してください:

git clone https://github.com/solana-foundation/kora.git
cd kora/examples/jito-bundles

Koraサーバー設定

kora.toml

バンドルサポートの主要な設定:

[kora]
rate_limit = 100
[kora.auth]
api_key = "kora_facilitator_api_key_example"
[kora.enabled_methods]
sign_bundle = true
sign_and_send_bundle = true
estimate_bundle_fee = true
get_blockhash = true
get_config = true
get_payer_signer = true
[validation]
max_allowed_lamports = 1000000
max_signatures = 10
price_source = "Mock"
allowed_programs = [
"11111111111111111111111111111111", # System Program
"MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr", # Memo Program
]
[validation.fee_payer_policy.system]
allow_transfer = true # Required for Jito tip transfers
[validation.price]
type = "free" # No payment required for this demo
[kora.bundle]
enabled = true
[kora.bundle.jito]
block_engine_url = "https://mainnet.block-engine.jito.wtf"

バンドルサポートに重要な設定:

  • sign_bundle / sign_and_send_bundle — バンドルRPCメソッドを有効化
  • allow_transfer = true — Koraの署名者がJitoチップを支払うため、転送権限が必要
  • bundle.enabled = true — バンドル機能のマスタースイッチ
  • このデモでは公開メインネットのブロックエンジンURLを使用しています。本番環境では、プライベートブロックエンジンURLを使用してください。

signers.toml

[signer_pool]
strategy = "round_robin"
[[signers]]
name = "main_signer"
type = "memory"
private_key_env = "KORA_PRIVATE_KEY"

.env.example.envにリネームし、KORA_PRIVATE_KEY 環境変数をメインネットの秘密鍵に設定してください。署名者ウォレットには、以下の支払いのためにメインネット上のSOLが必要です:

  1. すべてのバンドルトランザクションの手数料
  2. Jitoチップ(最小1,000 lamport)

重要:このガイドでは、Solanaメインネット上でのJitoチップの使用方法を示しています。チップは返金不可です。

サーバーの起動

server/ディレクトリから:

kora --config kora.toml --rpc-url https://api.mainnet-beta.solana.com rpc start --signers-config signers.toml

または提供されているスクリプトを使用してください。server/ディレクトリから:

../scripts/start-kora.sh

クライアント実装

インポートから始めて、クライアント実装を段階的に説明します。

インポートと設定

import { KoraClient } from "@solana/kora";
import {
createNoopSigner,
address,
getBase64EncodedWireTransaction,
partiallySignTransactionMessageWithSigners,
Blockhash,
KeyPairSigner,
pipe,
createTransactionMessage,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
appendTransactionMessageInstruction,
generateKeyPairSigner
} from "@solana/kit";
import { getAddMemoInstruction } from "@solana-program/memo";
import { getTransferSolInstruction } from "@solana-program/system";
const MINIMUM_JITO_TIP = 1_000n; // lamports
const CONFIG = {
solanaRpcUrl: "https://api.mainnet-beta.solana.com",
koraRpcUrl: "http://localhost:8080/",
jitoTipLamports: MINIMUM_JITO_TIP,
bundleSize: 4, // We'll create 4 transactions for this demo
pollIntervalMs: 6000,
pollTimeoutMs: 60000
};

以下を設定しています:

  • トランザクション構築のための Solana Kit のインポート
  • デモトランザクション用の Memo program(実際の操作に置き換えます)
  • Jitoチップ転送用の System Program
  • RPCエンドポイントとバンドルパラメータの 設定

Jitoチップアカウント

Jitoには、SOLを送信できる 8つのチップアカウント があります。このデモではランダムに1つ選択します。

// Jito tip accounts - one is randomly selected by the block engine
const JITO_TIP_ACCOUNTS = [
"96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5",
"HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe",
"Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY",
"ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49",
"DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh",
"ADuUkR4vqLUMWXxW9gh6D6L8pMSawimctcNZ5pGwDcEt",
"DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL",
"3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT"
];
function getRandomTipAccount(): string {
return JITO_TIP_ACCOUNTS[
Math.floor(Math.random() * JITO_TIP_ACCOUNTS.length)
];
}

チップアカウントはJitoが運用するアドレスです。いずれかにSOLを送信することで、validatorに対してチップ額を通知します。

ステップ1:クライアントの初期化

APIキーを使用してKoraクライアントを初期化します。これはkora.tomlで設定されたものと一致します。本番環境では、環境変数から読み込むことになります。

async function initializeClients() {
console.log("\n[1/4] Initializing clients");
console.log(" → Kora RPC:", CONFIG.koraRpcUrl);
console.log(" → Solana RPC:", CONFIG.solanaRpcUrl);
const client = new KoraClient({
rpcUrl: CONFIG.koraRpcUrl,
apiKey: "kora_facilitator_api_key_example"
});
return { client };
}

ステップ2:キーのセットアップ

async function setupKeys(client: KoraClient) {
console.log("\n[2/4] Setting up keypairs");
const senderKeypair = await generateKeyPairSigner();
console.log(" → Sender:", senderKeypair.address);
const { signer_address } = await client.getPayerSigner();
console.log(" → Kora signer address:", signer_address);
return { senderKeypair, signer_address };
}

デモ用に新しいkeypairを作成するためにgenerateKeyPairSigner()を使用します。keypairはmemo instructionsに署名するだけで、Kora手数料を支払う必要がない(設定による)ため、SOLやその他のトークンは不要です。Koraの署名者(getPayerSignerで取得)がすべての手数料とJitoチップを支払います。

ステップ3:バンドルトランザクションの作成

それでは、トランザクションのバンドルを作成しましょう。複数のトランザクションを作成し、それぞれに独自のinstructionsを持たせます。ここでは一意のmemo instructionsを使用して、Solana Mainnetに着地した後のトランザクションを簡単に検証できるようにしています。

async function createBundleTransactions(
client: KoraClient,
senderKeypair: KeyPairSigner,
signer_address: string
) {
console.log("\n[3/4] Creating bundle transactions");
const noopSigner = createNoopSigner(address(signer_address));
const latestBlockhash = await client.getBlockhash();
const tipAccount = getRandomTipAccount();
console.log(" → Blockhash:", latestBlockhash.blockhash.slice(0, 8) + "...");
console.log(" → Tip account:", tipAccount.slice(0, 8) + "...");
const transactions: string[] = [];
for (let i = 0; i < CONFIG.bundleSize; i++) {
const isLastTransaction = i === CONFIG.bundleSize - 1;
console.log(
` → Transaction ${i + 1}: Kora Memo "Bundle tx #${i + 1}"${
isLastTransaction ? " + Jito tip" : ""
}`
);
// Build transaction with memo
let transactionMessage = pipe(
createTransactionMessage({
version: 0
}),
(tx) => setTransactionMessageFeePayerSigner(noopSigner, tx),
(tx) =>
setTransactionMessageLifetimeUsingBlockhash(
{
blockhash: latestBlockhash.blockhash as Blockhash,
lastValidBlockHeight: 0n
},
tx
),
(tx) =>
appendTransactionMessageInstruction(
getAddMemoInstruction({
memo: `Kora Bundle tx #${i + 1} of ${CONFIG.bundleSize}`,
signers: [senderKeypair]
}),
tx
),
// Add Jito tip to the LAST transaction only
(tx) =>
isLastTransaction
? appendTransactionMessageInstruction(
getTransferSolInstruction({
source: noopSigner,
destination: address(tipAccount),
amount: CONFIG.jitoTipLamports
}),
tx
)
: tx
);
// Sign with sender keypair (required for memo instruction)
const signedTransaction =
await partiallySignTransactionMessageWithSigners(transactionMessage);
const base64Transaction =
getBase64EncodedWireTransaction(signedTransaction);
transactions.push(base64Transaction);
}
console.log(` ✓ ${transactions.length} transactions created for bundle`);
return transactions;
}

重要:Kora署名者によるチップ支払い:KoraノードにJitoチップを支払わせたいため、「何もしない」署名者(noopSigner)を使用します。ここではKoraのアドレスがチップ転送の送信元となります。Koraはバンドル処理時にこれに署名します。

ステップ4:バンドルの署名と送信

これで、すべてをまとめてバンドルをKoraに送信し、署名とJitoのブロックエンジンへの提出を行うことができます。

async function main() {
console.log("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
console.log("KORA JITO BUNDLE DEMO");
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
try {
// Step 1: Initialize clients
const { client } = await initializeClients();
// Step 2: Setup keys
const { senderKeypair, signer_address } = await setupKeys(client);
// Step 3: Create bundle transactions
const transactions = await createBundleTransactions(
client,
senderKeypair,
signer_address
);
// Step 4: Sign and send bundle
console.log("\n[4/4] Signing and sending bundle");
const { bundle_uuid } = await client.signAndSendBundle({
transactions,
signer_key: signer_address
});
console.log("\nBundle UUID:");
console.log(bundle_uuid);
} catch (error) {
console.error("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
console.error("ERROR: Demo failed");
console.error("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
console.error("\nDetails:", error);
process.exit(1);
}
}
main().catch((e) => console.error("Error:", e));

デモの実行

1. Koraサーバーを起動する

cd examples/jito-bundles/server
kora --config kora.toml --rpc-url https://api.mainnet-beta.solana.com rpc start --signers-config signers.toml

2. クライアントを実行する

新しいターミナルで、client/ディレクトリに移動してデモを実行します:

cd examples/jito-bundles/client
# Install dependencies
pnpm install
# Run the demo
pnpm start

期待される出力

ステップバイステップの実行が表示され、最後に成功したバンドルが表示されるはずです。バンドルは以下を実行します:

  • 4つのメモトランザクションを作成
  • 最後のトランザクションにJitoチップ(1,000 lamport)を追加
  • すべてのトランザクションが手数料支払者としてKoraによって署名される
  • Jitoのブロックエンジンにアトミックに送信

注意:

  • Jitoのデフォルトルーターはレート制限に達する可能性があります。429エラーが発生した場合は、後でもう一度試すか、より高い制限をリクエストできます。詳細については、Jitoのレート制限ドキュメントをご確認ください。
  • このデモは非常に小さなチップを使用しているため、バンドルがSolana Mainnetに到達しない可能性があります。Jitoのバンドルエクスプローラーでバンドルが表示されない場合は、より高いチップで後ほど再試行できます。

何が起こったかを理解する

単一トランザクションとは異なり、以下のことが行われました:

  1. 複数のトランザクション — 1つのトランザクションではなく、一緒に実行する必要がある4つのトランザクションを作成しました
  2. Jitoチップ — バリデーターにインセンティブを与えるために、チップ転送(Koraの署名者が支払う)を追加しました
  3. バンドル検証 — Koraはすべてのトランザクションがkora.tomlで指定された要件を満たしていることを検証しました
  4. アトミック送信 — すべてのトランザクションが単一のユニットとしてJitoに送信され、すべての手数料とチップはKoraサーバーによってKoraの署名者が支払いました

結果:4つのトランザクションすべてが順番に実行されるか、まったく実行されないかのいずれかです。部分的な状態は発生しません。

追加リソース

Is this page helpful?

管理運営

© 2026 Solana Foundation.
無断転載を禁じます。
つながろう