Telegram 机器人是相当一部分 Solana 交易者真正在用的下单方式。界面本身很简单,决定它好不好用的,是消息处理器背后的一切 —— 而那正是大多数实现出问题的地方。
最核心的约束是:用户按下"买入"时期待几秒内看到结果,而失败的时候他们不会去看日志。
把聊天层和交易引擎分开
第一个架构错误,是把交易逻辑塞进消息处理器里。
// ★不要这么写。★
bot.on("callback_query", async (q) => {
const quote = await getQuote(...);
const tx = await buildSwap(quote);
const sig = await connection.sendRawTransaction(tx.serialize());
await bot.answerCallbackQuery(q.id, { text: `已发送: ${sig}` });
});
三个问题,而且全都只在高负载下才浮现:
Telegram 期待一个快速的确认。 把处理器一直挂在报价、构建、签名、发送这一整串上,意味着响应慢和超时。
重启会丢掉在途交易。 状态活在处理器的闭包里,所以交易进行到一半时部署一次,用户拿不到结果,而你也没有记录。
并发没有上限。 五十个用户在同一个新盘上按买入,就是五十个并发的处理器。
★行得通的形状,是在两者之间加一个队列。★
bot.on("callback_query", async (q) => {
const jobId = await queue.push({
userId: q.from.id, action: "buy", mint, amount,
});
await bot.answerCallbackQuery(q.id, { text: "提交中…" });
// ★处理器立刻返回。交易由 worker 负责。★
});
worker 负责发送、重试、把结果收敛到一个终态,然后通过编辑消息把结果推回给用户。聊天层变成了一个视图,而交易引擎变成了可以独立测试、重启和扩容的东西。
密钥处理就是产品本身
每一个 Telegram 交易机器人都要回答同一个问题,而用户就按这个答案来评判它:私钥在谁手里?
机器人持有密钥。 机器人生成钱包并保管密钥。简单、快,同时意味着用户是在把资金托付给你的基础设施。这是大多数机器人采用的模式,也是为什么大多数机器人事故是灾难性的、而不只是让人恼火。
用户自己签名。 机器人构建交易,用户在别处签名。安全,但和一键交易格格不入 —— 那次往返把这件事的意义抵消掉了。
带链上限额的授权。 用户授权特定操作、并限定在特定边界内。搭起来更费事,但失败的影响是有界的,而不是全部。
★如果你持有密钥,下面这些安全要求不是可选的加分项:★
- 静态加密,且加密密钥存放在应用数据库之外
- 私钥绝不出现在任何日志行、错误消息或异常栈里
- 提现限额与确认步骤,即便会话被攻破仍然有效
- ★假设你的 Telegram bot token 迟早会泄漏,并把系统设计成"光有它还不够"★
在界面里明确说清你用的是哪种模式。 经历过机器人事故的用户第一个就问这个,而含糊的回答本身就是一种回答。
如果你的机器人已经搭好、用户在抱怨错过成交,免费领个 BoltTx key 改一行就能测测提交路径。
并发尖峰才是真正的压力测试
Telegram 机器人的流量不是平滑的。它是平的,然后一个代币上线,所有用户在同样几秒里一起动作。
// ★按用户串行,全局限并发。★
const userLocks = new Map();
async function runTrade(userId, job) {
const prev = userLocks.get(userId) ?? Promise.resolve();
const next = prev.then(() => globalLimiter.run(() => execute(job)));
userLocks.set(userId, next.catch(() => {}));
return next;
}
★两个不同的限制,而且两个都必要。★ 按用户串行,防止一个用户连点两下发出两笔买入。全局上限,防止上线尖峰把你的 RPC 限流额度耗尽 —— 到那时候所有用户会同时失败,包括那些本来完全没问题的。
报告终态,不要报告提交
对交易机器人最常见的抱怨是"它说成功了,但其实没有"。这几乎总是下面这个 bug:
const sig = await connection.sendRawTransaction(raw);
await bot.editMessageText("✅ 买入成功!"); // ★错。什么都还没落块。★
一个签名只意味着 RPC 接收了字节。用户需要的是结果,而结果有三种。
await bot.editMessageText("⏳ 已提交…");
const outcome = await resolveTransaction(sig, lastValidBlockHeight);
switch (outcome.status) {
case "success":
await bot.editMessageText(`✅ 已买入 ${amt} — ${short(sig)}`);
break;
case "reverted":
// ★落块了但失败了。用用户听得懂的话解释原因。★
await bot.editMessageText(`❌ 失败: ${explain(outcome.err)}`);
break;
case "expired":
// ★根本没落块。除了这次尝试,没花掉什么。★
await bot.editMessageText("⚠️ 未上链。要重试吗?");
break;
}
★"revert"和"过期"的区别,是用户最需要、却最少拿到的东西。★ revert 意味着他们的交易执行了、并且撞上了某个条件 —— 滑点、余额、某个还不存在的账户。过期意味着它根本没运行过。对两者都只说"失败",用户就无法判断该重试还是该改点什么。
把错误翻译成他们的语言:
| 链上错误 | 该怎么说 |
|---|---|
| slippage exceeded | 价格动了 —— 调高滑点或减小金额 |
| insufficient funds | SOL 不够付金额加手续费 |
| account not found | 需要先创建代币账户 |
| blockhash not found | 没能及时上链 —— 可以放心重试 |
手续费就是你的利润率
如果你按比例收费、并且从自己口袋里付优先费,那么★你的手续费策略就是你的利润率策略★。
不要写死。 固定的优先费在平静时多付,在新盘上线时少付 —— 而那恰恰是用户拿来评判你的那些交易。
按交易规模缩放。 一笔大买入值得比小买入付更多费用。对两者收一样的费,意味着你在某一端的账是错的。
设上限,并且把上限告诉用户。 手续费尖峰里,没有上界的策略就是没有上界的亏损。告诉用户当前条件极端,而不是默默地付、或者默默地失败。
决定由谁付,并且说出来。 用户会注意到实际成本和宣传的百分比对不上,而从区块浏览器上发现这件事,对信任的伤害比手续费本身大得多。
上链应该是什么水平
我们投递节点上的真实交易:中位确认 336 毫秒 —— 不到一个 slot。
★对面向用户的机器人来说,这是一个产品指标,不是基础设施指标。★ 用户比较机器人的标准,就是上线那一刻自己的买入有没有成交 —— 而且他们会因为屈指可数的几次糟糕体验就换掉你。
BoltTx 在这里的位置
我们负责提交。不做聊天层,不托管,不管你的收费模式。
提交走我们自建的四区域投递节点,带 SWQoS 路由,不暴露公共 mempool —— 用户的交易在落块之前、传输途中观察不到。当你的用户都在同一时刻买同一个新盘时,这一点很重要。
你的机器人用你选定的那种密钥模式在本地签名。我们不托管资金、不代签、不修改交易内容。tip 带在交易里、从签名的那个钱包链上支付,交易失败时跟着一起 revert —— 这是 Solana 原子交易的机制决定的。只有到达链上的交易才计费。
免费领 API key,没有月费:
const connection = new Connection("https://la.bolttx.io/?api-key=YOUR_KEY");
常见问题
怎么做一个 Solana 的 Telegram 交易机器人? 用一个队列把聊天层和交易引擎分开。消息处理器立刻确认,由 worker 负责报价、发送、重试,并报告终态。
Telegram 机器人该持有用户私钥吗? 这是大多数机器人采用的模式,因为它才能做到一键交易,同时也意味着用户在把资金托付给你的基础设施。如果选它,就在界面里明说,并把加密、日志卫生、提现限额当作硬性要求。
我的机器人为什么会把没成交说成成交了?
因为它报告的是 sendRawTransaction 返回的签名,而不是结果。签名只意味着 RPC 接收了字节,不意味着任何东西落了块。 先收敛到成功、revert 或过期,再告诉用户。
很多用户同时下单怎么办? 按用户串行,让连点两下不会发出两笔买入;全局限并发,让上线尖峰不至于耗尽限流额度、导致所有用户同时失败。
交易失败时该跟用户说什么? 区分 revert 和过期。revert 是执行了并撞上了滑点之类的条件,必须改点什么;过期是根本没运行,原样重试是合理的。
Telegram 机器人的默认滑点该设多少? 新盘要比成熟交易对高,而且应该允许用户调整。单一的全局默认值,要么对新盘太紧,要么对流动性好的交易对太松。
怎么防止重复点击造成的重复买入? 按用户串行,再加上按 callback query id 去重。光在界面上禁用按钮不够,因为回调可能已经在途了。
优先费该由机器人还是用户付? 两种都行,但要在界面里说清是哪种。用户会注意到实际成本和宣传的百分比对不上,而从区块浏览器上发现这件事,伤害比手续费本身大。
新盘上线时怎么让 Telegram 机器人保持响应? 立刻确认回调,把活儿丢进队列。把处理器一直挂在报价、构建、发送上,恰恰会在量最大的时候造成超时。
机器人重启时在途交易怎么办? 如果状态活在处理器里,它们就丢了 —— 用户拿不到结果,你也没有记录。要把任务和它们的签名持久化,让 worker 重启后能接着把它们收敛掉。
怎么给用户展示交易状态? 编辑原来那条消息走完各个阶段,而不是不断发新消息。"已提交"然后终态,聊天窗口干净,用户也只需要盯一个地方。
每个用户该有自己的钱包吗? 如果你持有密钥,一般该。共享钱包让对账很困难,而且会把一次泄漏变成所有用户的问题。 一人一钱包也让每个用户的链上历史清晰可查。
怎么估算一笔交易需要多少 SOL? 交易金额加基础手续费,加优先费,如果需要创建代币账户还要加租金。只检查交易金额的机器人,会产出让用户困惑的余额不足 revert。
我的用户为什么在打新时抢不到? 通常问题在提交路径,不在机器人逻辑。测一下上线期间"发送到落块"的 slot 距离 —— 队列深度和 RPC 限流恰恰在那一刻一起劣化。
面向用户的机器人用 skipPreflight 安全吗? 安全,而且通常是对的 —— preflight 加一次往返,而且模拟的是错的 slot。开发阶段做模拟,并且在构建之前先校验余额和账户。
怎么按新盘上线的条件测试 Telegram 机器人? 用并发的合成用户去压队列和限流,而不是一次测一个用户。一个对单个交易者能用的机器人,说明不了五十个人同时买入时它是什么表现。