Skip to content

A Telegram trading bot

Tested on BSC testnet before every release

This is an example. Every value in it (names, amounts, addresses) is there to show you how; change any of them to suit you. It’s recipe-telegram-bot.mjs, exactly as our tests run it on testnet. Point it at testnet with MM_API, or leave it for mainnet.

recipe-telegram-bot.mjs
// A Telegram trading bot: ask it for the newest launches and prices, and trade
// from your chat. Every buy and sale asks you to confirm first.
// TELEGRAM_TOKEN=… CHAT_ID=… PRIVATE_KEY=0x… node recipe-telegram-bot.mjs
//
// Commands:
// /launches the five newest launches
// /quote <token> <bnb> what a buy would get
// /buy <token> <bnb> asks you to /confirm
// /sell <token> <percent> asks you to /confirm
// /confirm sends the trade you just asked for
//
// Safety, on purpose:
// CHAT_ID only this chat can trade (everyone else gets read-only answers)
// MAX_BUY=0.05 the most one /buy may spend, in BNB
// Without PRIVATE_KEY the bot answers questions but cannot trade.
// Get TELEGRAM_TOKEN from @BotFather; send your bot a message, and CHAT_ID is in
// https://api.telegram.org/bot<token>/getUpdates. SCRIPT="/launches;/quote 0x… 0.01"
// runs those commands once without Telegram, printing the replies (for testing).
import { MarketMayhem } from '@marketmayhem/sdk';
const maxBuy = Number(process.env.MAX_BUY ?? '0.05');
const tradingChat = String(process.env.CHAT_ID ?? '');
const mm = process.env.PRIVATE_KEY
? MarketMayhem.fromPrivateKey(process.env.PRIVATE_KEY, { api: process.env.MM_API })
: MarketMayhem.readOnly({ api: process.env.MM_API });
const pending = new Map(); // chat -> the trade waiting for /confirm
async function answer(chat, text) {
const [cmd, a, b] = text.trim().split(/\s+/);
const canTrade = Boolean(process.env.PRIVATE_KEY) && String(chat) === tradingChat;
switch (cmd) {
case '/launches': {
const { launches } = await mm.launches({ limit: 5 });
return launches.map((l) => `$${l.symbol} ${l.token}`).join('\n') || 'No launches yet.';
}
case '/quote': {
const q = await mm.quote(a, 'buy', b);
return `${b} BNB buys about ${Number(q.receive.amount).toLocaleString('en-GB')} $${q.token.symbol}.`;
}
case '/buy': {
if (!canTrade) return 'Trading is off for this chat.';
if (!(Number(b) > 0) || Number(b) > maxBuy) return `Buy between 0 and ${maxBuy} BNB (MAX_BUY).`;
const preview = await mm.previewBuy(a, { spend: b });
pending.set(chat, { kind: 'buy', token: a, amount: b });
return `${preview.explain}\nSend /confirm to do it.`;
}
case '/sell': {
if (!canTrade) return 'Trading is off for this chat.';
const percent = Number(b);
if (!(percent > 0 && percent <= 100)) return 'Sell a percent from 1 to 100.';
pending.set(chat, { kind: 'sell', token: a, percent });
return `Sell ${percent}% of your ${a}? Send /confirm to do it.`;
}
case '/confirm': {
const t = pending.get(chat);
if (!t || !canTrade) return 'Nothing to confirm.';
pending.delete(chat);
const r = t.kind === 'buy' ? await mm.buy(t.token, { spend: t.amount }) : await mm.sell(t.token, { percent: t.percent });
return `${r.explain}\n${r.status}: ${r.hash}`;
}
default:
return 'Commands: /launches, /quote <token> <bnb>, /buy <token> <bnb>, /sell <token> <percent>, /confirm';
}
}
const reply = (chat, text) => answer(chat, text).catch((e) => `Couldn't do that: ${e.message}`);
if (process.env.SCRIPT) {
// Test mode: run the commands once, as the trading chat, and print the replies.
const replies = [];
for (const line of process.env.SCRIPT.split(';')) {
const text = await reply(tradingChat, line);
console.log(`> ${line}\n${text}\n`);
replies.push(text);
}
console.log('RESULT', JSON.stringify({ commands: replies.length, failed: replies.filter((r) => r.startsWith("Couldn't")).length }));
process.exit(replies.some((r) => r.startsWith("Couldn't")) ? 1 : 0);
}
// Long polling: Telegram holds each request open until a message arrives.
const TG = `https://api.telegram.org/bot${process.env.TELEGRAM_TOKEN}`;
let offset = 0;
console.log('bot running');
for (;;) {
const { result = [] } = await (await fetch(`${TG}/getUpdates?timeout=50&offset=${offset}`)).json();
for (const u of result) {
offset = u.update_id + 1;
const msg = u.message;
if (!msg?.text) continue;
const text = await reply(msg.chat.id, msg.text);
await fetch(`${TG}/sendMessage`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ chat_id: msg.chat.id, text }) });
}
}