最后更新时间:2025-01-09
您将构建什么
在完整交易流程指南中,您学习了如何使用 Kora 创建无 Gas 交易。然而,在许多场景下,单个交易是不够的,或者单个交易中没有足够的空间来包含 Kora 支付指令。在本指南中,我们将构建一个演示,展示如何使用 Kora 签名并将交易捆绑包发送到 Jito 的区块引擎, 以便在 Solana 主网上原子化执行。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 完整交易流程指南 — 我们将在这些概念的基础上继续构建
- 安装 Node.js(LTS 或更高版本)
- 熟悉 Solana 交易
- 熟悉 Jito 捆绑包
Kora v2.2.0 Beta
重要提示:本指南需要 Kora v2.2.0 beta 版本。您可以在 此处找到该版本。这是预发布版本,可能包含错误。
cargo install kora-cli@2.2.0-beta.7
Jito 捆绑包基础知识
在 Solana 上,交易中的每条指令都是原子化的——如果一条指令失败,整个交易就会失败。捆绑包是一种工具,使您能够以原子化和顺序方式执行最多 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.gitcd 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 = truesign_and_send_bundle = trueestimate_bundle_fee = trueget_blockhash = trueget_config = trueget_payer_signer = true[validation]max_allowed_lamports = 1000000max_signatures = 10price_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 来支付:
- 所有捆绑包交易的交易费用
- 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; // lamportsconst 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 demopollIntervalMs: 6000,pollTimeoutMs: 60000};
我们正在设置:
- Solana Kit 导入用于构建交易
- Memo 程序用于我们的演示交易(您可以用实际操作替换)
- System Program用于 Jito 小费转账
- 配置用于 RPC 端点和捆绑包参数
Jito 小费账户
Jito 有 8 个小费账户 可以发送 SOL。我们在此演示中随机选择一个。
// Jito tip accounts - one is randomly selected by the block engineconst 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 };}
我们使用 generateKeyPairSigner()
为演示创建一个新的 keypair。由于该 keypair 仅签署 memo 指令,并且不必支付任何 Kora 费用(根据我们的配置),因此不需要 SOL 或其他代币。Kora 的签名者(通过
getPayerSigner 获取)支付所有费用和 Jito 小费。
步骤 3:创建捆绑包交易
现在让我们创建一个交易捆绑包。我们创建多个交易,每个交易都有自己独特的指令。我们在这里使用独特的 memo 指令,以便在交易登陆 Solana 主网后轻松验证。
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 memolet 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 clientsconst { client } = await initializeClients();// Step 2: Setup keysconst { senderKeypair, signer_address } = await setupKeys(client);// Step 3: Create bundle transactionsconst transactions = await createBundleTransactions(client,senderKeypair,signer_address);// Step 4: Sign and send bundleconsole.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/serverkora --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 dependenciespnpm install# Run the demopnpm start
预期输出
您应该会看到逐步执行的过程,最后成功生成捆绑包。该捆绑包将:
- 创建 4 个备忘录交易
- 在最后一笔交易中添加 Jito 小费(1,000 lamport)
- 所有交易由 Kora 作为费用支付方签名
- 原子性地提交到 Jito 的区块引擎
注意:
- Jito 的默认路由器可能会遇到速率限制。如果您收到 429 错误,可以稍后重试或请求更高的限制。请查看 Jito 的速率限制文档了解更多信息。
- 由于我们的演示使用的小费非常少,捆绑包可能无法在 Solana 主网上落地。如果您在 Jito 的捆绑包浏览器上看不到该捆绑包,可以稍后使用更高的小费重试。
理解发生了什么
与单笔交易相比,以下是不同之处:
- 多笔交易 — 我们创建了 4 笔必须一起执行的交易,而不是单笔交易
- Jito 小费 — 我们添加了小费转账(由 Kora 的签名者支付)以激励验证者
- 捆绑包验证 — Kora 验证所有交易是否满足
kora.toml中指定的要求 - 原子性提交 — 所有交易作为单个单元由我们的 Kora 服务器提交给 Jito,所有费用和小费均由 Kora 的签名者支付
结果:要么全部 4 笔交易按顺序执行,要么一笔也不执行。不会出现部分状态。
其他资源
- 需要帮助? 在 Solana Stack Exchange
上提问,并添加
Kora标签 - Jito 文档 — 官方 Jito MEV 文档
- Bundle RPC 方法 — signBundle、signAndSendBundle、estimateBundleFee
- GitHub 仓库 — 源代码和示例
Is this page helpful?