Telegram bots are the backbone of Solana trading culture. BullX, Trojan, Photon — the most popular trading interfaces on Solana are Telegram bots. But you do not need to build a full trading platform to benefit from a custom bot. A simple bot that monitors wallets, sends price alerts, or executes basic swaps can be built in a weekend.
This tutorial walks you through building a Solana Telegram bot from scratch. We will cover the bot framework (grammY), Solana blockchain interaction (@solana/web3.js), data from RPC providers, and deployment. By the end, you will have a working bot that you can extend with whatever functionality you need.
Prerequisites
You need:
- Node.js 20+ installed
- Basic TypeScript/JavaScript knowledge
- A Telegram account
- A Solana RPC endpoint (we will set this up)
- Familiarity with how Solana transactions work
Step 1: Create Your Telegram Bot
Every Telegram bot starts with BotFather.
- Open Telegram and search for
@BotFather.
- Send
/newbot.
- Choose a name (display name) and username (must end in
bot).
- BotFather gives you an API token. Save it — this is your bot's authentication key.
Security note: Never commit your bot token to version control. Use environment variables.
Step 2: Project Setup
Initialize a new TypeScript project:
mkdir solana-tg-bot && cd solana-tg-bot
npm init -y
npm install grammy @solana/web3.js dotenv
npm install -D typescript @types/node tsx
npx tsc --init
Update tsconfig.json to use modern settings:
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "bundler",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*"]
}
Create a .env file:
BOT_TOKEN=your_telegram_bot_token
SOLANA_RPC_URL=https://mainnet.helius-rpc.com/?api-key=YOUR_KEY
HELIUS_API_KEY=your_helius_api_key
Step 3: Choose Your RPC Provider
Your bot needs a Solana RPC endpoint to read blockchain data and submit transactions. The free public endpoint (https://api.mainnet-beta.solana.com) has aggressive rate limits and is unreliable for production use. You need a dedicated provider.
Recommended Providers
Helius is the top choice for Solana bot development. Beyond standard RPC, Helius provides:
- Enhanced APIs for parsed transaction history, token metadata, and NFT data — saving you from writing complex parsing logic.
- Webhooks for real-time event notifications (wallet activity, token transfers) without polling.
- DAS (Digital Asset Standard) API for comprehensive token and NFT data.
- Free tier: 100K requests/day. More than enough for development and small bots.
Shyft offers similar enhanced APIs with a focus on parsed data:
- Transaction parsing, token data, and wallet portfolios via REST APIs.
- GraphQL API for flexible data queries.
- Callback/webhook support for real-time monitoring.
QuickNode provides high-performance RPC with add-ons:
- Solana-specific add-ons for token balances, NFT data, and priority fee estimation.
- Streams (webhooks) for real-time blockchain event monitoring.
- Global edge network for low-latency responses.
For this tutorial, we will use Helius. Sign up at helius.dev and grab an API key.
Step 4: Basic Bot Structure
Create src/bot.ts:
import "dotenv/config";
import { Bot, Context, session } from "grammy";
const bot = new Bot(process.env.BOT_TOKEN!);
// Session data for tracking user preferences
interface SessionData {
watchedWallets: string[];
priceAlerts: { token: string; targetPrice: number; direction: "above" | "below" }[];
}
bot.use(session({ initial: (): SessionData => ({
watchedWallets: [],
priceAlerts: [],
})}));
// Start command
bot.command("start", (ctx) => {
ctx.reply(
"Welcome to Solana Monitor Bot!\n\n" +
"Commands:\n" +
"/price <token> - Get current price\n" +
"/watch <wallet> - Track a wallet\n" +
"/alert <token> <price> <above|below> - Set price alert\n" +
"/balance <wallet> - Check SOL balance\n" +
"/portfolio <wallet> - View token holdings"
);
});
bot.start();
console.log("Bot is running...");
Run it: npx tsx src/bot.ts. Send /start to your bot on Telegram. If it replies, the foundation works.
Step 5: Solana Connection and Balance Checking
Create src/solana.ts:
import { Connection, PublicKey, LAMPORTS_PER_SOL } from "@solana/web3.js";
const connection = new Connection(process.env.SOLANA_RPC_URL!, "confirmed");
export async function getSOLBalance(address: string): Promise<number> {
const pubkey = new PublicKey(address);
const balance = await connection.getBalance(pubkey);
return balance / LAMPORTS_PER_SOL;
}
export async function getTokenBalances(address: string) {
// Using Helius enhanced API for parsed token balances
const response = await fetch(
`https://api.helius.xyz/v0/addresses/${address}/balances?api-key=${process.env.HELIUS_API_KEY}`
);
return response.json();
}
export function isValidSolanaAddress(address: string): boolean {
try {
new PublicKey(address);
return true;
} catch {
return false;
}
}
Add the balance command to bot.ts:
import { getSOLBalance, isValidSolanaAddress } from "./solana.js";
bot.command("balance", async (ctx) => {
const address = ctx.match?.trim();
if (!address || !isValidSolanaAddress(address)) {
return ctx.reply("Usage: /balance <solana_address>");
}
try {
const balance = await getSOLBalance(address);
ctx.reply(`Balance: ${balance.toFixed(4)} SOL`);
} catch (error) {
ctx.reply("Error fetching balance. Check the address and try again.");
}
});
Step 6: Token Price Lookup
Create src/prices.ts. We will use Birdeye's public API or Jupiter's price API:
interface TokenPrice {
symbol: string;
price: number;
change24h: number;
}
// Jupiter Price API (free, no key required)
export async function getTokenPrice(mintAddress: string): Promise<TokenPrice | null> {
try {
const response = await fetch(
`https://api.jup.ag/price/v2?ids=${mintAddress}`
);
const data = await response.json();
const priceData = data.data?.[mintAddress];
if (!priceData) return null;
return {
symbol: priceData.mintSymbol || "UNKNOWN",
price: parseFloat(priceData.price),
change24h: 0, // Jupiter price API v2 focuses on current price
};
} catch {
return null;
}
}
// Common token mint addresses
export const KNOWN_TOKENS: Record<string, string> = {
sol: "So11111111111111111111111111111111111111112",
usdc: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
jup: "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN",
jto: "jtojtomepa8beP8AuQc6eXt5FriJwfFMwQx2v2f9mCL",
bonk: "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263",
wif: "EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm",
pyth: "HZ1JovNiVvGrGNiiYvEozEVgZ58xaU3RKwX8eACQBCt3",
};
export function resolveTokenMint(input: string): string | null {
const lower = input.toLowerCase();
if (KNOWN_TOKENS[lower]) return KNOWN_TOKENS[lower];
// Check if input is already a mint address
if (input.length >= 32 && input.length <= 44) return input;
return null;
}
Add the price command:
import { getTokenPrice, resolveTokenMint } from "./prices.js";
bot.command("price", async (ctx) => {
const input = ctx.match?.trim();
if (!input) return ctx.reply("Usage: /price <token_symbol_or_mint>");
const mint = resolveTokenMint(input);
if (!mint) return ctx.reply("Unknown token. Try a symbol (sol, jup) or paste a mint address.");
const price = await getTokenPrice(mint);
if (!price) return ctx.reply("Could not fetch price for this token.");
ctx.reply(
`${price.symbol}\n` +
`Price: $${price.price < 0.01 ? price.price.toExponential(2) : price.price.toFixed(4)}`
);
});
Step 7: Wallet Monitoring with Webhooks
This is where it gets powerful. Instead of polling for changes, we use Helius webhooks to get notified instantly when a watched wallet has activity.
Create src/webhooks.ts:
import express from "express";
const app = express();
app.use(express.json());
// Store chat IDs for wallet notifications
const walletSubscriptions = new Map<string, Set<number>>();
export function subscribeWallet(wallet: string, chatId: number) {
if (!walletSubscriptions.has(wallet)) {
walletSubscriptions.set(wallet, new Set());
// Register webhook with Helius
registerHeliusWebhook(wallet);
}
walletSubscriptions.get(wallet)!.add(chatId);
}
async function registerHeliusWebhook(wallet: string) {
await fetch(`https://api.helius.xyz/v0/webhooks?api-key=${process.env.HELIUS_API_KEY}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
webhookURL: `${process.env.WEBHOOK_URL}/helius-webhook`,
transactionTypes: ["TRANSFER", "SWAP"],
accountAddresses: [wallet],
webhookType: "enhanced",
}),
});
}
// Webhook endpoint that Helius calls
app.post("/helius-webhook", (req, res) => {
const transactions = req.body;
for (const tx of transactions) {
// Process and notify subscribers
const involvedAccounts = tx.accountData?.map((a: any) => a.account) || [];
for (const [wallet, subscribers] of walletSubscriptions) {
if (involvedAccounts.includes(wallet)) {
const message = formatTransaction(tx, wallet);
for (const chatId of subscribers) {
// Send notification via bot (needs bot reference)
globalThis.botInstance?.api.sendMessage(chatId, message);
}
}
}
}
res.sendStatus(200);
});
function formatTransaction(tx: any, wallet: string): string {
const type = tx.type || "UNKNOWN";
const sig = tx.signature?.slice(0, 8) || "???";
return (
`Wallet Activity Detected\n` +
`Type: ${type}\n` +
`Signature: ${sig}...\n` +
`Explorer: https://solscan.io/tx/${tx.signature}`
);
}
export function startWebhookServer(port = 3333) {
app.listen(port, () => console.log(`Webhook server on port ${port}`));
}
Install express: npm install express @types/express
Step 8: Price Alert System
Create src/alerts.ts:
import { getTokenPrice } from "./prices.js";
interface PriceAlert {
chatId: number;
token: string;
mint: string;
targetPrice: number;
direction: "above" | "below";
}
const activeAlerts: PriceAlert[] = [];
export function addAlert(alert: PriceAlert) {
activeAlerts.push(alert);
}
export function removeAlertsForChat(chatId: number) {
const indices = activeAlerts
.map((a, i) => a.chatId === chatId ? i : -1)
.filter(i => i !== -1)
.reverse();
for (const i of indices) activeAlerts.splice(i, 1);
}
export async function checkAlerts(
notify: (chatId: number, message: string) => void
) {
const uniqueMints = [...new Set(activeAlerts.map(a => a.mint))];
for (const mint of uniqueMints) {
const price = await getTokenPrice(mint);
if (!price) continue;
const triggered = activeAlerts.filter(a =>
a.mint === mint &&
((a.direction === "above" && price.price >= a.targetPrice) ||
(a.direction === "below" && price.price <= a.targetPrice))
);
for (const alert of triggered) {
notify(
alert.chatId,
`Price Alert Triggered!\n` +
`${alert.token.toUpperCase()} is now $${price.price.toFixed(6)}\n` +
`Target: ${alert.direction} $${alert.targetPrice}`
);
// Remove triggered alert
const idx = activeAlerts.indexOf(alert);
if (idx !== -1) activeAlerts.splice(idx, 1);
}
}
}
Start a polling interval in your main bot file:
import { checkAlerts, addAlert } from "./alerts.js";
// Check alerts every 30 seconds
setInterval(() => {
checkAlerts((chatId, message) => {
bot.api.sendMessage(chatId, message);
});
}, 30_000);
Step 9: Portfolio Overview Command
Combine Helius's enhanced APIs for a rich portfolio command:
bot.command("portfolio", async (ctx) => {
const address = ctx.match?.trim();
if (!address || !isValidSolanaAddress(address)) {
return ctx.reply("Usage: /portfolio <solana_address>");
}
try {
const [solBalance, tokenData] = await Promise.all([
getSOLBalance(address),
getTokenBalances(address),
]);
let message = `Portfolio for ${address.slice(0, 4)}...${address.slice(-4)}\n\n`;
message += `SOL: ${solBalance.toFixed(4)}\n\n`;
if (tokenData.tokens && tokenData.tokens.length > 0) {
message += "Tokens:\n";
const topTokens = tokenData.tokens
.filter((t: any) => t.amount > 0)
.slice(0, 10);
for (const token of topTokens) {
const decimals = token.decimals || 0;
const amount = token.amount / Math.pow(10, decimals);
message += `${token.symbol || token.mint.slice(0, 8)}: ${amount.toLocaleString()}\n`;
}
if (tokenData.tokens.length > 10) {
message += `\n...and ${tokenData.tokens.length - 10} more tokens`;
}
}
ctx.reply(message);
} catch (error) {
ctx.reply("Error fetching portfolio. Try again later.");
}
});
Step 10: Deployment
For production deployment, you have several options.
VPS Deployment (Recommended)
A simple VPS (DigitalOcean, Hetzner, Railway) running your bot with PM2:
npm install -g pm2
npm run build
pm2 start dist/bot.js --name solana-tg-bot
pm2 save
pm2 startup
Key Production Considerations
- Error handling. grammY has built-in error handling. Use
bot.catch() to handle errors gracefully instead of crashing.
- Rate limiting. Both Telegram and your RPC provider have rate limits. Implement request queuing for busy bots.
- Persistence. The in-memory alert and subscription storage we built works for development but does not survive restarts. Use SQLite, PostgreSQL, or Redis for production.
- Graceful shutdown. Handle SIGTERM to clean up webhook registrations and save state.
bot.catch((err) => {
console.error("Bot error:", err);
});
process.on("SIGTERM", () => {
bot.stop();
process.exit(0);
});
Extending Your Bot
Once the foundation is working, you can add:
- Swap execution via Jupiter API — let users swap tokens directly from Telegram. This requires careful security handling (private keys, transaction signing).
- New token alerts — monitor Pump.fun or Raydium for new token launches and notify subscribers.
- Inline keyboards — grammY supports interactive buttons for confirming trades, navigating menus, and toggling settings.
- Group chat support — let multiple users in a group share alerts and portfolio monitoring.
- Historical charts — generate price chart images using libraries like
chartjs-node-canvas and send them as photos.
Security Best Practices
If your bot handles any value (executing swaps, holding keys):
- Never store private keys in plain text. Use encrypted key storage or hardware security modules.
- Validate all user input. Addresses, amounts, token symbols — validate everything before processing.
- Implement user authentication. For sensitive operations, require a PIN or password.
- Rate limit user commands. Prevent abuse by limiting how frequently users can trigger resource-intensive operations.
- Log everything. Keep audit logs of all commands and transactions for debugging and security review.
Conclusion
Building a Solana Telegram bot is accessible to any developer comfortable with TypeScript and APIs. The combination of grammY for the bot framework, @solana/web3.js for blockchain interaction, and enhanced APIs from Helius, Shyft, or QuickNode gives you everything you need.
Start with the basics — price lookups and balance checks. Add wallet monitoring with webhooks. Then extend based on your specific needs. The Solana ecosystem's rich API infrastructure means you can build sophisticated functionality without parsing raw transaction data yourself.
Explore Solana developer tools, RPC providers, and APIs in our tool directory.