Build a Crypto Whale Alert Telegram Bot in Under an Hour
The cleanest way to get crypto alerts is Telegram. No app fatigue, no notification spam, just instant DMs when something matters. In this tutorial we'll build a bot that scans famous Ethereum wallets and DMs you when they move $50k+ — using only free APIs and free hosting.
Total time: ~45 minutes. Total cost: $0. Code lives in ~120 lines of Node.js.
A Telegram bot that sends you messages like:
"🐋 Vitalik.eth received 1,247 ETH (~$4.4M)" — with a link to Etherscan, every time a tracked wallet does something significant.
Stack
- Node.js 20+ (your local machine, then Railway)
- Telegram Bot API — free, no rate limit for normal use
- Etherscan API — free, 1 req/5s without key, 5 req/sec with free key
- CoinGecko — free, for live ETH price
- Railway (or Render / Fly.io) — free tier hosts the bot 24/7
Step 1 — Create the bot via BotFather (3 min)
- Open Telegram, search
@BotFather, start a chat. - Send
/newbot. - Pick a display name (e.g. "MyWhaleAlerts").
- Pick a username ending in
bot(e.g.my_whale_alerts_bot). - BotFather sends you a token like
123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11. Save it.
Now find your own Telegram chat ID:
- Search
@userinfobot, send/start. It replies with your numeric ID.
Step 2 — Set up the project (5 min)
mkdir whale-bot && cd whale-bot
npm init -y
npm install dotenv
# Create .env (DO NOT commit)
echo "TELEGRAM_BOT_TOKEN=YOUR_BOT_TOKEN" > .env
echo "TELEGRAM_CHAT_ID=YOUR_CHAT_ID" >> .env
echo "ETHERSCAN_API_KEY=YOUR_OPTIONAL_KEY" >> .env
echo ".env" > .gitignore
Update package.json to add "type": "module" so we can use ESM imports.
Step 3 — The bot code (30 min)
Save this as bot.js:
import 'dotenv/config';
const TG_TOKEN = process.env.TELEGRAM_BOT_TOKEN;
const CHAT_ID = process.env.TELEGRAM_CHAT_ID;
const ETH_KEY = process.env.ETHERSCAN_API_KEY || '';
const SCAN_INTERVAL_MS = 60_000;
const MIN_USD = 50_000;
const WHALES = [
{ addr: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', label: 'Vitalik.eth' },
{ addr: '0x28C6c06298d514Db089934071355E5743bf21d60', label: 'Binance 14' },
{ addr: '0xF977814e90dA44bFA03b6295A0616a897441aceC', label: 'Binance 8' },
{ addr: '0x71660c4005BA85c37ccec55d0C4493E66Fe775d3', label: 'Coinbase 1' },
{ addr: '0x503828976D22510aad0201ac7EC88293211D23Da', label: 'Coinbase 2' },
{ addr: '0x2910543Af39abA0Cd09dBb2D50200b3E800A63D2', label: 'Kraken' },
{ addr: '0x0000000000A5c50F35baeD3b2C76b6e78D03d35F', label: 'Wintermute' },
{ addr: '0x5754284f345afc66a98fbB0a0Afe71e0F007B949', label: 'Tether Treasury' },
];
const seenHashes = new Set();
let ethPrice = 3500;
async function refreshEthPrice() {
try {
const r = await fetch('https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd');
const j = await r.json();
if (j.ethereum?.usd) ethPrice = j.ethereum.usd;
} catch {}
}
async function sendTelegram(text) {
try {
await fetch(`https://api.telegram.org/bot${TG_TOKEN}/sendMessage`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
chat_id: CHAT_ID,
text,
parse_mode: 'HTML',
disable_web_page_preview: true,
}),
});
} catch (e) { console.error('[tg]', e.message); }
}
async function fetchTxns(addr) {
const keyParam = ETH_KEY ? `&apikey=${ETH_KEY}` : '';
const url = `https://api.etherscan.io/api?module=account&action=txlist`
+ `&address=${addr}&page=1&offset=5&sort=desc${keyParam}`;
try {
const r = await fetch(url);
const j = await r.json();
if (j.status !== '1' || !Array.isArray(j.result)) return [];
return j.result;
} catch { return []; }
}
async function scanOnce() {
await refreshEthPrice();
for (const whale of WHALES) {
const txns = await fetchTxns(whale.addr);
for (const tx of txns) {
if (seenHashes.has(tx.hash)) continue;
seenHashes.add(tx.hash);
const ethVal = parseFloat(tx.value) / 1e18;
const usdVal = ethVal * ethPrice;
if (usdVal < MIN_USD) continue;
const isOutgoing = tx.from.toLowerCase() === whale.addr.toLowerCase();
const verb = isOutgoing ? '📤 sent' : '📥 received';
const msg =
`${whale.label} ${verb} ${ethVal.toFixed(2)} ETH ` +
`(~$${(usdVal/1000).toFixed(0)}K)\n\n` +
`View on Etherscan`;
await sendTelegram(msg);
}
// Throttle for free-tier rate limit
await new Promise(r => setTimeout(r, ETH_KEY ? 250 : 6000));
}
// Cap memory
if (seenHashes.size > 1000) {
const toRemove = seenHashes.size - 800;
let removed = 0;
for (const h of seenHashes) {
if (removed++ >= toRemove) break;
seenHashes.delete(h);
}
}
}
console.log('Whale bot starting. Scan interval:', SCAN_INTERVAL_MS, 'ms');
sendTelegram('🤖 Whale bot online. Tracking ' + WHALES.length + ' wallets.');
scanOnce();
setInterval(() => scanOnce().catch(e => console.error('[scan]', e.message)), SCAN_INTERVAL_MS);
Run it locally to test:
node bot.js
Within ~60 seconds you should get a "🤖 Whale bot online" Telegram message. After ~5–10 minutes you'll start getting actual whale alerts (assuming any of the tracked wallets moved size).
Step 4 — Deploy to Railway (free tier, 5 min)
- Sign up at railway.app with your GitHub.
- Push your bot to a private GitHub repo.
- Railway → New Project → Deploy from GitHub repo.
- Settings → Variables → add
TELEGRAM_BOT_TOKEN,TELEGRAM_CHAT_ID,ETHERSCAN_API_KEY. - Railway auto-detects
node bot.js; if not, set start command in Settings. - Deploy. Within ~60 seconds you'll see "🤖 Whale bot online" in Telegram.
Railway gives you 500 hours/month free — way more than the 720 hours a month requires for 24/7 uptime. Use Render or Fly.io if you prefer; same pattern.
Customizations you'll want
1. Per-wallet thresholds
Vitalik moves $1M+ regularly; you might only care about $5M+. Add a per-wallet minUsd:
const WHALES = [
{ addr: '0xd8dA...96045', label: 'Vitalik.eth', minUsd: 5_000_000 },
{ addr: '0x28C6...1d60', label: 'Binance 14', minUsd: 100_000 },
// ...
];
// Then in scanOnce:
if (usdVal < (whale.minUsd ?? MIN_USD)) continue;
2. ERC-20 token transfers
Currently the bot tracks ETH only. To track stablecoins (USDC/USDT) and other tokens, swap action=txlist with action=tokentx. Each tx now has tokenSymbol, tokenName, tokenDecimal, and the value is in token units.
3. Multi-user support
To let other people subscribe to your bot, add a Supabase database, store chat_id per user, and listen for /start commands. The full code for this is in the AlphaDesk Pro tier.
4. Solana support
Use Helius (free 100k req/month) instead of Etherscan. Their parsed-transaction endpoint decodes Solana swaps into structured JSON, much nicer than raw RPC.
Production gotchas
- Rate limits: Without an Etherscan key you get ~1 req/5s. With a free key, 5 req/sec. For 8 wallets every 60s, free key is plenty.
- Bot getting blocked: If you spam too many messages too fast, Telegram throttles to 1 msg/sec for the same chat. The code above is sequential so you're fine.
- Memory leak: Without the
seenHashestrim, the Set grows unbounded over weeks. The code caps at 1,000 entries. - Environment variables: Never commit your
.env. Add it to.gitignore. Railway/Render reads from their dashboard, not the repo.
AlphaDesk Pro tier ships a polished version of this bot — multi-user, Supabase-backed, with admin panel and Stripe subscriptions if you want to charge for access.
Get AlphaDesk Pro →FAQ
Can I monetize this bot?
Yes. Easiest path: free tier (3 wallets), paid tier ($5-9/mo, unlimited wallets + custom thresholds). Use Stripe Checkout. The code template for this is in AlphaDesk Pro.
How do I avoid spam alerts?
Tighten the threshold. $50k is sensitive; $250k is restrictive. Or filter by direction (only alerts on inflows, not outflows).
What if the bot dies overnight?
Railway auto-restarts crashed processes. Add a process-level error handler at the top:
process.on('uncaughtException', e => console.error('[crash]', e));
process.on('unhandledRejection', e => console.error('[unhandled]', e));
Can I add multiple chains (BSC, Polygon, Arbitrum)?
Yes. Each EVM chain has its own Etherscan-clone (BscScan, Polygonscan, Arbiscan). Same API shape, just swap the base URL. Helius for Solana.