Skip to main content
LitePart of the Lite plan; Developer includes it too. The ready-made bot comes with Developer, through the private Telegram group. See plans · Get your key

Build a Telegram sniper bot

A call on Telegram moves a coin: a caller posts a mint, and everyone reading it buys at once. The bot that buys first gets the price before the crowd. This guide builds that bot in about a hundred lines of Node.js: your own Telegram account reads the chats you choose, and Lightspeed buys the mint the moment it is posted, in one request. If you would rather not run your own, the ready-made bot at the end of this page comes with Developer.

How it works​

  • A user account, not a bot. A bot made with BotFather reads only the chats it is added to, and a channel only as its admin. An ordinary account, logged in through Telegram's API, sees every chat it has joined, the way the app does. Use a spare account, not your own.
  • The mint in the post. A Solana address is 32 to 44 base58 characters, and a pump.fun mint ends in pump. The bot reads each new post's text, its hidden links and its buttons, and takes the first mint it has not seen before.
  • One request to buy. Lightspeed builds, signs and sends the buy from your Lightspeed wallet; nothing is read from the chain on the way.
  • A report to you. Each buy is sent, with its signature, to a chat you pick: the account's Saved Messages or your own username.

What you need​

  • Node.js 22 or newer, npm install telegram input: GramJS, the Telegram client, and a prompt for the first login.
  • A Telegram api_id and api_hash: log in at my.telegram.org, open API development tools and create an app.
  • A spare Telegram account to listen with, joined to every chat you want to watch. Telegram only sends an account the posts of chats it is a member of.
  • A Lite or Developer plan for Lightspeed, and a funded Lightspeed account: its ls_… key signs the buys.
  • A server next to the API: Frankfurt on Lite; Frankfurt, Amsterdam or New York on Developer (Regions).

The script​

Save it as tg-sniper.mjs.

import { existsSync, readFileSync, writeFileSync } from "node:fs";
import input from "input";
import { TelegramClient } from "telegram";
import { StringSession } from "telegram/sessions/index.js";
import { NewMessage } from "telegram/events/index.js";

// ─── change these ─────────────────────────────────────
const API_ID = 1234567; // my.telegram.org → API development tools
const API_HASH = "0123456789abcdef0123456789abcdef";
const WATCH = []; // chats: "username" or id; empty: list this account's chats and exit
const SENDERS = []; // in groups, only these user ids trigger a buy; empty: anyone
const REPORT_TO = "me"; // "me" is Saved Messages; or your own "username"
const LS_KEY = "ls_…"; // your Lightspeed account key
const BUY_SOL = 0.05; // per call
const SLIPPAGE = 25; // percent; a called coin moves fast
const PRIORITY_FEE = 0.0002; // SOL; on Lite the lever that counts (the Shrine lane takes no tip)
const TIP = 0.001; // SOL per provider copy, paid once by the copy that lands
const PROVIDERS = ["all"]; // Developer: race every lane and the block 0 lane; Lite ignores it
const PUMP_ONLY = true; // only mints ending in "pump"
const SKIP_FORWARDS = true; // a forward is someone else's call, posted again later
const DRY_RUN = true;
// ──────────────────────────────────────────────────────

const API = "https://sol.shrine.trade";
const SESSION_FILE = "telegram.session"; // the account's login: guard it like a password
const MINT_RE = /\b[1-9A-HJ-NP-Za-km-z]{32,44}\b/g; // base58, the length of a Solana address
const senders = new Set(SENDERS.map(String));
const seen = new Set(); // every mint already bought or tried

async function lightspeed(body) {
if (DRY_RUN) { console.log(" dry-run", body); return { success: true }; }
const r = await (await fetch(`${API}/api/lightspeed`, {
method: "POST", headers: { "content-type": "application/json", "x-api-key": LS_KEY }, body: JSON.stringify(body),
})).json();
if (!r.success) throw new Error(`${r.error}: ${r.message}`);
return r;
}

// The post's text plus the links behind words and buttons ("BUY" -> pump.fun/coin/<mint>).
function textOf(msg) {
const links = [
...(msg.entities ?? []).map((e) => e.url),
...(msg.replyMarkup?.rows ?? []).flatMap((row) => row.buttons.map((b) => b.url)),
];
return [msg.message ?? "", ...links.filter(Boolean)].join(" ");
}

// 1. Log in as the listening account. The first run asks for the phone, the code and the 2FA password.
const session = new StringSession(existsSync(SESSION_FILE) ? readFileSync(SESSION_FILE, "utf8") : "");
const client = new TelegramClient(session, API_ID, API_HASH, { connectionRetries: 10 });
client.setLogLevel("error");
await client.start({
phoneNumber: () => input.text("phone number (+44…): "),
phoneCode: () => input.text("code Telegram sent you: "),
password: () => input.password("2FA password: "),
onError: (e) => console.error(e.message),
});
writeFileSync(SESSION_FILE, client.session.save());

if (!WATCH.length) { // no chats yet: print the ones this account is in
for (const d of await client.getDialogs({ limit: 200 })) console.log(String(d.id).padEnd(16), d.title);
process.exit(0);
}
const chats = await Promise.all(WATCH.map((c) => client.getPeerId(c))); // usernames to ids now, not on the first post

// 2. Every new post in a watched chat: find a mint, buy it once.
client.addEventHandler(async (event) => {
const msg = event.message;
if (SKIP_FORWARDS && msg.fwdFrom) return;
if (senders.size && !senders.has(String(msg.senderId))) return;
const mint = [...textOf(msg).matchAll(MINT_RE)].map((m) => m[0])
.find((m) => !seen.has(m) && (!PUMP_ONLY || m.endsWith("pump")));
if (!mint) return;
seen.add(mint); // before the request: a repost cannot buy it again
const lag = Date.now() / 1000 - msg.date; // msg.date is whole seconds: an upper bound
console.log(`call ${mint} in ${event.chatId} from ${msg.senderId}, ≤${lag.toFixed(1)} s after the post`);
const t0 = performance.now();
try {
const r = await lightspeed({
action: "buy", mint, amount: BUY_SOL, slippage: SLIPPAGE,
priorityFee: PRIORITY_FEE, tip: TIP, providers: PROVIDERS,
});
console.log(` sent in ${(performance.now() - t0).toFixed(0)} ms: ${r.signature ?? "dry"}`);
// 3. Report it. Not awaited: the buy is already out.
client.sendMessage(REPORT_TO, { message: `bought ${mint}\n${r.signature ?? "dry run"}` }).catch(() => {});
} catch (e) {
console.error(` buy failed: ${e.message}`);
}
}, new NewMessage({ chats })); // new posts only: edits are another event

console.log(`watching ${chats.length} chats`);

Run it​

  1. Log in and list your chats. Leave WATCH empty and run node tg-sniper.mjs. It asks for the phone number, the code Telegram sends and the 2FA password if the account has one, saves the login to telegram.session and prints every chat the account is in, with its id.
  2. Pick the chats. Put their usernames or ids into WATCH. A private chat has no username: use the id the first run printed.
  3. Dry run. With DRY_RUN = true it prints every call it would buy, who posted it and how long after the post it saw it. Let it run through a few calls and check what it would have bought.
  4. Go live. DRY_RUN = false, a small BUY_SOL, and watch the first fills.

What each part does​

  • Reading the post. Callers often hide the mint behind a word or a button linking to pump.fun, so textOf adds those links to the text. The pattern matches anything shaped like a Solana address; PUMP_ONLY keeps the ones ending in pump, which also drops the wallet and pool addresses a post quotes.
  • The filters. Only posts in WATCH reach the handler. In a group, SENDERS limits it to the callers you trust, so a member pasting a mint buys nothing; the dry run prints each poster's id. Forwards are skipped: a forward repeats a call made earlier, somewhere else. Edits never arrive, since NewMessage is new posts only; for chats that post first and edit the mint in, GramJS has EditedMessage in telegram/events/EditedMessage.js.
  • One buy per mint. Telegram sometimes delivers a post twice, and a call is reposted across channels within seconds. The mint goes into seen before the request leaves, so a second sighting, even one that arrives while the first buy is in flight, buys nothing.
  • The buy. One Lightspeed request, amount in SOL. Lightspeed routes from its own cache: a coin on its curve is bought on pump.fun, a graduated one on PumpSwap. An address that is not a tradable token answers with an error, and nothing is sent.
  • Fees. On Lite the buy goes out on Shrine's staked lane alone, which takes no tip: raise PRIORITY_FEE when calls get crowded. On Developer the copies race; each copy that reverts pays its priority fee and only the one that lands pays the tip, so the budget goes into TIP.
  • The delay line. msg.date is when Telegram took the post, in whole seconds, so ≤1.4 s means somewhere between 0.4 and 1.4 s. Keep the server's clock synced: a clock half a second off moves the number by as much.

Exits​

The script buys and stops there. For a target, a stop and a timer, follow the coin's trades and sell through Lightspeed, as the sniper guide does. subscribe_trades takes the mint itself, so a call needs nothing more:

import { io } from "socket.io-client";

const socket = io("https://sol.shrine.trade", { transports: ["websocket"] });

// after a buy:
socket.emit("subscribe_trades", { mint });
// then the sniper guide's "trade" handler: entry, target, stop, timer, one "100%" sell

Where the time goes​

StepTime
caller presses send, post reaches your accountabout 0.5 to 2 s, on Telegram's side
mint found, filterswell under a millisecond
request reaches Lightspeed from the same cityabout 1 ms
sent to landedabout 100 ms on Lite, about 50 ms raced on Developer
  • Telegram is the slow part. Delivery takes about 0.5 to 2 s and varies by chat and by moment. No bot can beat that; yours can only add nothing to it. Everything after the post arrives should take milliseconds.
  • Close to Telegram and to the API. Telegram's European data centres are in Amsterdam, and accounts with European numbers are usually served from there. A listener in Frankfurt or Amsterdam is a few milliseconds from both Telegram and the API; one on another continent adds a crossing each way.
  • Nothing on the way. Keep the Lightspeed wallet funded and look nothing up between the post and the request: no balance check, no metadata, no chain read. The filters work on the text you already have.
  • One listener. One connection per account. The same session used from two places at once is revoked by Telegram, and two listeners would each buy the same call.
  • De-dup reposts. The same mint arrives from several channels within seconds; without seen every one of them is a buy.
  • Racing on Developer. On Lite the buy goes out on Shrine's staked lane, about 100 ms to landing. Developer races all 8 SWQoS lanes and the block 0 lane at once, about 50 ms (provider racing), which decides the slot when every bot in the chat fires together.

Safety​

  • Dry run first. Watch a few calls with DRY_RUN = true before any SOL moves. A wrong filter buys every address in every post.
  • Small amounts. Fund the Lightspeed wallet with what you snipe with, not more, and withdraw profits.
  • A call is not a reason. Many calls are paid for, many coins are sold into the buyers a call brings, and copies with the same name and ticker under a different mint are posted to catch fast bots. Watch callers you trust, and keep SENDERS and PUMP_ONLY on.
  • Your keys stay yours. Never paste your Lightspeed key, a private key or telegram.session into a chat or a bot: the session file is a full login to the account. Anyone asking for them, "support" included, is a scam.

The ready-made bot​

We run our own Telegram sniper, and it comes with the Developer plan:

  • Watches the chats you set. Channels and groups, the ones you choose.
  • Buys through the race. The same one-request flow as above, raced over Lightspeed's lanes.
  • Buys at the migration. When a called coin's curve is already full, it buys the coin as it migrates, on PumpSwap.
  • Lives in Telegram. Every buy is reported to your Telegram, and you run the bot from a Telegram bot.

To get it:

  1. Get Developer. Compare the plans on the pricing page, then get Developer on the dashboard.
  2. Join the private group. It is invite only; the invite is on your dashboard once Developer is active.
  3. Ask for the bot. Post in the group and we give you access.

Developer also brings provider racing over every lane and the block 0 lane, all three regions and the Ultra Fast Shred Stream for bots of your own. See Plans and limits.