Solana Jito bundles
在这篇文章中,我们将学习使用 TS 构建和提交 Jito bundler 到Solana网络的 实用示例。

一键发币: SUI | SOL | BNB | ETH | BASE | ARB | OP | POLYGON | AVAX | FTM | OK
在这篇博客中,你将学习:
- Solana 的 交易大小限制
- Jito bundles 是什么以及为什么我们需要它们
- 使用 TS 构建和提交 Jito bundler 到网络的 实用示例(GitHub 仓库)
1、引言
Solana 是一个快速、廉价且高性能的网络;它拥有最高的 TPS,优于其他所有链。但现实世界并不完美,任何事物都有代价。当你创造如此高效且廉价的东西时,必然会有取舍。其中一个取舍就是交易大小限制。
2、Solana 交易大小限制
Solana 网络强制执行 1232 字节 的交易大小限制,以确保每个交易都能放入单个网络数据包中。这一设计选择是经过深思熟虑且高效的。Solana 网络遵循最大传输单元(MTU)大小为 1280 字节的标准,与 IPv6 MTU 一致,并在扣除必要的头部(IPv6 头部 40 字节和分片头部 8 字节)后,剩余 1232 字节可用于数据包内容。你可以在这里了解更多
虽然 这通常足够 用于大多数用例,但在某些情况下,你可能需要更多。如果你出于某种原因想要提交两个交易,并希望其中一个先于另一个提交,并确保如果第一个交易出现问题,第二个交易永远不会到达网络,
你不能同时签署并发送两个交易,因为无法保证第一个交易会先被提交。相反,你必须先签署并提交第一个交易,等待确认后再签署并提交第二个交易。这个过程会给用户带来糟糕的体验,因为他们需要为单一操作签署两次。那么,解决方案是什么?这就是 Jito Bundles 的作用。
3、Jito
Jito 是 Solana 网络上最大的项目之一;他们做了很多酷炫的事情(查看他们的博客),但我们现在关注的是 Jito Bundles 功能。Jito Bundles 包含最多五个按顺序执行且原子化的交易,确保要么全部成功,要么全部失败。
Bundles 是如何工作的?
- 交易者将 Bundles 提交给区块引擎
- 区块引擎模拟 Bundles 以确定最有利可图的组合
- 获胜的 Bundles 被发送给验证节点以包含在区块中
- 验证节点以原子方式执行 Bundles 并收集小费
4、实用示例
在这个示例中,你将构建并提交一个 Bundle 到 Jito。你可以在 GitHub 仓库 中找到示例代码。
类似于 Solana 的优先费用,Jito 使用一种称为“Jito 小费”的机制来优先处理 Bundles。这些用户定义的金额激励验证节点优先处理 Bundles,确保更快的执行。
小费只是一个将 SOL 转账到已知 Jito 小费账户的指令,此指令应在你的 Bundle 中作为另一笔交易的一部分或作为独立交易存在,我们将在这里查看两者。
如果你继续克隆 仓库,打开 README 文件并按照说明安装依赖项(我使用的是 Bun,但你可以自由选择其他工具),然后设置环境变量,你会发现 src
文件夹中有两个文件:embedded-tip-ix.ts
和 separated-tip-tx.ts
,它们非常相似,唯一的区别在于我们是在其中一个 Bundle 的交易中嵌入小费指令(最后一个交易),而在另一个中将其分离为独立交易。你可以在此处阅读有关小费最佳实践的更多信息 这里 和 这里
简而言之:你应该尽量将小费指令放在 Bundle 的最后,并且最好将其放在交易中而不是独立交易中,在这种情况下,你可以确保你的小费不会因恶意验证节点或 叔块 而浪费掉。
现在让我们来看看 src/embedded-tip-ix.ts
文件并跳过导入部分(因为它很无聊),你首先看到的函数是 getRandomeTipAccountAddress
const getRandomeTipAccountAddress = async (
searcherClient: searcher.SearcherClient,
) => {
const account = await searcherClient.getTipAccounts();
return new PublicKey(account[Math.floor(Math.random() * account.length)]);
};
此函数获取小费账户并随机选择一个。使用随机账户而不是始终选择第一个账户可以增加我们的 Bundle 成功的概率,如果有太多交易尝试向同一个账户小费,可能会导致其中一些失败。
接下来是 buildMemoTransaction
函数:
const MEMO_PROGRAM_ID = "Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo";
const buildMemoTransaction = (
keypair: Keypair,
message: string,
recentBlockhash: string,
tipIx?: TransactionInstruction,
): VersionedTransaction => {
const ix = new TransactionInstruction({
keys: [
{
pubkey: keypair.publicKey,
isSigner: true,
isWritable: true,
},
],
programId: new PublicKey(MEMO_PROGRAM_ID),
data: Buffer.from(message),
});
const instructions = [ix];
if (tipIx) instructions.push(tipIx);
const messageV0 = new TransactionMessage({
payerKey: keypair.publicKey,
recentBlockhash: recentBlockhash,
instructions,
}).compileToV0Message();
const tx = new VersionedTransaction(messageV0);
tx.sign([keypair]);
console.log("txn signature is: ", bs58.encode(tx.signatures[0]));
return tx;
};
这是一个简单的函数,用于构建一个无实际用途的备忘录交易,但它非常便宜且简单,因此非常适合我们的示例,因为我们将在主网上运行它。它还接受一个可选的小费指令,并在提供时将其推送到交易中。
暂且搁置这一点,让我们进入重点,main
函数的前几行只是设置几个变量。
const main = async () => {
const blockEngineUrl = process.env.BLOCK_ENGINE_URL || "";
console.log("BLOCK_ENGINE_URL:", blockEngineUrl);
const authKeypairPath = process.env.AUTH_KEYPAIR_PATH || "";
console.log("AUTH_KEYPAIR_PATH:", authKeypairPath);
const decodedKey = new Uint8Array(
JSON.parse(Fs.readFileSync(authKeypairPath).toString()) as number[],
);
const keypair = Keypair.fromSecretKey(decodedKey);
const bundleTransactionLimit = parseInt(
process.env.BUNDLE_TRANSACTION_LIMIT || "5",
);
之后,我们需要创建搜寻客户端,这是我们用来与 Jito 服务器通信的工具
// 创建与 Jito 交互的搜寻客户端
const searcherClient = searcher.searcherClient(blockEngineUrl);
我们还可以订阅 onBundleRequest
以便在我们向网络发送 Bundle 后接收更新
// 订阅 Bundle 结果
searcherClient.onBundleResult(
(result) => {
console.log("received bundle result:", result);
},
(e) => {
throw e;
},
);
然后我们将获取小费账户并构建小费指令
// 获取随机的小费账户地址
const tipAccount = await getRandomeTipAccountAddress(searcherClient);
console.log("tip account:", tipAccount);
const rpcUrl = process.env.RPC_URL || "";
console.log("RPC_URL:", rpcUrl);
// 获取最新的区块哈希
const connection = new Connection(rpcUrl, "confirmed");
const blockHash = await connection.getLatestBlockhash();
// 构建转账指令
const tipIx = SystemProgram.transfer({
fromPubkey: keypair.publicKey,
toPubkey: tipAccount,
lamports: 1000,
});
如前所述,小费可以包含在一个 Bundle 的交易中,也可以是一个独立的交易。
const transactions = [
buildMemoTransaction(keypair, "jito test 1", blockHash.blockhash),
// 将小费指令包含在第二个交易中
buildMemoTransaction(keypair, "jito test 2", blockHash.blockhash, tipIx),
];
但是我们也可以将其作为一个独立的交易来执行,如下所示:
const tipTx = new VersionedTransaction(
new TransactionMessage({
payerKey: keypair.publicKey,
recentBlockhash: blockHash.blockhash,
instructions: [tipIx],
}).compileToV0Message(),
);
tipTx.sign([keypair]);
const transactions = [
buildMemoTransaction(keypair, "jito test 1", blockHash.blockhash),
buildMemoTransaction(keypair, "jito test 2", blockHash.blockhash),
];
一切准备就绪后,我们可以使用这些交易构建一个捆绑包,并将其提交给Jito。
const jitoBundle = new bundle.Bundle(
[...transactions, tipTx],
bundleTransactionLimit,
);
try {
const resp = await searcherClient.sendBundle(jitoBundle);
console.log("resp:", resp);
} catch (e) {
console.error("error sending bundle:", e);
}
这是示例文件 src/embedded-tip-ix.ts
的完整代码 链接
现在,如果你运行该文件,你应该会看到交易签名和捆绑包ID。你可以在任何浏览器中检查这些交易,并在 Jito 探索器 中检查捆绑包ID(可能需要几分钟才能显示出来)
txn signature is: 5YQVsedCaaf1bCbTUJgd23vNfte2doB1K3CB6tKxCH7KYY7Y8rDwbmetCcgBufhz8nY1nWDeCRNqhUkWNydnsjeZ
txn signature is: 3mnhGK2X2FVnsada8YL46TYdZc7BfR14GdYTjJWk8mrG2WzBJGXdyN7aLVNP3ZkWGwUBpGypjW7JWFeYGKNRa2vR
resp: 09d2c693a232d48781f69d786276b8af04be9138b0777d313b18251271825b3c
恭喜,你已经成功提交了一个捆绑包
5、结束语
Jito 捆绑包为解决Solana交易大小限制提供了一种强大的解决方案,确保复杂操作的无缝和原子化执行。通过利用小费和智能交易捆绑,开发人员可以优化用户体验并充分利用Solana的能力。有了本指南和提供的示例,你现在拥有将Jito捆绑包有效集成到Solana项目中的工具。尝试一下并探索可能性吧!
原文链接:Atom transactions using Jito bundles
DefiPlot翻译整理,转载请标明出处
免责声明:本站资源仅用于学习目的,也不应被视为投资建议,读者在采取任何行动之前应自行研究并对自己的决定承担全部责任。