DeFi Bots Series — Part 3: Telegram Bot Lite, Portfolio RPC, and a Lean Path to the Scheduler

DeFi Bots Series — Part 3: Telegram Bot Lite, Portfolio RPC, and a Lean Path to the Scheduler

9/29/20256 min • defi
SolanaTelegramSupabasePrivyJupiterMeteoraDLMMBotsSchedulerTypeScriptPython

TL;DR — I turned the Telegram bot into a lean, production-friendly surface that our upcoming DeFi strategy scheduler can ping directly. We kept Redis and Supabase, disabled Kafka, removed AI-agent hops for core commands, pulled SOL & SPL balances via RPC, fetched prices via Jupiter V3, stitched PnL to Supabase, and made the UI snappy with proper inline buttons + pagination. It’s the calm before the orchestration storm.


Why a “Lite” bot?

For the last six months I worked with the Singapore crew at taigo.ai. I built (and rebuilt) a fair bit:

  • Telegram MiniApp + auth and wallet UX (Privy)
  • Big refactors across the Telegram bot
  • The DeFi server that powers execution:
    • Meteora DLMM (open/close/rebalance)
    • Jupiter swaps
    • pump.fun flows
    • Jito tips and tx-acceleration ergonomics

That stack got feature-rich. Which is great… until you need to ship a focused scheduler that opens positions, monitors them, and reacts. I want one narrow surface: a bot that tells me what’s going on and receives alerts—not a festival of agents and brokers.

So I made the bot Lite:

  • keep Redis, Supabase, Privy;
  • no Kafka;
  • no AI agent for basic commands (/start, /asset, /wallet, /delegate);
  • keep the door open to reintroduce agents later behind a flag, not in the hot path.

The vibe: progress in chat

ObCat, [28/9/25 23:45]
/start
 
AIGEXbot, [28/9/25 23:45]
✅ Received. (Lite mode: Kafka disabled)
 
ObCat, [29/9/25 1:18]
/asset
 
AIGEXbot, [29/9/25 1:18]
✅ Received. (Lite mode: Kafka disabled)
 
ObCat, [29/9/25 2:49]
/start
 
AIGEXbot, [29/9/25 2:49]
💼 Your Wallet
━━━━━━━━━━━━━━━━━━
💳 Wallet Type: privy
📍 Address: 2yhmpEANAmircavh8wRJgWBcCYzUcb3gP88peqpTrQ5b
💰 Balance: 0.1959 SOL  (~$41.11)
💵 Price: $209.8456/SOL
 
AIGEXbot, [29/9/25 2:49]
Hello! I'm Taigo — your smart crypto assistant!
Taigo in, Easy win!
✅ Check holdings: /asset
✅ Manage wallet: /wallet
✅ Monitor LPs: /monitor
 
ObCat, [29/9/25 3:10]
/wallet
 
AIGEXbot, [29/9/25 3:10]
✅ Received. (Lite mode: Kafka disabled)
 
ObCat, [29/9/25 3:29]
/asset
 
AIGEXbot, [29/9/25 3:30]
My Holdings
💎 SOL: 0.1959 SOL  ($ 40.93) — Price $ 208.94/SOL
💳 Wallet: 2yhmpEANAmircavh8wRJgWBcCYzUcb3gP88peqpTrQ5b
📊 Portfolio: $ 72.34  (Tokens: $ 31.41 + SOL: $ 40.93)
 
📌 USDC (https://t.me/aigex_miniapp_bot?start=trade-EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v-USDC)
   💰 0.0671 SOL  ($14.01)
   💵 Price: $0.999611   🎉 PnL: +%
 
📌 6p6x…GiPN
   💰 0.0408 SOL  ($8.52)
   💵 Price: $7.630838   🎉 PnL: +%
 
📌 C3Dw…pump
   💰 0.0131 SOL  ($2.73)
   💵 Price: $0.014445   🎉 PnL: +%
 
📌 NFTLX (https://t.me/aigex_miniapp_bot?start=trade-BkCjdHWuSxFhzZhqD9kMnKtV4edkpobmxVowsB5cLgar-NFTLX)
   💰 0.0076 SOL  ($1.58)
   💵 Price: $0.{4}22   🎉 PnL: +%
 
📌 6W4g…pump
   💰 0.006 SOL  ($1.26)
   💵 Price: $0.{5}4   🎉 PnL: +%
 
*Page 1/4 — Tap token to trade.
 
ObCat, [29/9/25 3:36]
/wallet
 
AIGEXbot, [29/9/25 3:36]
💼 Your Wallet
━━━━━━━━━━━━━━━━━━
💳 Type: privy
📍 Solana: 2yhmpEANAmircavh8wRJgWBcCYzUcb3gP88peqpTrQ5b
💰 SOL: 0.1959 SOL  (~$ 41.06)  — Price $ 209.62/SOL
📊 Portfolio: $ 72.47  (Tokens: $ 31.41 + SOL: $ 41.06)
🔑 Delegation ID: None
 
ObCat, [29/9/25 4:02]
/delegate
 
AIGEXbot, [29/9/25 4:02]
✅ You're currently delegated. To revoke server-side consent, click below:

Tiny detail, big payoff: the bot now feels instant and honest about what it’s doing.

What I changed (and why)

1) A single Lite flag controls the surface

  • .env.lite: LITE_MODE=1
  • Load it early in aigexbot/__init__.py so imports see it.
  • Expose is_lite() in bot_instance.py.
  • In bot_server/__init__.py, conditional imports keep Kafka out of the process in Lite.
# bot_server/__init__.py
from aigexbot.telegram_bot.bot_instance import is_lite
if not is_lite():
    from aigexbot.telegram_bot.kafka.client import KafkaClient
    from aigexbot.telegram_bot.kafka.consumer_service import KafkaConsumerService

Result: no accidental Kafka connection attempts before the app even starts.

2) Handlers now do the work—no agent round-trips

Old flow: Telegram → Kafka → AI agent → back again, even for /start. New flow: Telegram handles directly:

  • /start: shows wallet + SOL balance + SOL USD price (via WSOL).
  • /asset: pulls SPL tokens, prices via Jupiter V3, PnL via Supabase.
  • /wallet: same info, with buttons to open/disconnect in MiniApp.
  • /delegate: talks to Privy and MiniApp directly.

Cleaner. Faster. Fewer moving pieces to babysit.

3) On-chain balances via native RPC, not Birdeye

Birdeye got pricey and gated. I switched to pure RPC (with simple caching):

  • getBalance → SOL
  • getTokenAccountsByOwner (jsonParsed) → SPL balances (mint, decimals, uiAmount)
# portfolio.py (excerpt)
async def get_spl_tokens(owner: str) -> List[dict]:
    body = {"jsonrpc": "2.0","id":1,"method":"getTokenAccountsByOwner",
            "params":[owner, {"programId": TOKEN_PROGRAM_ID}, {"encoding":"jsonParsed"}]}
    # parse tokenAmount.uiAmount/amount, filter > 0

No API keys, no drama. If you run a private endpoint, just set:

SOLANA_RPC_URL=https://your-preferred-rpc
SOLANA_RPC_FALLBACKS=https://fallback-1,https://fallback-2

4) Prices via Jupiter Price API v3

I fetch up to 50 mints per call and cache them briefly. I also include WSOL to price SOL in USD, then compute token USD values and portfolio totals. Sustainable and consistent with what we use elsewhere.

async def get_prices(mints: List[str]) -> Dict[str, float]:
    r = await http.get("https://lite-api.jup.ag/price/v3", params={"ids": ",".join(mints)})
    return {k: float(v["usdPrice"]) for k, v in r.json().items() if "usdPrice" in v}

5) PnL from Supabase (no Golang proxy)

We used to call a Go webapp that called Supabase. I cut out the middle-hop:

  • Replaced WebAppServerAPIClient.get_user_info/get_privy_id with direct Supabase queries.
  • Kept the same response shape so nothing else breaks.
  • PnL path uses trading_tx_records math; if there’s no data yet, it degrades gracefully.
# storage/supabase.py (concept)
supabase_client.table("user_info").select(...).eq("user_id", uid).eq("bot_id", bot).eq("source", MessageSource.TELEGRAM.value)

Buttons that actually do something

  • Pagination for /asset: asset_page::<n>
  • Refresh button: refresh_assets
  • Token trade deep-link: https://t.me/<bot>?start=trade-<mint>-<SYMBOL>

Callbacks route back to the same handler, which edits the previous message in place. Feels native.

Architecture, zoomed out

This isn’t just a cleanup. It’s the foundation for the scheduler (from [Part 2]).

  • The orchestrator will run in the DeFi server, kick off strategies (monitor, LP-copy, etc).
  • The bot is now a passive receiver and dashboard:
    • show holdings / PnL
    • dump alerts (TP/SL/rebalance)
    • open links to execute flows (MiniApp or direct actions)
  • We left enough hooks to expand, but the default path is tight.

What this unlocks (for the scheduler)

  • Low-latency notifications straight from the DeFi server
  • No need to keep an AI agent, Kafka, and a Go service warm just to say “hey, TP hit”
  • Deterministic pricing + balances from sources we already trust in execution
  • PnL in the chat tied to the same Supabase the executors write to

Philosophy: make the control surface boring and reliable so the strategy brain can be spicy.

Gotchas & guardrails

  • RPC rate limits exist. I added tiny caches (seconds). If you scale up, consider a private RPC or batching.
  • Jupiter V3 sometimes won’t price dead tokens; I skip those in the portfolio view.
  • Token symbol/name mapping: quick wins from Jupiter token list or cached registry—easy to slot in.
  • PnL only sings once trading_tx_records has data. That’s fine; it’s ready when you are.
  • Keep LITE_MODE as an explicit choice. Don’t silently fall back.

Credits & context

This journey started with taigo.ai—I shipped their MiniApp, a large refactor of the Telegram bot, and the DeFi server (Meteora DLMM, Jupiter, pump.fun, Jito, “next block”, tx accel). The Lite refactor is me being a smart, modest motherf— (hi, mom) about what actually matters before we spin up the autonomous scheduler.

Less ceremony. More signal.

What’s next

  • Wire the scheduler to push position lifecycle updates to Telegram.
  • Flip dry-run → live for one controlled user and one strategy.
  • Add token metadata mapping for friendlier names everywhere.
  • Optional: bring back an LLM helper, but as a sidecar—not in the critical path.

If you’re building something similar, steal shamelessly. And if your bot feels sluggish, remember: sometimes the bravest thing is to delete code.

Stay Updated

Get notified when I publish new articles about Web3 development, hackathon experiences, and cryptography insights.

You might also like

DeFi Bots Series — Part 5: Live Rebalance on Meteora DLMM (RPC Profiles, Clean PnL & One-Sided Liquidity)

I rewired RPC handling with role-based profiles, unified LP strategy controls, fixed PnL accounting, and executed a live one-sided rebalance on a PUMP/USDC DLMM pool over gRPC—end to end with Supabase ledgering.

DeFi Bots Series — Part 4: Prepping the Monitor — Decimals, Prices, Symbols & Clean Balances

Before we let the monitor act on positions, we hardened the boring bits: one source of truth for decimals, fast prices, safe BigInt math, and clean balance reads. The goal is simple: trustworthy PnL so alerts and actions are correct.

DeFi Bots Series — Part 7: The Monitor Test Saga (One-Sided Bids, Skew Rebalances, and Real PnL)

I stress-tested a one-sided, USDC-anchored LP strategy overnight: ~15 rebalances, lots of fee accrual, a few bugs, and a clearer picture of what to fix next. We tightened pool orientation, made SOL/WSOL funding sane, added skew gates (TVL/fees), and wrote proper lineage + flows. The monitor is quieter—until it needs not to be.