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.
// 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 }) }); }}