手术刀般的精准:构建交易
我们已经发现了目标,确认了安全,现在到了扣动扳机的时刻。 在 Solana 中,一个 交易 (Transaction) 就像一个精心打包的快递包裹。如果包裹上的地址写错(账户不对),或者包裹里的信件格式不对(指令数据错误),验证者会直接拒收。
本章将教你如何手动组装这个包裹,而不是依赖臃肿的 SDK。
5.1 交易的解剖学
一个标准的 Solana 交易包裹包含以下核心组件:
- 签名者 (Signatures):你的私钥签名。这是包裹的封条,证明是你发的。
- 消息体 (Message):
- 账户清单 (Account Keys):交易中涉及的所有人的地址列表(你、项目方、代币合约、系统程序等)。
- 最新区块哈希 (Recent Blockhash):这是"时间戳"。如果这个哈希太旧(超过 ~1分钟),交易失效。
- 指令集 (Instructions):这是核心。告诉程序具体要做什么。
5.2 核心指令一:计算预算 (Compute Budget)
这是狙击机器人的加速器。如果不加这个,你的交易就是"经济舱",在拥堵时会被直接扔下飞机。
你需要添加两个指令到你的交易的最前端:
1. SetComputeUnitLimit (设置油箱大小)
告诉验证者:"这个交易很简单,只会消耗很少的计算资源"。
- 默认值:200,000 CU。
- 狙击技巧:Pump.fun 的买入通常只需要 ~60,000 CU。如果你显式设置为 80,000 CU,验证者会更乐意打包你的交易,因为这能让他们在同一个区块里塞入更多交易。
2. SetComputeUnitPrice (设置油价/贿赂)
告诉验证者:"虽然我油用得少,但我愿意为每滴油付高价"。
- 单位:MicroLamports (1 SOL = 10^9 Lamports = 10^15 MicroLamports)。
- 策略:这是你击败竞争对手的关键。通过
getRecentPrioritizationFees查询当前网络的火热程度,然后加倍出价。
typescript// Web3.js 代码示例 const budgetIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 80000 }); const priceIx = ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 150000 });
5.3 核心指令二:业务逻辑 (Swap)
这是包裹里的"信件内容"。我们要调用 DEX 程序(如 Raydium 或 Pump.fun)的 Buy 方法。
一个指令 (Instruction) 包含三个部分:
- Program ID:收信人(例如 Pump.fun 合约地址)。
- Keys (Accounts):涉及的账户列表(你的钱包、代币账户、池子账户等)。
- Data (Binary):具体内容的二进制编码。
难点:Data 是如何编码的?
很多新手卡在这里。为什么 Data 是一串乱码? Solana 程序通常使用 Borsh 序列化。对于 Anchor 框架开发的程序(绝大多数现代程序),Data 的前 8 个字节是 Discriminator (鉴别器)。
Discriminator = sha256("global:func_name")[0..8]
它告诉合约:"我要调用的是 buy 函数,而不是 sell 函数"。
Pump.fun Buy 指令的数据结构:
[ 8 bytes ] Discriminator (比如: "66063d1201daebea" 代表 "buy") [ 8 bytes ] Amount (你想买多少个币,u64) [ 8 bytes ] Max Sol Cost (你愿意花多少 SOL,含滑点,u64)
你必须在本地将这些数字转换成 Buffer (二进制数组) 放入 Data 字段。
5.4 版本化交易 (Versioned Transactions - v0)
传统的交易格式 (Legacy) 在复杂的 DeFi 操作中经常遇到"交易太大"的问题(最大 1232 字节)。 狙击机器人必须使用 Versioned Transaction (v0)。
它引入了 地址查找表 (Address Lookup Tables - ALT),可以将一长串地址压缩成一个短索引。虽然新盘狙击可能用不到 ALT(因为还没来得及建表),但 v0 格式是现在的标准。
typescript// 构建 v0 交易流程 const messageV0 = new TransactionMessage({ payerKey: wallet.publicKey, recentBlockhash: blockhash, instructions: [ budgetIx, // 1. 限制计算量 priceIx, // 2. 加钱插队 swapIx // 3. 执行买入 ], }).compileToV0Message(); const transaction = new VersionedTransaction(messageV0); transaction.sign([wallet]);
5.5 序列化与发送
最后一步是将这个对象序列化 (Serialize) 成一串二进制数据包,然后通过 RPC 发送出去。
- Preflight Check (预检):RPC 节点会先模拟执行一遍。如果模拟失败(比如滑点过低),交易不会被广播。
- Skip Preflight:狙击机器人通常会设置
skipPreflight: true。- 为什么?因为预检需要时间,而且有时候模拟失败是因为网络状态还没更新(比如你太快了,RPC 还没看到池子创建)。
- 风险:如果交易真的有问题,你会损失手续费。但在抢新币时,速度高于一切。
typescriptconst rawTransaction = transaction.serialize(); // 发送原始二进制数据,跳过预检 connection.sendRawTransaction(rawTransaction, { skipPreflight: true, maxRetries: 0 // 既然是狙击,失败了重试通常也来不及了 });