Development of a Telegram Bot for Portfolio Management
Telegram is the main platform for the crypto community. Portfolio management directly in the messenger without switching between apps — the logical next step. The bot can display balances, send notifications about price changes, execute swaps via DEX aggregators, and track positions in DeFi protocols.
Architecture
Wallet Storage Options
Watch-only mode. User adds a public address, bot only reads data. Zero risk to funds. Suitable for monitoring.
Built-in wallet. Bot generates a key pair, encrypted private key is stored in a database. User sets a password/PIN, which is used to decrypt during signing. Most convenient option for trading, but requires secure storage.
Connect existing wallet. Via WalletConnect v2, user connects MetaMask — transactions are signed in the wallet. Secure, but less convenient (requires mobile wallet nearby).
Stack
Telegram Bot API (telegraf.js)
↓
Bot Server (Node.js + TypeScript)
├── Portfolio Service (balances via Alchemy/DeBank API)
├── DeFi Service (positions: AAVE, Compound, Uniswap)
├── Wallet Service (key generation, encryption)
├── Trading Service (swaps via 1inch/Paraswap API)
└── Alert Service (price alerts, liquidation warnings)
↓
PostgreSQL + Redis (sessions, cache)
Implementation of Core Features
Balances and Portfolio
import { Telegraf, Context } from "telegraf";
import { message } from "telegraf/filters";
const bot = new Telegraf(process.env.BOT_TOKEN!);
bot.command("portfolio", async (ctx) => {
const userId = ctx.from.id;
const user = await userService.getUser(userId);
if (!user?.watchAddress) {
return ctx.reply("Add wallet address: /add_wallet 0x...");
}
await ctx.reply("Loading portfolio...");
const portfolio = await portfolioService.getPortfolio(user.watchAddress);
const message = formatPortfolioMessage(portfolio);
await ctx.reply(message, { parse_mode: "HTML" });
});
function formatPortfolioMessage(portfolio: Portfolio): string {
const totalUSD = portfolio.tokens.reduce((sum, t) => sum + t.valueUSD, 0);
let msg = `<b>Portfolio</b> — $${totalUSD.toFixed(2)}\n\n`;
for (const token of portfolio.tokens.sort((a, b) => b.valueUSD - a.valueUSD)) {
const pct = ((token.valueUSD / totalUSD) * 100).toFixed(1);
msg += `${token.symbol}: ${token.balance.toFixed(4)} ($${token.valueUSD.toFixed(2)}, ${pct}%)\n`;
}
if (portfolio.defiPositions.length > 0) {
msg += `\n<b>DeFi positions:</b>\n`;
for (const pos of portfolio.defiPositions) {
msg += `${pos.protocol}: $${pos.valueUSD.toFixed(2)} (${pos.type})\n`;
}
}
return msg;
}
Price Alerts
interface PriceAlert {
userId: number;
token: string;
targetPrice: number;
direction: 'above' | 'below';
isTriggered: boolean;
}
// Worker that checks alerts every minute
async function checkAlerts() {
const activeAlerts = await db.alerts.findActive();
const tokens = [...new Set(activeAlerts.map(a => a.token))];
const prices = await priceService.getPrices(tokens);
for (const alert of activeAlerts) {
const currentPrice = prices[alert.token];
const triggered =
(alert.direction === 'above' && currentPrice >= alert.targetPrice) ||
(alert.direction === 'below' && currentPrice <= alert.targetPrice);
if (triggered) {
await bot.telegram.sendMessage(
alert.userId,
`🔔 ${alert.token} reached $${currentPrice.toFixed(2)} (target: $${alert.targetPrice})`
);
await db.alerts.markTriggered(alert.id);
}
}
}
Inline Keyboards for Navigation
Convenient bot UX — inline buttons instead of commands:
bot.command("start", async (ctx) => {
await ctx.reply("Main menu", {
reply_markup: {
inline_keyboard: [
[
{ text: "📊 Portfolio", callback_data: "portfolio" },
{ text: "💰 Balances", callback_data: "balances" },
],
[
{ text: "🔄 Swap", callback_data: "swap" },
{ text: "🔔 Alerts", callback_data: "alerts" },
],
[
{ text: "⚙️ Settings", callback_data: "settings" },
],
],
},
});
});
Key Storage Security
If the bot stores users' private keys — this is critical responsibility:
- AES-256-GCM encryption with key = KDF(user_pin + server_secret)
- Server secret stored in AWS Secrets Manager (not in code)
- PIN never stored, only verified through decryption attempt
- Automatic logout after N minutes of inactivity
- Transaction limits (maximum amount per day)
- Whitelist of recipient addresses
Portfolio Data Sources
| Source | What it provides |
|---|---|
| Alchemy Token API | ERC-20 balances per address |
| DeBank API | DeFi positions (AAVE, Compound, Uniswap LP) |
| CoinGecko API | Token prices |
| 1inch API | Swap quotes |
| Etherscan API | Transaction history |
Basic monitoring bot (watch-only, balances, DeFi positions, price alerts) — 2-3 weeks. Version with built-in wallet and swaps — 4-6 weeks including security review.







