Solana 清算机器人:一场没有发令枪的比赛

清算是一场没人喊开始的比赛。怎么盯健康度、什么时候提交,以及为什么赢的是先落块的、不是先发现的。

BoltTx Team··15 min read
solana清算机器人defi交易上链交易机器人mev

清算看起来是 DeFi 里最干净的机会。一个仓位变得可清算,你把它平掉,拿走奖励。规则是公开的,利润事先就定死了。

而这恰恰是它难的原因。 所有人都能看到同样的仓位在接近同样的阈值,于是整场竞争坍缩成一件事:谁先落块。

这场比赛没有发令枪

大多数交易机会都始于一个事件:一次上新、一笔大额 swap、一个价格成交。清算始于一个"缺席" —— 某个仓位因为别处的价格动了而越过阈值,而链上不会有任何东西宣布这件事。

所以对不同的机器人来说,比赛的起跑时刻是不同的,取决于各自怎么盯。每五秒轮询一次的,发现得晚;盯着预言机的,在价格更新的那一刻就知道了。

★但先发现只有在你也先落块的时候才有意义。★ 两个在同一瞬间发现同一个仓位的机器人,接下来就是一场纯粹的提交竞赛 —— 而那由费用、重试行为和路由决定。

两种盯法

轮询健康度。 定时用 getProgramAccounts 读所有仓位、算健康度、对低于阈值的动手。简单,但规模一大就很贵 —— 几百个账户短周期轮询,额度烧得飞快。

// 批量取,别循环。一次调用最多 100 个账户。
const accounts = await connection.getMultipleAccountsInfo(positionPubkeys);
const atRisk = accounts
  .map((acc, i) => ({ pubkey: positionPubkeys[i], state: deserialize(acc.data) }))
  .filter((p) => healthFactor(p.state) < 1.05); // 盯"接近",不只盯"已越过"

★过滤条件要设在"接近阈值",不是"已经越过"。★ 等你轮询到健康度低于 1.0 的时候,这个机会已经在被争抢了。盯住 1.05 附近的仓位,你才有时间准备。

盯价格源。 仓位之所以变得可清算,是因为价格动了。如果你盯的是预言机更新而不是仓位本身,你和协议在同一刻知道这个变化。

connection.onAccountChange(
  oracleAccount,
  (info) => {
    const price = parsePrice(info.data);
    // 只对已知处在边缘的仓位重算健康度。
    const nowEligible = watchlist.filter((p) => healthAt(p, price) < 1.0);
    nowEligible.forEach(submitLiquidation);
  },
  "processed",
);

这才是有意义的优化:★事先算好"哪个仓位在哪个价位变得可清算",然后对价格更新做出反应,而不是重新扫一遍全部。★

如果检测已经够快、问题在"先落块",免费领个 BoltTx key 改一行就能拿来做对比。

清算为什么会拥堵

清算机会是成簇出现的。一次剧烈的价格移动会让很多仓位同时变得可清算,而所有盯着的机器人在同一秒内做出反应。

于是又是那个熟悉的陷阱:你的机器人在测试时表现很好,在生产环境里每一场有竞争的清算都输。测试发生在网络平静时;★而清算恰恰发生在不平静的时候。★

那一刻决定胜负的三件事:

import { ComputeBudgetProgram } from "@solana/web3.js";

// 费用按账户竞争。每个竞争的机器人都在写
// 和你相同的仓位账户与金库账户。
const recent = await connection.getRecentPrioritizationFees({
  lockedWritableAccounts: [positionAccount, vaultAccount],
});
const fees = recent.map((r) => r.prioritizationFee).sort((a, b) => a - b);
const median = fees[Math.floor(fees.length / 2)] ?? 0;

instructions.unshift(
  ComputeBudgetProgram.setComputeUnitPrice({
    // 清算的收益是已知的。按收益来定费用,
    // 而不是按一个固定常数。
    microLamports: Math.max(median * 3, 10_000),
  }),
  ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 }),
);

再加上持续重试到 blockhash 过期,以及一条在区块空间被抢时扛得住的提交路径。

用清算奖励来倒推该出多少手续费

清算的收益是确定的,这意味着费用决策是算术,不是猜测。

// 清算奖励(lamport),减去你要付的费用。
const bonusLamports = collateralValue * bonusRate;
const baseFee = 5_000;                      // 签名成本
const priorityCost = (cuPrice * cuLimit) / 1_000_000;

const netIfWon = bonusLamports - baseFee - priorityCost;
const costIfLost = baseFee;                 // ★输掉的比赛照样要付这个★

★最后那一行是大多数机器人忽略的。★ 输掉一场清算竞赛不是免费的 —— 你为一笔"上链后失败"或者"根本没上链"的交易付了基础费。

如果你参与十次清算、赢两次,你的费用成本是十次尝试对两次收益。一个 20% 胜率的机器人,每次赢都得覆盖五次尝试的成本 —— 这会改变"多少费用是理性的"这个判断。

清算特有的失败模式

别人先到了。 你的交易落块然后 revert,因为仓位已经健康了。你为此付了基础费。这是竞争的正常成本。

仓位自己恢复了。 你的交易落块之前价格回去了。结果一样、成因不同 —— 而这指向收紧延迟,不是加钱。

计算预算耗尽。 清算往往要触碰很多账户,计算量大。★CU limit 估低了会让一笔本来能赢的交易失败★ —— 这是这份清单上最不该发生的损失。开发阶段拿一个真实仓位模拟一次。

预言机价格过期。 你用一个协议已经不接受的价格算出了可清算性,交易在协议自己的过期检查上 revert。

上链应该是什么水平

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

在一场有竞争的清算里,落在第 2 个 slot 和第 5 个 slot 的差别,通常就是"拿到奖励"和"白付基础费"的差别。

★行情剧烈时那个数字会拉长★ —— 大部分交易不受影响,少数明显变慢。而清算就活在这少数里,因为造成清算的价格移动,和让网络拥堵的是同一批事件。

该测的是什么

只看胜率,说明不了你输在哪里。

log({
  position,
  eligibleSlot,                 // 越过阈值的时刻
  detectSlot,                   // 你发现的时刻
  submitSlot,
  landSlot,
  detectLag: detectSlot - eligibleSlot,   // ★盯法问题★
  landLag: landSlot - submitSlot,         // ★提交问题★
  outcome,                                // 赢 / revert / 从未上链
});

★detectLag 和 landLag 指向完全不同的解法。★ 检测滞后大,说明该换盯法;落块滞后大,说明是费用、重试或路由。优化错了那一个,就是团队花几个月胜率纹丝不动的原因。

BoltTx 在这里的位置

我们做提交。不做预言机数据源、不做仓位索引、不做协议集成。

提交走我们自建的四区域投递节点,带 SWQoS 路由,不暴露公共 mempool —— 交易在落块之前,传输途中观察不到。这一点在清算场景下有实际意义:你的交易本身就暴露了你要吃哪个仓位。

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

免费领 API key,没有月费:

// 检测那边不用动,只换发送端点。
const sender = new Connection("https://la.bolttx.io/?api-key=YOUR_KEY");

然后在几场有竞争的清算里记录 landLag,做个对比。

常见问题

Solana 清算机器人是怎么工作的? 它盯着借贷仓位的健康度接近清算阈值,一旦某个变得可清算就提交清算交易。协议为平掉仓位支付奖励,而多个机器人会争抢同一个机会。

我的清算机器人为什么总是输? 通常输在提交而不是检测。如果你能很快发现可清算仓位、却晚三个以上 slot 落块,损失在费用、重试行为、或者价格移动带来的拥堵下的路由。

该轮询仓位还是盯预言机? 盯价格源更快,因为仓位变得可清算是价格更新的结果。事先算好哪个仓位在哪个价位翻转,然后对预言机做出反应,而不是重新扫全部。

怎么在不撞限流的前提下监控很多仓位? 用 getMultipleAccounts 批量取,别循环调 getAccountInfo。盯住"接近阈值"的仓位而不是扫描全部,并考虑用预言机订阅替代仓位轮询。

清算机器人的 priority fee 该设多少? 按已知的奖励来定,而不是按固定常数。而且要把"输掉的比赛照样付基础费"算进去 —— 你每赢一次的真实成本,是费用乘以尝试次数。

我的清算交易为什么 revert 了? 常见原因:别的机器人先到了、仓位已经健康;价格在你落块前回升了;计算预算不够;或者你用的预言机价格被协议判定为过期。

清算需要多少计算预算? 比大多数指令多,因为清算要触碰很多账户。开发阶段拿一个真实仓位模拟,把上限设在结果略高处 —— 估低了会让一笔本来能赢的交易失败。

Solana 上做清算现在还赚钱吗? 在竞争激烈的协议上利润很薄,由执行决定胜负。关注度低的协议和不常见的抵押品类型还有空间。但无论哪种,决定因素都是落块速度而不是策略。

清算需要 MEV 保护吗? 私密提交路径有帮助,因为你的交易本身就暴露了你要平哪个仓位。但它不会消除竞争,因为别的机器人可以独立看到同一个可清算仓位。

健康度到多少该触发我的机器人? 在仓位越过阈值之前就开始跟踪,而不是在它越过的那一刻。从 1.05 左右开始盯,你才有时间准备和预热路径;等到低于 1.0 才动,你已经晚起跑了。

清算为什么会扎堆出现? 因为一次价格移动会让很多仓位同时变得可清算。而同一次移动也让网络拥堵 —— 这就是为什么清算机会和困难的提交条件总是一起到来。

免费档 RPC 能跑清算机器人吗? 小规模的监控也许可以。但在一次剧烈行情中拼提交,共享端点是最糟的情况,因为它们恰好在那些时刻最忙。

怎么判断瓶颈在检测还是提交? 记录仓位变得可清算的 slot、你发现它的 slot、以及你的交易落块的 slot。检测滞后和落块滞后指向完全不同的解法。

该做部分清算还是全额清算? 取决于协议规则和你的资金量。部分清算降低资金要求、竞争可能也小一些,但每笔赚得按比例更少,而基础费一分不少。

两个机器人清算同一个仓位会怎样? 先落块的成功。第二个会 revert,因为仓位已经不可清算了 —— 而它照样为一笔"到达链上并失败"的交易付了基础费。

延伸阅读

← 返回博客列表