Telegram bots are the backbone of Solana trading culture. Trojan and Photon are among the most popular trading interfaces on Solana, and BullX was too until it suspended all trading in June 2026. 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.
Looking for a specific build instead of the from-scratch foundations? We have three ready-made paths: a rug-check bot in 10 minutes (zero dependencies, clone and run), a webhook-driven deployer alert bot, and the data-layer guide for Telegram bot SaaS products if you're building for paying users.
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. If you are cost-sensitive or building cross-chain, our GetBlock Solana RPC review of its free tier and dedicated stack breaks down a cheaper alternative with a genuinely usable free plan.
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);
}
}
}