Solana transfer hook,以及你必须传的那些账户

hook 把每一次转账都变成对别人程序的一次 CPI —— 多出账户、多出计算,还多出一条你控制不了的 revert 路径。

BoltTx Team··13 min read
solanatransfer-hooktoken-2022cpi计算交易上链

转账费扩展改变的是一个金额。★transfer hook 扩展改变的是"转账"这件事本身。★

带 hook 的代币每一次转账,都会在你的交易内部调用一个第三方程序 —— 逻辑是代币作者写的,而你从指令列表里看不到它

hook 实际在做什么

你的 swap 指令
  └─ 代币程序:transfer
      └─ ★hook 程序:作者写了什么就是什么★
          └─ 可以读账户、强制规则、★让你的交易 revert★

★hook 在每一次转账时运行,而且它可以失败。★ 一旦失败,你的交易 revert —— 而原因出现在日志里一个你从没引用过的程序底下。

常见的正当用途:白名单、转账限额、链上版税强制、合规检查。★它们的共同点是:你的交易现在依赖别人的代码。★

账户问题

这正是带 hook 的代币会击垮那些本来正确处理了 Token-2022 的机器人的地方。

★程序没法为一次 CPI 凭空造出账户。★ hook 碰到的每一个账户都必须出现在你的原始交易里 —— 但你不知道那些账户是什么,因为是 hook 作者定的

这个扩展用一份链上清单解决它:

import { getExtraAccountMetaAddress } from "@solana/spl-token";

// ★hook 程序会公布它需要哪些额外账户。★
const metaListPda = getExtraAccountMetaAddress(mint, hookProgramId);
const metaAccount = await connection.getAccountInfo(metaListPda);

SDK 可以替你组装:

import { createTransferCheckedWithTransferHookInstruction } from "@solana/spl-token";

const ix = await createTransferCheckedWithTransferHookInstruction(
  connection, source, mint, destination, owner,
  amount, decimals, [], "confirmed",
  TOKEN_2022_PROGRAM_ID,
);

★这个调用会从链上读取额外账户清单并自动追加★ —— 这就是它是异步的原因,而其它每一个指令构造器都不是

手工构建指令,正是这件事出错的地方。 对一个带 hook 的 mint 用普通的 createTransferCheckedInstruction,编译没问题、提交没问题,然后 revert 并报 NotEnoughAccountKeys,点名一个你从没听说过的账户

如果你的 hook 处理是对的、交易还是会错过,免费领个 BoltTx key 改一行就能测测提交路径。

你继承下来的成本

计算。 hook 从你这笔交易的预算里消耗单元。★你在为一段自己没写、也没法提前测量的代码付钱★ —— 唯一的办法是模拟真实的那次转账。

const sim = await connection.simulateTransaction(tx, {
  replaceRecentBlockhash: true, sigVerify: false,
});
const limit = Math.ceil((sim.value.unitsConsumed ?? 200_000) * 1.3);

★这里用比平时更宽的余量是合理的★,因为 hook 的消耗会随它自己的内部状态变化 —— 在更长的白名单上做一次查找,比在短名单上更贵。

交易体积。 额外账户每个 32 字节,计入 1232 字节上限。一笔本来就贴近上限的多跳 swap,可能被 hook 的账户需求顶出去。

CPI 深度。 hook 多占一层。你的指令、聚合器、池子程序、代币程序,再加 hook —— ★那正好把四层嵌套占满,而一个带 hook 的代币出现在更深的聚合器路由里就会超。★

交易之前先检测 hook

import { getTransferHook, getMint } from "@solana/spl-token";

const mint = await getMint(connection, mintAddress, "confirmed", TOKEN_2022_PROGRAM_ID);
const hook = getTransferHook(mint);

if (hook && !hook.programId.equals(PublicKey.default)) {
  // ★这个代币每次转账都会往外调。★
}

★配置成默认(全零)program ID 的 hook 实际上是禁用的★ —— 这个区分值得处理:扩展可以存在,但什么都不做。

除了"有没有",还要检查:

权限在谁手里? 上线之后,控制它的人随时能更换 hook 程序。★一个今天可以放心交易的代币,明天可能把 hook 指向另一段代码。★

hook 程序可验证吗? 如果源码没公开,你每一次交易都在自己的交易里执行未知逻辑

它会选择性 revert 吗? ★一个 hook 可以放行买入、拦住卖出。★ 在你试图退出之前,这和普通转账毫无区别

为什么这里模拟更重要

对大多数 Token-2022 特性,读 mint 配置就能知道会发生什么。★对 hook 不行 —— hook 是任意代码,只有运行它才能揭示行为。★

// ★上规模之前,先双向模拟一笔小额转账。★
const buySim  = await connection.simulateTransaction(buyTx,  {...});
const sellSim = await connection.simulateTransaction(sellTx, {...});

模拟卖出是最容易被跳过的那一步,而它恰恰是要紧的那一步。一个允许买入、拦住卖出的 hook,会让你握着一个退不出去的仓位 —— 而买入模拟看起来完全健康。

什么时候干脆跳过这个代币

★不是每个代币都值得为它做集成,而 hook 是一个合理的筛子。★

跳过: hook 程序未验证、权限在一把来路不明的单一密钥手里,或者卖出模拟失败而买入成功

处理: hook 来自已知协议、源码公开、且双向模拟都干净。

对一个抢打新的高速机器人,★诚实的答案通常是跳过★ —— 额外账户、额外计算和未知的 revert 条件,花掉的时间超过这个机会的价值,而失败的后果是握着一个卖不掉的东西。

上链应该是什么水平

我们投递节点上的真实交易:中位确认 336 毫秒 —— 不到一个 slot。

★hook 导致的 revert 会落块,并像其它任何 revert 一样让你付掉基础手续费。★ 既然原因在一个你控制不了的程序里,模拟就是唯一能便宜地抓住它的地方。

BoltTx 在这里的位置

我们负责提交。一个代币有没有 hook、它需要哪些账户,都是你指令构建的属性

提交走我们自建的四区域投递节点,带 SWQoS 路由,不暴露公共 mempool —— 交易在落块之前、传输途中观察不到。我们不修改交易内容,这包括绝不往你的指令里增删账户。

你在本地签名。我们不托管资金、不代签。tip 带在交易里、从你自己的钱包链上支付,交易失败时跟着一起 revert —— 这是 Solana 原子交易的机制决定的。只有到达链上的交易才计费。

免费领 API key,没有月费:

const connection = new Connection("https://la.bolttx.io/?api-key=YOUR_KEY");

常见问题

Solana 的 transfer hook 是什么? 一项 Token-2022 扩展,在该代币每一次转账时调用一个第三方程序。hook 可以读账户、强制规则,并让你的交易 revert

我的转账为什么报 NotEnoughAccountKeys? hook 需要一些你的指令没包含的额外账户。改用 createTransferCheckedWithTransferHookInstruction,它会从链上读取所需账户并追加进去。

怎么知道一个 transfer hook 需要哪些账户? 它们公布在链上的一份额外账户清单里,由 mint 和 hook 程序推导得出。SDK 的辅助函数会替你解析 —— 这就是那个构造器是异步的原因。

怎么检测一个代币有没有 transfer hook? 对 mint 调 getTransferHook注意配置成默认全零 program ID 的 hook 实际是禁用的,所以要检查 program ID 而不只是"存不存在"。

transfer hook 消耗计算吗? 消耗,而且从你这笔交易的预算里出。既然消耗取决于 hook 自己的逻辑和状态,要模拟真实转账并用比平时更宽的余量

transfer hook 能拦住我卖出吗? 能。一个 hook 可以放行买入、拒绝卖出,在你试图退出之前和普通代币毫无区别。建仓之前先模拟卖出。

transfer hook 影响 CPI 深度吗? 影响,它多占一层。在一条本来就嵌套了好几个程序的聚合器路由里,带 hook 的代币会超出四层嵌套上限。

hook 程序上线后能被更换吗? 如果设了权限,能。一个今天交易顺畅的代币,可以把 hook 重新指向另一段代码 —— 所以权限和当前程序同样重要。

我的机器人该支持带 hook 的代币吗? 只有当 hook 来自已知协议、源码公开、且双向模拟都干净时才该。对高速打新来说,跳过通常是更划算的选择

transfer hook 的指令构造器为什么是异步的? 因为它要从链上读取额外账户清单,才能决定追加哪些账户。其它指令构造器是纯函数,不需要网络访问。

hook 程序 revert 了会怎样? 你的整笔交易 revert,因为它是原子的。 它落了块、什么都没改变、还花掉了基础手续费 —— 而失败出现在一个你从没引用过的程序底下。

所有 DEX 都支持 transfer hook 吗? 并非普遍支持。协议必须自己组装那些额外账户,所以用一笔小额交易验证,不要假设某条路由能处理它。

为 hook 该留多少计算余量? 比平常的百分之二十更多,因为 hook 的成本会随它的内部状态变化。在更长的白名单上做检查,比在短名单上消耗更多。

交易之前能看到 transfer hook 做什么吗? 只有在程序源码公开时能。否则你是在自己的交易里执行未知逻辑 —— 而这本身就是跳过这个代币的理由。

transfer hook 和转账费是一回事吗? 不是。费用改变到账金额、由代币程序强制;hook 运行任意的第三方代码,并且可以直接拒绝这次转账。

怎么安全地测试 hook 处理? 投入资金之前,用小额同时模拟一次买入和一次卖出。 卖出模拟才是那个能揭示"hook 设计成困住仓位"的检查。

延伸阅读

返回博客列表