
DeFi Bots Series — Part 2: Orchestrator, LP-Copy Warm-Up & Safe Monitor (Dry-Run)
TL;DR — Added a modular bot orchestrator, a primed LP-copy scanner that only prints new pools, and a monitor strategy that runs dry-run (no txs) while sending Telegram updates. All actions use direct imports of our Meteora executors (close/rebalance), not gRPC.
What landed since Part 1
- ✅ Tx-scanner source for LP-copy (
TxScannerPositionsSource) - ✅ LpCopyStrategy: warm-up seeding, de-dupe, per (follower, leader) state
- ✅ Orchestrator + Scheduler: multiple strategies per tick
- ✅ MonitorStrategy (dry-run): TP/SL, out-of-range ➜ rebalance, Telegram notify
- ✅ Config-first (
configs/monitor.json), user overrides, cooldowns - ✅ Supabase reads: positions table as source of truth
- ✅ Direct-import actions wired (no gRPC path):
closePositionAndRecord,rebalanceSinglePosition - 🛠️ Minor TS fix in monitor override picker (see snippet below)
Micro-architecture
Orchestrator
├─ LpCopyStrategy (scanner-backed, warm-up, print new pools)
└─ MonitorStrategy (policy -> {HOLD|TP|SL|REBALANCE}, dry-run notify)
│
└─ imports -> executeTx.{closePositionAndRecord, rebalanceSinglePosition}
+ Supabase (positions/flow) + TelegramConfigs
configs/monitor.json (example):
{
"enabled": true,
"dryRun": true,
"defaultTakeProfitPct": 12,
"defaultStopLossPct": 6,
"defaultPlacement": 1,
"cooldownMin": 20,
"onlyUserIds": [],
"overrides": [
{ "userId": 12345, "takeProfitPct": 10, "stopLossPct": 5, "placement": 0, "cooldownMin": 15 }
]
}configs/lp_copy.json:
{
"intervalSec": 12,
"followers": [
{
"followerUserId": 12345,
"slippageBps": 800,
"leaders": [
{ "wallet": "7KHx2Uc5qsqz652eXbu8Qtabi5KLxWJLgxFzcaBzP32i", "allowPools": "*", "scaleBps": 10000 }
]
}
]
}Orchestrator wiring
const orch = new Orchestrator();
const lp = new LpCopyStrategy(loadLpCopyCfg());
await (lp as any).prime?.(); // warm-up, avoid first-tick spam
orch.register(lp);
const mon = new MonitorStrategy(loadMonitorCfg()); // dryRun=true by default
orch.register(mon);
new Scheduler(() => orch.tick(), (lpCfg.intervalSec ?? 12) * 1000).start();LP-copy warm-up behavior
-
First boot: seed “seen pools” for each (follower, leader), no print.
-
Ticks: only 🆕 new pools print once:
[lp-copy] warm-up follower=12345 leader=... pools=4 🆕 [lp-copy] NEW POOL leader=... follower=12345 pool=... pair=?/? pos=... tx=... [lp-copy] no new pools for leader=... follower=12345
Monitor strategy (dry-run)
-
Reads positions from Supabase.
-
Computes PnL + range status (via Meteora + Birdeye).
-
Decision:
>=TP → TAKE_PROFIT<=-SL → STOP_LOSS- Out of range → REBALANCE
- Else → HOLD
-
With
"dryRun": true, it only notifies on Telegram (no tx).
Telegram message shape:
🤖 *Monitor* (dry-run)
pool: `9kXy…Lg2F` pos: `Dt8o…ZcEw`
uPnL $12.34 | total 5.67% ($34.56)
range: [1070..1086] active=1088 🔴 out
action: *REBALANCE*
Would REBALANCE with placement=SYMMETRICTS nit fix
Map config keys → override fields (to satisfy the type system):
// inside MonitorStrategy
private pick<K extends keyof MonitorCfg>(key: K, userId: number): MonitorCfg[K] {
const o = this.cfg.overrides?.find(v => v.userId === Number(userId));
const fromOverride =
key === "defaultTakeProfitPct" ? (o?.takeProfitPct as any) :
key === "defaultStopLossPct" ? (o?.stopLossPct as any) :
key === "defaultPlacement" ? (o?.placement as any) :
key === "cooldownMin" ? (o?.cooldownMin as any) :
undefined;
return (fromOverride ?? (this.cfg as any)[key]) as MonitorCfg[K];
}How I’m testing it (now)
- Infra up (no scheduler): Telegram receiver + Supabase + creds.
- Open 1 position manually (gRPC); verify in
user_lp_positions. - Run bot with dryRun=true; inspect logs + Telegram notifications.
- Force paths: tweak thresholds (tiny TP/SL) or ranges to trigger each action (still dry-run).
- Then flip
"dryRun": falsefor one test user to validate txs + DB writes.
Notes & guardrails
- All state (monitor cooldowns, lp-copy seen pools) is in-memory; for prod, persist “seen” and “last action” in Supabase to survive restarts.
- Scanner uses Anchor discriminators + inner createAccount; avoids bin-array traps at scan time.
- LP-copy execution path (funding swaps, ATAs, APR/TVL filters) is intentionally deferred.
Up next (Part 3)
We’ll revive a simplified Telegram bot surface, connect Supabase thoroughly, verify Privy wallet + SOL balance, and run the Monitor with actions on for a controlled position. Then layer in APR/TVL filters and Jupiter pre-flight for LP-copy execution.
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 3: Telegram Bot Lite, Portfolio RPC, and a Lean Path to the Scheduler
I stripped our Telegram surface down to a fast, durable “Lite” mode: no Kafka, no AI agent in the middle—just clean wallet UX, on-chain balances via RPC, token prices from Jupiter, PnL wired to Supabase, and buttons that actually do something. This sets the table for the trading scheduler.

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.