一个机器人在一段剧烈波动的行情中途把 SOL 用光了。它已经用同样的余额跑了一个月。
★平均消耗对"你需要多少余额"什么都说明不了。★ 成本是成簇出现的,而且恰好簇在你停不起的那一刻。
按最坏的那一小时估算
错误在于按均值做预算:
// ★错:第一个糟糕的小时就会失败。★
const balance = avgCostPerTrade * expectedTradesPerDay;
波动期有三件事一起动,而且每一件都乘以另外两件:
优先费上升。 争用是按账户的,所有人都想要同一个池子时它就飙升。
失败率上升。 更多 revert、更多过期、更多重试 —— 每一次尝试都为一无所获付了手续费。
交易频率上升。 你的策略恰恰在条件最差时触发得更频繁。
★一个合理的下限,是"在抬高的手续费下连续失败一段时间"的成本 —— 用 getRecentPrioritizationFees 对你争用的那些账户量出来,而不是"一天的平均活动量"。★
const reserve =
worstCaseFeePerAttempt * attemptsPerHour * hoursOfAutonomy
+ rentForExpectedNewAccounts
+ rentExemptMinimum;
hoursOfAutonomy 才是真正的参数 —— 机器人必须在没有人的情况下撑多久。一个在工作时间有人盯着的机器人,需要的比一个整个周末无人值守的少。
把钱包分开
一个钱包干所有事很方便,而且会让每一个问题都更糟:
| 钱包 | 装什么 | 风险 |
|---|---|---|
| ★热钱包 / 交易★ | ★只装运营资金★ | ★按全损来假设★ |
| 手续费支付方 | 付手续费的 SOL | 只会浪费手续费 |
| 金库 | ★其余全部★ | ★永不上线★ |
★热钱包的余额,就是一个被攻破的进程能让你损失的上限。★ 这个视角比任何效率论证都更能定出这个数 —— 它应该只装策略运转所需的量,不多装。
把手续费支付方和交易 authority 分开,又加了一道边界:一把泄漏的支付方密钥能浪费手续费,但动不了仓位。
如果你的余额估算是对的、交易还是会错过,免费领个 BoltTx key 改一行就能测测提交路径。
自动补款不能有竞争
最直观的自动补款,在第一次并发运行时就会重复打款:
// ★坏了:先查再做。★
if (await getBalance(hot) < threshold) {
await transferFromTreasury(amount);
}
两个实例、或者一个实例里两个重叠的定时器,都会在任何一笔转账落块之前读到低余额。
// ★原子认领,外加一次在途检查。★
const claimed = await db.query(
`INSERT INTO topups (wallet, hour_bucket) VALUES ($1, $2)
ON CONFLICT DO NOTHING RETURNING id`,
[hot.toBase58(), currentHourBucket],
);
if (!claimed.rowCount) return;
if (await hasPendingTopUp(hot)) return; // ★有一笔在途就够了★
★一笔在途的补款还没落块,所以余额读出来仍然是低的。★ 不追踪在途转账的话,一个低于阈值的机器人会每个周期都补一次,直到第一笔确认为止。
按比率告警,不要只按事件。 频繁补款意味着储备定小了;一次补款失败意味着机器人马上要停。
把你正坐着的租金收回来
一个交易很多代币的机器人会累积空的代币账户,每一个都锁着什么都不干的租金豁免 lamport:
const accounts = await connection.getParsedTokenAccountsByOwner(
wallet, { programId: TOKEN_PROGRAM_ID },
);
const empty = accounts.value.filter(
(a) => a.account.data.parsed.info.tokenAmount.uiAmount === 0,
);
// 每个账户一条 createCloseAccountInstruction,批量执行。
★这是在收回你自己的资本,不是在赚什么。★ 对一个已经交易过几百个代币的机器人来说这是一笔可观的数,而关闭每个账户只要一条指令,批量做很便宜。
两个注意: 不要关掉一个你马上又要用的账户;还有记得 wrapped SOL —— 关掉那个账户会把它解包回原生 SOL,这有时候正是你要的,有时候是个意外。
监控续航,不是监控余额
一个余额数字,离开消耗速率就什么都说明不了:
const burnPerHour = spentLastDay / 24;
const runwayHours = (balance - rentExemptMinimum) / burnPerHour;
if (runwayHours < 12) alert(`续航 ${runwayHours.toFixed(1)} 小时`);
★按剩余小时数告警,不要按 lamport。★ 一个以 lamport 为单位的阈值至少有一半时间是错的 —— 手续费便宜时太敏感,不便宜时又太晚。续航会自我调整,因为消耗速率随行情移动。
值得一起追踪的:
锁住的租金 vs 花掉的 SOL。 锁住的租金是可收回的资本,把它当费用会低估你实际持有的量。
每笔成功交易的成本。 它上升而成功率不变,意味着手续费在爬 —— 你的储备假设正在悄悄过时。
要干净地停下,不要突然停下
一个在半途归零的机器人,会留下开着的仓位和未收敛的交易。 停机行为要提前决定:
为出场预留。 ★留够在抬高手续费下平掉每一个开仓的量,并且绝不把它花在开仓上。★ 握着平不掉的仓位却没 SOL 了,是这类失败里昂贵的那个版本。
先降级再停机。 低于一个软阈值时,停止开新仓,继续管理已开的。
在事情要紧之前告警。 十二小时续航时的警告是可行动的;归零时的是一次事故。
上链应该是什么水平
我们投递节点上的真实交易:中位确认 336 毫秒 —— 不到一个 slot。
★上链可靠性会直接传导到"你需要多少 SOL"上。★ 每一次过期都意味着一次重建和又一笔手续费,所以一条宽尾巴会抬高你每笔成功交易的成本,并缩短给定余额能买到的续航。
BoltTx 在这里的位置
我们负责提交。钱包结构和出资完全归你。
提交走我们自建的四区域投递节点,带 SWQoS 路由,不暴露公共 mempool —— 交易在落块之前、传输途中观察不到。
你在本地签名。我们不托管资金、不代签、不修改交易内容。★tip 带在交易里、从你自己的钱包链上支付,所以它属于你要估算的那份储备★ —— 交易失败时它跟着一起 revert,这是 Solana 原子交易的机制决定的。没有月费,只有到达链上的交易才计费。
const connection = new Connection("https://la.bolttx.io/?api-key=YOUR_KEY");
常见问题
交易机器人该放多少 SOL? 够在抬高手续费下扛过一段连续失败的量,加上它将创建的账户的租金,再加上租金豁免下限。按平均成本估算,第一个糟糕的小时就会失败。
我的机器人为什么在波动期把 SOL 用光? 因为手续费、失败率和交易频率一起上升,而且互相相乘。按平均条件估算出来的余额,恰恰在最要紧的时候不够用。
机器人该用一个钱包还是几个? 几个。热钱包只装运营资金,因为它的余额就是一个被攻破的进程能损失的上限。把手续费支付方分开能进一步限定损失。
怎么安全地自动补 SOL? 在共享存储里做原子认领,再加一次"是否已有转账在途"的检查。单纯的余额检查会竞争,而一笔在途的补款读出来仍然是低余额。
我的机器人为什么反复补款? 因为在途的那笔还没落块,余额看起来仍然低。要显式追踪在途补款,否则每个周期都会再触发一次,直到第一笔确认。
怎么从空的代币账户收回租金? 关闭它们。每个都会退回它的租金豁免 lamport,而且每个账户只要一条指令,批量做很便宜。交易过很多代币的机器人能收回一笔实数。
租金是费用还是资产? 资产。它是被锁住而不是被花掉的,账户关闭时会退回,所以要和手续费分开记 —— 否则你会低估自己实际持有的资本。
低余额告警该按什么触发? 按剩余续航小时数,不是按 lamport 阈值。 续航会随消耗速率自动调整,而固定阈值在平静时太敏感、在繁忙时又太晚。
续航怎么算? 可花余额除以近期每小时消耗,其中可花余额要扣掉租金豁免下限。用一个滚动窗口重算,让它反映当前的手续费状况。
该为平仓预留 SOL 吗? 该,而且绝不把它花在开仓上。握着平不掉的仓位却用光了,代价远大于错过一次你本来就付不起的开仓。
手续费支付方在交易中途用光了会怎样? 交易在执行之前就失败。如果发生在两笔相关交易之间,你可能被留在一个部分完成的状态里 —— 这正是"为出场预留"重要的原因。
关掉 wrapped SOL 账户会退回我的 SOL 吗? 会,它会解包回原生 SOL。清理时这有时正是你要的、有时是个意外,所以除非有意为之,把它排除在自动清扫之外。
手续费余量该留多少? 够在抬高的优先费下发很多次尝试,不是按基础费发一次。拥堵时单次尝试的花费,可以是网络清闲时的很多倍。
金库钱包该上线吗? 不该。它应该装机器人运转不需要的一切,而补款应该是它和热钱包之间唯一的通路。
怎么知道我的储备假设还成不成立? 持续追踪每笔成功交易的成本。它上升而成功率不变,意味着手续费涨了,你最初的估算已经悄悄过时。
安全的降级模式是什么? 低于软阈值时停止开新仓,继续管理已开的。这保住了出场能力 —— 而那是你输不起的那部分。