Day 33: I Built a Trading Bot That Made $100M Last Year. Here’s How Telegram Changed DeFi Forever.
Manav Gandhi9 min read·Just now--
BonkBot architecture, Telegraf framework, inline keyboards, wallet generation in chat, and why the best UX is invisible infrastructure.
Day 33 was the day I finally understood why people trade millions through a chat app.
Not as a curiosity. As the future of DeFi UX. Today I learned:
- How BonkBot generated $100M revenue in one year
- Why Telegram bots beat traditional dApps for trading
- How to create interactive keyboards with Telegraf
- The 30-second callback query rule that breaks bots
- Why wallet generation happens with zero blockchain knowledge
- How answerCbQuery prevents infinite loading spinners
- The difference between editMessageText and sendMessage
- Why trading through chat is actually more secure
The breakthrough came from understanding the best UX doesn’t feel like blockchain at all — it feels like messaging your friend.
Let me show you what I learned.
The Problem: DeFi UX is Terrible
Here’s the reality of trading crypto today:
Traditional DeFi Flow:
1. Install MetaMask browser extension
2. Write down 12-word seed phrase
3. Fund wallet from CEX
4. Navigate to DEX website
5. Connect wallet (popup)
6. Approve token spending (transaction)
7. Execute swap (another transaction)
8. Check if transaction confirmed
9. Repeat for next tradeTime: 5-10 minutes per trade
Friction: Extreme
Abandonment: ~80% of new usersBonkBot Flow:
1. Open Telegram
2. Type /start
3. Tap "Buy SOL"
4. DoneTime: 10 seconds
Friction: None
Abandonment: <5%Result: BonkBot processed $100 million in fees last year by making DeFi feel like ordering pizza.
Real-Life Analogy: The ATM Revolution
1970s Banking:
- Go to bank branch
- Wait in line
- Fill out withdrawal slip
- Show ID to teller
- Get cash
- Time: 30+ minutes
Post-ATM Banking:
- Insert card
- Enter PIN
- Select amount
- Get cash
- Time: 60 seconds
The Innovation: Not the money itself — the interface to access it.
Telegram Bots = ATMs for DeFi
You’re not learning a new financial system. You’re using a familiar interface (messaging) to access a powerful backend (blockchain).
What is BonkBot?
BonkBot is a Telegram bot that lets you trade Solana tokens entirely through chat.
What it does:
- Generates a wallet for you (no seed phrases to write down)
- Stores your private key (encrypted)
- Lets you buy/sell tokens with buttons
- Shows your portfolio in real-time
- Sends transaction confirmations as messages
Revenue model:
- 1% fee on every transaction
- Volume: Billions in trades
- Result: $100M in revenue in 2024
Reference: BonkBot on DeFiLlama
The insight: People will pay for convenience.
Web3 doesn’t need to feel like Web3. It just needs to work.
Setting Up a Telegram Bot
Step 1: BotFather
BotFather is Telegram’s official bot for creating bots. It’s meta — a bot that makes bots.
Process:
- Open Telegram
- Search
@BotFather - Send
/newbot - Name your bot: “My Trading Bot”
- Username: “my_trading_bot” (must end in “bot”)
- Receive bot token:
123456789:ABCdefGhIjKlMnOpQrStUvWxYz
Bot Token Anatomy:
123456789:ABCdefGhIjKlMnOpQrStUvWxYz
│ │
│ └─ Secret (like password)
└─ Bot ID (like username)Security: ⚠️ Never commit bot tokens to GitHub. Anyone with your token controls your bot.
Real-life analogy: Bot token = key to your car. The car (bot) works for whoever has the key.
Telegraf Framework: The Modern Way
Telegraf is the Node.js/TypeScript framework for building Telegram bots.
Why not use the raw Telegram API?
Approach Code Complexity Type Safety Developer Experience Raw API High (manual HTTP requests) None Poor Telegraf Low (clean abstractions) Full TypeScript Excellent
Installation:
bun init
bun add telegrafBasic bot:
import { Telegraf } from 'telegraf';const bot = new Telegraf('YOUR_BOT_TOKEN');bot.start((ctx) => ctx.reply('Hello! I am your trading bot.'));bot.launch();
That’s it. Your bot is live.
The Three Core Capabilities
1. Text Message Handling
bot.on(message('text'), async (ctx) => {
const userMessage = ctx.message.text;
await ctx.reply(`You said: ${userMessage}`);
});Use case: Commands like “buy 1 SOL” or “show my balance”
2. Callback Queries (Button Clicks)
This is the killer feature. Inline keyboards.
bot.on('callback_query', async (ctx) => {
// CRITICAL: Answer within 30 seconds
await ctx.answerCbQuery();
const action = ctx.callbackQuery.data;
await ctx.reply(`You clicked: ${action}`);
});The 30-second rule:
If you don’t call answerCbQuery() within 30 seconds:
- ❌ Button shows loading spinner forever
- ❌ User thinks bot crashed
- ❌ Terrible UX
- ❌ Telegram may rate-limit your bot
Always answer immediately:
bot.action('buy_sol', async (ctx) => {
// Answer FIRST
await ctx.answerCbQuery('Processing purchase...');
// Then do the work
await executeTrade();
ctx.reply('✅ Trade executed!');
});Real-life analogy: answerCbQuery() is like a cashier saying "I got your order" before making your coffee. Acknowledgment matters.
3. Inline Queries (Not Needed)
Inline queries are when users type @yourbot query in any chat. We won't use this for trading.
Creating Interactive Keyboards
Inline keyboards are the UI of Telegram bots.
Example:
┌──────────────────────────────┐
│ Welcome to Trading Bot! │
├──────────────────────────────┤
│ [ 🔑 Create Wallet ] │
│ [ 👁️ View Address ] │
│ [ 💰 Check Balance ] │
│ [ 💸 Send Tokens ] │
└──────────────────────────────┘Code:
import { Markup } from 'telegraf';const keyboard = Markup.inlineKeyboard([
[Markup.button.callback('🔑 Create Wallet', 'create_wallet')],
[Markup.button.callback('👁️ View Address', 'view_address')],
[Markup.button.callback('💰 Check Balance', 'check_balance')],
[Markup.button.callback('💸 Send Tokens', 'send_tokens')]
]);ctx.reply('Choose an action:', keyboard);
Button types:
Type Purpose Example callback Triggers bot action Trading operations url Opens link View on Solscan pay Payment button Premium features
Most trading bots use 90% callback buttons.
The Start Command
Every Telegram bot responds to /start.
When it’s triggered:
- User opens bot for first time
- User clicks a deep link
- User manually types
/start
Implementation:
bot.start(async (ctx) => {
const welcomeMessage = `
🤖 **Welcome to Solana Trading Bot!**Trade tokens with zero blockchain knowledge.**Features:**
• 🔑 Auto-generated wallet
• 💰 Instant balance checking
• 💸 One-tap token swaps
• 📊 Transaction history
• 🔒 Encrypted key storageChoose an option below:`; return ctx.reply(welcomeMessage, {
parse_mode: 'Markdown',
...Markup.inlineKeyboard([
[Markup.button.callback('🔑 Generate Wallet', 'generate_wallet')],
[
Markup.button.callback('👁️ View Address', 'view_address'),
Markup.button.callback('🔐 Export Key', 'export_key')
],
[
Markup.button.callback('💰 Check Balance', 'check_balance'),
Markup.button.callback('📊 History', 'tx_history')
]
])
});
});
Markdown support:
**bold***italic*`code`[link](url)
Parse mode: Set parse_mode: 'Markdown' or 'HTML'
Generating Wallets
Key insight: Users don’t see the blockchain. They see buttons.
Wallet generation flow:
User: *clicks "Create Wallet"*
Bot: *generates Solana keypair*
Bot: *stores encrypted private key*
Bot: *shows success message*
User: *never knows a private key exists*Implementation:
import { Keypair } from '@solana/web3.js';// In-memory storage (demo only)
const USERS: Record<string, Keypair> = {};bot.action('generate_wallet', async (ctx) => {
try {
const userId = ctx.from?.id;
if (!userId) return;
// Answer callback query FIRST
await ctx.answerCbQuery('Generating wallet...');
// Generate keypair
USERS[userId] = Keypair.generate();
// Update message
ctx.editMessageText('✅ **Wallet created!**\n\nYour wallet is ready to use.', {
parse_mode: 'Markdown',
...Markup.inlineKeyboard([
[Markup.button.callback('👁️ View Address', 'view_address')],
[Markup.button.callback('💰 Check Balance', 'check_balance')]
])
});
} catch (error) {
await ctx.answerCbQuery('❌ Failed to create wallet');
ctx.reply('❌ Error occurred. Please try again.');
}
});
Keypair structure:
{
publicKey: PublicKey, // Wallet address (shareable)
secretKey: Uint8Array, // Private key (NEVER share)
}Storage options:
Storage Pros Cons Use Case In-memory Fast, simple Lost on restart Development Database Persistent Setup complexity Production Encrypted DB Secure Performance cost Production (required)
For production: Encrypt private keys with user-specific passwords or hardware security modules.
Viewing Wallet Address
bot.action('view_address', async (ctx) => {
try {
const userId = ctx.from?.id;
if (!userId) return;
// Check if wallet exists
if (!USERS[userId]) {
return ctx.sendMessage('❌ No wallet found. Create one first.', {
...Markup.inlineKeyboard([
[Markup.button.callback('🔑 Create Wallet', 'generate_wallet')]
])
});
}
await ctx.answerCbQuery('Getting address...');
// Get public key in base58
const address = USERS[userId].publicKey.toBase58();
ctx.sendMessage(
`📍 **Your Wallet Address**\n\n\`${address}\`\n\nShare this to receive SOL and tokens.`,
{
parse_mode: 'Markdown',
...Markup.inlineKeyboard([
[Markup.button.callback('💰 Check Balance', 'check_balance')],
[Markup.button.url('View on Solscan', `https://solscan.io/account/${address}`)]
])
}
);
} catch (error) {
await ctx.answerCbQuery('❌ Failed');
ctx.reply('❌ Error occurred.');
}
});Base58 encoding:
Solana uses Base58 (not Base64) because:
- ✅ No confusing characters: 0, O, I, l removed
- ✅ URL-safe
- ✅ Easy to copy-paste
Example address:
7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsUMessage Editing vs New Messages
Two ways to respond:
ctx.editMessageText()
ctx.editMessageText('Updated content', {
...Markup.inlineKeyboard([...])
});When to use:
- Updating status of same operation
- Changing button options
- Keeping chat clean
Real-life analogy: Erasing a whiteboard and writing new content (same surface).
ctx.sendMessage()
ctx.sendMessage('New content', {
...Markup.inlineKeyboard([...])
});When to use:
- Providing additional information
- Confirming completed actions
- Transaction receipts
Real-life analogy: Adding a new page to a notebook (new entry).
Best practice:
- Use
editMessageText()for status updates - Use
sendMessage()for new information
Example flow:
User clicks "Check Balance"
Bot edits message: "⏳ Checking balance..."
Bot edits again: "💰 Balance: 1.5 SOL"Clean. Organized. No message spam.
Error Handling
Always wrap actions in try-catch:
bot.action('risky_operation', async (ctx) => {
try {
await ctx.answerCbQuery();
// Operation that might fail
const result = await doSomethingRisky();
ctx.reply(`✅ Success: ${result}`);
} catch (error) {
console.error('Operation failed:', error);
// User-friendly error
ctx.reply('❌ Something went wrong. Try again later.');
}
});Common errors:
Error Cause Fix Callback timeout No answerCbQuery Call immediately Message too long Text > 4096 chars Split messages Invalid keyboard Malformed button array Validate structure Rate limit Too many requests Add delays
Loading States
Always provide feedback:
bot.action('check_balance', async (ctx) => {
await ctx.answerCbQuery('Checking balance...');
// Show loading message
const loadingMsg = await ctx.reply('⏳ Fetching from blockchain...');
// Do the work
const balance = await getBalanceFromSolana(userId);
// Delete loading message
await ctx.deleteMessage(loadingMsg.message_id);
// Show result
ctx.reply(`💰 Balance: ${balance} SOL`);
});Why this matters:
Without loading states:
- User clicks button
- Nothing happens for 3 seconds
- User clicks again (duplicate requests)
- Confusion
With loading states:
- User clicks button
- Immediate acknowledgment
- Clear progress indicator
- User waits patiently
UX difference: Massive.
Complete Bot Architecture
┌─────────────────────────────────────┐
│ TELEGRAM USER │
└────────────┬────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ TELEGRAM BOT API │
│ (Message routing & delivery) │
└────────────┬────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ YOUR BOT (Telegraf) │
│ │
│ ┌────────────────────────────┐ │
│ │ Message Handlers │ │
│ │ • /start │ │
│ │ • Text messages │ │
│ │ • Callback queries │ │
│ └────────────────────────────┘ │
│ │
│ ┌────────────────────────────┐ │
│ │ Wallet Manager │ │
│ │ • Generate keypairs │ │
│ │ • Store encrypted keys │ │
│ │ • Manage balances │ │
│ └────────────────────────────┘ │
│ │
│ ┌────────────────────────────┐ │
│ │ Blockchain Interface │ │
│ │ • Check balances │ │
│ │ • Send transactions │ │
│ │ • Query history │ │
│ └────────────────────────────┘ │
└────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ SOLANA BLOCKCHAIN │
│ (RPC endpoints) │
└─────────────────────────────────────┘Three-layer architecture:
- Presentation (Telegram UI)
- Business Logic (Wallet management)
- Data (Blockchain queries)
Security Considerations
Private key storage:
❌ NEVER:
- Store in plain text
- Log to console
- Send over Telegram unencrypted
- Commit to Git
✅ ALWAYS:
- Encrypt at rest
- Use environment variables
- Implement key derivation
- Warn users about export
Encryption example:
import crypto from 'crypto';function encryptKey(privateKey: string, password: string): string {
const key = crypto.scryptSync(password, 'salt', 32);
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
let encrypted = cipher.update(privateKey, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag();
return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;
}Production requirements:
- Database encryption
- SSL/TLS everywhere
- Rate limiting
- Audit logging
- User warnings
Why This Day Mattered
This completes the practical trading infrastructure:
- Day 28: Anchor Framework (build programs)
- Day 29: Cross-Program Invocations (compose programs)
- Day 30: Program Derived Addresses (deterministic accounts)
- Day 31: Private Key Management (secure assets)
- Day 32: Indexing & Real-Time Data (trade competitively)
- Day 33: Telegram Trading Bots (accessible UX)
The progression shows the full stack:
- Build secure programs
- Access data fast
- Make it accessible to everyone
The insight: Technical excellence means nothing if users can’t access it.
BonkBot didn’t invent anything new technically. It just put a better interface on existing infrastructure.
$100M in revenue came from solving UX, not blockchain.
What I’m Still Figuring Out
- Session management — How do you handle users with multiple devices? Do sessions sync across Telegram clients?
- Transaction confirmation — How do you design the flow when a transaction takes 30 seconds? Loading states? Push notifications?
- Slippage protection — How do you warn users about price impact in a chat interface? Can you show price charts in Telegram?
- Multi-wallet support — Should users have multiple wallets in one bot? How do you switch between them in chat?
- Backup/recovery — How do you implement seed phrase backup in Telegram without compromising security?
Open Questions for the Community
💬 For people who’ve built Telegram bots or used BonkBot:
- How do you handle database scaling when you have millions of users? What’s your encryption strategy at scale?
- What’s your experience with transaction failures in chat interfaces? How do you communicate errors to non-technical users?
- Have you implemented referral programs in Telegram bots? What’s the UX pattern that works best?
- How do you handle regulatory compliance (KYC/AML) in a chat interface? Is it even possible?
- What’s the biggest UX challenge you’ve faced building trading bots? How did you solve it?
Resources From Today
→ Week 33 Notes: https://petal-estimate-4e9.notion.site/TG-Bot-for-trading-BonkBot-2657dfd1073580138690cbdff953d56c
→ Full Repository: https://github.com/27manavgandhi/-100xDevs-Cohort3-Web3
→ Telegraf Framework: https://github.com/telegraf/telegraf
→ Telegram Bot API: https://core.telegram.org/bots/api
→ BonkBot Analytics: https://defillama.com/protocol/fees/bonkbot
→ Solana Web3.js: https://solana-labs.github.io/solana-web3.js/
Seed phrase recovery update: Tested with $10 test wallet. Documented process. Posted on Day 34. Accountability delivered.
Day 34 tomorrow. From bots to protocols. Showing up. Building in public.