Solana meme-coin auto-trading bot with MiniMax M2.7 LLM exit advisor, Jupiter Ultra swaps, Telegram control, and a Pepe-on-Moon dashboard
MoonBags
Solana meme-token auto-trading bot with LLM-powered exit decisions.
Note (May 2026): SCG Alpha (Private Feed) is temporarily disabled. The upstream SCG Alpha hosting has been cancelled and the signal source is offline. All Private Feed / SCG Alpha references in the code have been commented out. OKX and GMGN signal sources remain fully operational. To re-enable, restorePRIVATESIGNALSOURCEinsrc/config.tsand configure the upstream endpoint.
MoonBags is the execution and management layer for Solana meme-token signals. It consumes real-time discovery streams from Private Feed calls, OKX smart-money signals, and/or GMGN's curated trenches + KOL call feeds, buys via Jupiter Ultra, then manages exits with either a configurable trail/stop or โ optionally โ a MiniMax M2.7 LLM that reads live on-chain data (smart money flow, dev holdings, holder PnL, kline trends) every 30 seconds to decide when to sell.
Telegram /sources lets you pick between privateonly, okxwatch, okxonly, gmgnwatch, gmgnlive, gmgnonly, or hybrid at runtime, no restart. Whatever source creates the entry, MoonBags still sizes it, executes through Jupiter, watches it, and exits it with the same universal exit settings.
You operate the bot through a Telegram bot (/start, /positions, /settings, /sellall, etc.) or a local web dashboard.
โ ๏ธ Disclaimers
Not financial advice. This software is released for educational and research purposes. Using it to trade real money is your decision and your risk alone. Meme coins are extremely volatile โ you will have losing trades, and you can lose your entire wallet balance. Nothing in this repo, the dashboard, the Telegram bot, or the LLM advisor's output constitutes investment, legal, tax, or any other kind of professional advice. Do your own research.
Critical upstream dependencies โ Private Feed, OKX, and GMGN. The active signal layers are Private Feed calls, OKX OnchainOS smart-money signals, and GMGN OpenAPI's trenches/signal endpoints. I do not own, operate, or control OKX, GMGN, Private Feed, Jupiter, Helius, MiniMax, or Telegram. If any provider changes API shape, rate-limits you, changes pricing, or shuts down, the affected intake or execution path can stop working until the code or provider is updated. You're also subject to each provider's terms of service.
Other third-party services the bot depends on (any of which can break the bot if they change): Jupiter Ultra (swap execution + fees), Helius RPC (Solana reads), OKX onchainos CLI (on-chain data enrichment), MiniMax (LLM advisor, optional), Telegram Bot API (control + notifications).
Use at your own risk.
Table of contents
- Where the trades come from โ the discovery layer
- What it does
- Architecture at a glance
- Prerequisites
- Quick start โ one-command onboarding
- Manual setup (reference)
- Environment variables reference
- Running the bot
- Telegram commands
- LLM exit advisor
- Milestone alerts
- Web dashboard
- State files
- Operating day-to-day
- Backtesting
- Troubleshooting
- Safety notes
Where the trades come from โ the discovery layer
The hardest part of meme-coin trading isn't execution โ it's discovery. Out of the thousands of tokens minted on Solana every day, which ~10 are worth your SOL?
MoonBags supports three active discovery sources. Pick one or run them together via /sources:
Private Feed (curated calls). Reads a private signal feed either from Postgres (PRIVATESIGNALSOURCE=postgres) or from a configured HTTP endpoint (PRIVATESIGNALSOURCE=direct). The first poll seeds dedupe so old calls are not bought on restart; fresh calls can buy in Private Feed-enabled modes.
OKX OnchainOS (fast-twitch). Real-time websocket stream of buys by OKX-labeled Smart Money and KOL wallets. Fires ~3 seconds after the wallet's buy lands on-chain. You're front-running the rest of the market by seconds. Default filters (after a 156-signal data analysis): minHolders โฅ 100, walletTypes โ {SmartMoney, KOL}, minAmountUsd โฅ 500, combined with a /mcapfilter 25000 runtime floor.
GMGN OpenAPI (curated discovery). Polls three endpoints every 60s:
/v1/trenchesโ Pump.fun / pump_mayhem / letsbonk launches with smart-degen activity, safety preset on/v1/market/tokensignalโ smart-money + KOL call signals (signaltype=12)/v1/market/rank(trending) โ disabled by default; buying post-pump momentum is the wrong lens for early entries
hybrid mode).
If you want to swap in your own discovery source, the signal-source interface is at src/okxSignalSource.ts and src/gmgnSignalSource.ts โ every downstream layer (dedup, position manager, exit engine, Jupiter, Telegram) is signal-source agnostic.
What it does
- Receives live signals from Private Feed, OKX (websocket, ~3s after smart-wallet buys land), and/or GMGN (60s poll across trenches + smart-money calls).
/sources: privateonly, okxonly, gmgnlive, gmgnonly, hybrid, or any of the watch-only variants.
- Buys new alerts that pass your local filters via Jupiter Ultra (Solana DEX aggregator), spending a fixed SOL amount per trade.
- Tracks every open position every 3 seconds โ pulls live prices, updates the running peak, and checks for arm/trail/stop conditions.
- Arms a trailing stop once a position hits a profit threshold (default +50%).
- Exits based on either:
hold / tightentrail / exitnow based on smart money flow, dev wallet activity, holder PnL, momentum, etc.
- Notifies every buy, arm, sell, and LLM decision to your Telegram chat.
- Milestone alerts โ when a position crosses a PnL threshold you configured (default +100%, +200%, +500%, +1000%), fires a Telegram message with a one-tap sell button.
- Persists all state to disk so a restart picks up where it left off.
Architecture at a glance
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ UPSTREAM โ discovery sources โ
โ (NOT part of this repo, NOT mine) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ
โ โ OKX โ โ GMGN โ โ
โ โ OnchainOS โ โ OpenAPI โ โ
โ โ (WSS, ~3s) โ โ (60s poll) โ โ
โ โ โ โ โ โ
โ โ smart-money โ โ trenches + โ โ
โ โ + KOL buys โ โ KOL signals โ โ
โ โโโโโโโโฌโโโโโโโโ โโโโโโโโฌโโโโโโโโ โ
โ โ โ โ
โโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโ
โ โ
โโโโโโโโโโฌโโโโโโโโโโโโ
โผ
+-------------+ +-------------------+ +----------------+
| Jupiter |<--+ MoonBags +-->| Solana RPC |
| Ultra API | | (this repo, the | | (Helius) |
| + 0.5% ref | | execution layer)| +----------------+
+-------------+ +-+-----------+-----+
| | |
buy/sell swaps | | | on-chain data: smart money,
| | | dev trades, holder PnL, klines
| | โผ
| | +----------------+
| | | OKX onchainos |
| | | CLI |
| | +----------------+
| |
| โผ
| +----------------+
| | MiniMax M2.7 | (optional โ
| | exit advisor | choose LLM Managed)
| +----------------+
โผ
+----------------+
| Telegram | โ you control + receive alerts here
| bot + alerts |
+----------------+
Prerequisites
- macOS, Linux, or Windows via WSL2 Ubuntu (native Windows shell is not supported)
- Node.js 20+ (install via nvm)
- A funded Solana wallet (for live trading) โ needs SOL for both trades and gas
- Accounts for: Helius, Jupiter, Private Feed, Telegram, OKX (free), and optionally MiniMax (paid)
Quick start โ one-command onboarding
If someone handed you this repo and said "get MoonBags running", start here. The installer is meant to be the friendly path: it installs dependencies, installs OKX OnchainOS, runs the setup wizard, and finishes with a health check.
From a fresh machine:
curl -fsSL https://raw.githubusercontent.com/fciaf420/moonbags/main/install.sh | bash
If you already cloned the repo and are inside it:
MO bash install.sh
If your checkout does not include install.sh yet, run the same steps manually:
npm install
npm run install:onchainos
npm run setup
npm run doctor
What the installer covers:
| Step | What happens | |------|--------------| | Install app packages | Runs npm install in the project. | | Install OKX OnchainOS | Runs npm run install:onchainos, which calls OKX's official installer. | | Set up credentials | Runs the interactive setup wizard and writes .env after confirmation. | | Check the install | Runs npm run doctor so you can fix missing keys, PATH issues, or service problems before trading. |
After the installer finishes, open Telegram and send these to your bot:
/doctor
/setup_status
/start
/doctorchecks the bot from Telegram: wallet, RPC, Jupiter, OKX OnchainOS, Telegram, and common runtime problems./setup_statusshows what is complete and what still needs attention./startconfirms the bot is online and shows the main dashboard.
DRYRUN=true until /doctor and /setupstatus are clean and you are comfortable with the alerts.
Start the bot
For a first dry run:
npm run start
For a long-running install, use pm2:
npm install -g pm2
pm2 start "npm run start" --name moonbags
pm2 logs moonbags
pm2 save
Useful restart commands:
pm2 restart moonbags
pm2 restart moonbags --update-env # use after changing .env
pm2 logs moonbags
Once pm2 is running, Telegram /update becomes the self-healing update path: it checks origin/main, shows incoming commits, refuses unsafe local changes, runs npm install when package files changed, and restarts moonbags with pm2.
What the setup wizard asks for
The wizard walks through every credential, validates the services it can check live, auto-detects your Telegram chat_id, and can generate a fresh Solana keypair for you.
| Step | What it does | |------|--------------| | 1 | Checks that the onchainos CLI is on $PATH | | 2 | OKX OnchainOS credentials, plus optional GMGN OpenAPI key for GMGN Watch/Live source modes | | 3 | Jupiter API key โ with link + live validation | | 4 | Helius RPC key โ with link + live validation | | 5 | Solana wallet โ offers to generate a fresh keypair (saves to moonbags-keypair.json) or accept a pasted base58 secret | | 6 | Telegram bot token โ verifies via getMe, then auto-detects your chat_id after you message the bot once | | 7 | MiniMax API key (optional) + LLM advisor on/off toggle | | 8 | Trading params โ backtest-optimized defaults (BUY 0.02 SOL, arm +50%, trail 55%, stop -40%), editable | | 9 | Writes .env (backs up existing one first) |
The wizard never writes anything until the final confirmation step, and any existing .env is backed up to .env.backup.<timestamp> before it's touched.
After the wizard finishes:
# Open the dashboard
open http://localhost:8787/
Control from Telegram:
/start
Manual setup (reference)
The wizard covers everything below. This section exists for people who want to understand what each credential does, or who need to configure something without running the wizard.
1. Solana wallet
You need a Solana keypair the bot can sign with. Use a fresh wallet โ don't use a wallet that holds anything important. This wallet should hold only the SOL you're willing to deploy.
Option A โ generate one with the Solana CLI:
solana-keygen new -o moonbags-keypair.json
Then convert to base58 (Phantom/Solflare format):
node -e "console.log(require('bs58').encode(Buffer.from(require('fs').readFileSync('moonbags-keypair.json','utf-8').match(/\d+/g).map(Number))))"
Option B โ export from Phantom/Solflare as a base58 secret key.
Put the base58 string into .env as PRIV_B58=....
Fund the wallet: transfer at least 0.5 SOL to the address. Each trade uses BUYSIZESOL plus ~0.0005 SOL in fees.
2. Helius RPC
Solana's public RPC is rate-limited. You need a private feed endpoint.
- Sign up at dashboard.helius.dev (free tier works).
- Copy your API key.
- Set in
.env:
HELIUSAPIKEY=your-key-here
3. Jupiter API key
Jupiter Ultra provides the swap routing. The free tier is sufficient.
- Get a key at developers.jup.ag/portal.
- Set in
.env:
JUPAPIKEY=jup_xxxxxxxxxxxxxxx
4. OKX OnchainOS
This is a compiled Rust binary (npm package) that wraps OKX's on-chain data API. It's used by the price feed, the LLM advisor, backtests, and optional WebSocket acceleration for open-position monitoring.
npm run install:onchainos
That command runs OKX's official installer:
curl -sSL https://raw.githubusercontent.com/okx/onchainos-skills/main/install.sh | sh
The installer places the onchainos binary on your user PATH, commonly under ~/.local/bin. If which onchainos still returns nothing, open a new terminal or add that directory to your shell PATH:
export PATH="$HOME/.local/bin:$PATH"
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc
Then restart the bot so it picks up the PATH:
pm2 restart moonbags --update-env
Verify it works:
which onchainos
onchainos --version
onchainos token trending --help
onchainos market price --address So11111111111111111111111111111111111111112 --chain solana
Use onchainos 2.1.0 or newer. If /backtest fails with unrecognized subcommand 'trending', rerun the installer to update the CLI and restart the bot:
npm run install:onchainos
Optional: set OKXWSSENABLED=true to let MoonBags open OKX WebSocket sessions for tokens you already hold. WSS does not create entries or execute sells; it only refreshes market data faster and wakes the normal Jupiter-confirmed exit checks sooner.
Then create OnchainOS API credentials at web3.okx.com/onchain-os/dev-portal. Use a read-only key and save the passphrase you set during creation.
Set in .env:
OKXAPIKEY=your-okx-api-key
OKXSECRETKEY=your-okx-secret-key
OKX_PASSPHRASE=your-okx-passphrase
The onchainos CLI expects OKXPASSPHRASE. The bot also accepts the older local alias OKXAPIPASSPHRASE and passes it through as OKXPASSPHRASE when spawning the CLI. It's IPv4-only.
GMGN OpenAPI (optional source)
GMGN can be used as an additional signal source from Telegram /sources. Create an API key at gmgn.ai/ai, then add it to .env:
GMGNAPIKEY=your-gmgn-api-key
GMGN source modes do not use GMGN for execution. MoonBags uses GMGN market/signal data for discovery and still routes buys/sells through Jupiter.
Private Feed (shared signal source)
The Private Feed is a curated signal stream relayed into a shared Postgres table. The read-only DATABASE_URL ships pre-configured in .env.example โ cp .env.example .env and you're subscribed. No API key, no schema setup, no upstream credentials needed.
After starting the bot, run /ping in Telegram and you should see Private Feed: โ
along with the most recent alert count.
Private Feed alerts are auto-accepted with no entry filtering โ the upstream is curated, so MoonBags trusts every signal that arrives. Pause / blacklist / source-mode toggles still apply, and the 60-minute mint cooldown prevents re-buying the same token.
5. Telegram bot
This is how you'll interact with MoonBags.
- Create a bot: message @BotFather on Telegram, send
/newbot, follow prompts. Save the bot token. - Get your chat ID: message your new bot once (any text), then visit:
https://api.telegram.org/bot<YOURBOTTOKEN>/getUpdates
Find "chat":{"id":XXXXXXX} in the JSON. That's your chat ID.
- Set in
.env:
TELEGRAMBOTTOKEN=8775xxxxxxx:AAGxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
TELEGRAMCHATID=518183629
The bot is gated to your chat ID only โ random users who find it can't talk to it.
6. MiniMax (optional โ LLM advisor)
If you want the LLM to manage exit decisions for armed positions:
- Subscribe to a MiniMax Token Plan using the referral link below for 10% off:
Starter plan (1500 M2.7 requests / 5h) is plenty for ~6 simultaneous armed positions.
- Get your Token Plan API Key from platform.minimax.io/user-center/payment/token-plan.
- Set the API key in
.env:
MINIMAXAPIKEY=your-token-plan-key
Using a different LLM provider (e.g. OpenRouter): Set LLMAPIKEY, LLMENDPOINT, and LLMMODEL instead. Any OpenAI-compatible provider works:
LLMAPIKEY=sk-or-v1-... LLM_ENDPOINT=https://openrouter.ai/api/v1/chat/completions LLM_MODEL=anthropic/claude-3.5-sonnet MINIMAXAPIKEY is still accepted for backwards compatibility if you don't set LLMAPIKEY.
Then choose LLM Managed from Telegram /settings โ Exit Strategy.
Environment variables reference
Create .env in the project root with these values:
Keep secrets here. Trading exit settings are edited in Telegram and persisted separately in state/settings.json, so you don't have to keep retyping the live risk knobs into the env file.
# === REQUIRED ===
JUPAPIKEY=jup_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
HELIUSAPIKEY=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
OKXAPIKEY=your-okx-api-key
OKXSECRETKEY=your-okx-secret-key
OKX_PASSPHRASE=your-okx-passphrase
GMGNAPIKEY=your-gmgn-api-key
PRIV_B58=base58-encoded-solana-keypair-secret
=== RPC ===
RPCURL=https://beta.helius-rpc.com?api-key=${HELIUSAPI_KEY}
=== TRADING BASICS ===
Exit strategy, TP ladder, trail, stop, moonbag, and milestones are edited
in Telegram and saved to state/settings.json.
BUYSIZESOL=0.02 # SOL per trade
MAXCONCURRENTPOSITIONS=10 # max open positions
=== ALERT FILTERS (0 = disabled) ===
MAXALERTAGE_MINS=0
MINLIQUIDITYUSD=0
MIN_SCORE=0
MAXRUGRATIO=0
MAXBUNDLERPCT=0
MAXTOP10PCT=0
REQUIRERISINGLIQ=false
MINALERTMCAP=0 # only buy alerts at or above this mcap ($). Also editable via /mcapfilter or /stats
MAXALERTMCAP=0 # only buy alerts at or below this mcap ($). Also editable via /mcapfilter or /stats
=== POLLING ===
PRIVATESIGNALPOLL_MS=3000 # how often to poll Private Feed for new alerts
PRICEPOLLMS=3000 # how often to update prices for open positions
LLMPOLLMS=30000 # how often the LLM advisor checks armed positions
LLMHEARTBEATMINS=15 # LLM sends a rich check-in per watched position every N minutes (0 = off). Also editable live via /llm
OKXWSSENABLED=false # optional WSS acceleration for open positions only
=== EXECUTION ===
SLIPPAGE_BPS=2500 # fallback slippage for non-Ultra quotes
DRY_RUN=true # FALSE to enable real trades
=== DASHBOARD ===
DASHBOARD_PORT=8787 # localhost-only web dashboard
=== TELEGRAM ===
TELEGRAMBOTTOKEN=8775xxxxxxx:AAGxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
TELEGRAMCHATID=518183629
=== LLM EXIT ADVISOR ===
Option A โ MiniMax (default)
MINIMAXAPIKEY= # MiniMax Token Plan key
Option B โ any OpenAI-compatible provider (e.g. OpenRouter)
LLMAPIKEY=sk-or-v1-...
LLM_ENDPOINT=https://openrouter.ai/api/v1/chat/completions
LLM_MODEL=anthropic/claude-3.5-sonnet
On first boot, MoonBags creates state/settings.json from the env defaults. Telegram /settings then becomes the source of truth for live trading behavior:
{
"exit": {
"profitStrategy": {
"type": "trail",
"fixedTargetPct": 1,
"ladderTargets": [
{ "pnlPct": 0.5, "sellPct": 0.25 },
{ "pnlPct": 1, "sellPct": 0.25 },
{ "pnlPct": 2, "sellPct": 0.25 }
],
"trailRemainder": true
},
"trail": { "armPct": 0.5, "trailPct": 0.55 },
"risk": { "stopPct": 0.4, "maxHoldSecs": 99999999999999999 },
"runner": { "keepPct": 0, "trailPct": 0.6, "timeoutSecs": 7200 },
"llm": { "enabled": false }
}
}
Security note: .env should never be committed. Add it to .gitignore (already present in this repo).
Running the bot
Dev mode (recommended for first run)
DRY_RUN=true npm run start
In dry-run, the bot fetches alerts and prices but does not submit any swap transactions. Watch the logs to verify everything is wired correctly.
Live trading
- Set
DRY_RUN=falsein.env. - Make sure your wallet has enough SOL (at least 10ร your
BUYSIZESOLplus ~0.01 SOL for fees). - Run:
npm run start
Production (long-running)
Use a process manager so the bot survives crashes:
Option A โ pm2:
npm install -g pm2 pm2 start "npm run start" --name moonbags pm2 logs moonbags pm2 save # persist across reboot pm2 startup # follow instructions to enable on boot
Option B โ Docker (any OS โ recommended for most users):
cp .env.example .env # fill in your keys docker compose up -d # builds, starts, auto-restarts on crash docker compose logs -f # watch logs Positions and settings are written to ./state/ on your host so they survive container restarts. The dashboard is available at http://localhost:8787.
Option C โ systemd (Linux): Create /etc/systemd/system/moonbags.service:
[Unit] Description=MoonBags trading bot After=network.target
[Service] Type=simple User=youruser WorkingDirectory=/path/to/moonbags ExecStart=/usr/bin/env npm run start Restart=on-failure RestartSec=5
[Install] WantedBy=multi-user.target
sudo systemctl enable --now moonbags
journalctl -u moonbags -f
Verify it's running
You should see in the logs:
{"level":30,"time":...,"msg":"memeautobuy starting","dryRun":false,"buySol":0.02} {"level":30,"time":...,"msg":"[state] no prior state file, starting fresh"} {"level":30,"time":...,"msg":"dashboard available","url":"http://localhost:8787/"} {"level":30,"time":...,"msg":"[telegram] bot polling started"}
And in Telegram you'll get:
๐ MoonBags online mode: LIVE | buy: 0.02 SOL arm: +50% trail: 55% stop: -50%
Send /start to your bot to confirm it responds.
Telegram commands
Every command is gated to the TELEGRAMCHATID in .env โ random users who find the bot can't talk to it.
| Command | Description | |---------|-------------| | /start | ๐ MoonBags dashboard: mode (LIVE/DRY), SOL balance, open positions (with armed โก count), realized PnL, config summary, LLM state, uptime, wallet address. Inline buttons for Positions / Settings / Refresh. | | /positions | Open positions with one-tap force-sell buttons. Auto-refreshes 1.5s after a sell fires. | | /settings | Interactive menu with Buy, Exit Strategy, Risk Controls, TP targets, milestones, and LLM controls. Trading changes save to state/settings.json and apply on next tick โ no restart. | | /pnl | Today's PnL + all-time stats, win/loss count, win rate, best + worst trade. Reads state/closed.json. | | /stats | Signal metadata analysis โ win rate + avg PnL by mcap tier, Pearson correlations between signal fields and trade outcomes. Includes an inline "Adopt" button to activate the best-performing mcap range as an entry filter in one tap. Stats grow as new trades close (forward testing only). | | /mcapfilter [min] [max\|off] | Set or clear the mcap entry filter manually. /mcapfilter 50000 200000 = $50kโ$200k range. /mcapfilter 50000 = $50k floor, no ceiling. /mcapfilter off = clear. Persists in state/settings.json. | | /history [N] | Last N closed trades (default 10, max 50) โ name, PnL, exit reason, hold duration. | | /llm | One-tap toggle for the LLM exit advisor. Warns if LLMAPIKEY (or MINIMAXAPIKEY) is empty. | | /share | Encode your current settings as a shareable MB1:... string. Forward the message to anyone โ they paste it into /import to adopt your settings in one tap. | | /import MB1:... | Import settings shared by another user. Shows a diff and asks for confirmation before applying. | | /sources | Choose entry source mode: Private Feed only, OKX Watch/Live/only, or GMGN Watch/Live/only. | | /wss | OKX WSS status and enable/disable buttons for open-position market-data acceleration. | | /pause | Stop taking new Private Feed/OKX/GMGN entry alerts. Open positions keep running. Persists across restart. | | /resume | Resume taking new alerts. | | /sellall | Emergency liquidation. Lists every open position, requires typing CONFIRM (exact, case-sensitive) within 60s. Any other reply cancels. | | /skip <mint> | Blacklist a token (ignore future Private Feed/OKX/GMGN alerts for it). /skip alone lists current. /skip clear resets. Persists across restart. | | /mint <mint> | On-demand on-chain snapshot for any token: price + 5m/1h/4h/24h % changes, smart money / bundler / dev flow, top-10 holder PnL, dev hold %, LP burn, GMGN link. | | /wallet | Full wallet address + SOL balance + Solscan link. | | /backtest [source] [hybrid] | Run the exit-strategy backtester. Source: gmgn (default) pulls fresh calls from GMGN signals/trenches/trending; private is retained but disabled. Add hybrid to switch the grid to trail + scale-out + moonbag. Examples: /backtest, /backtest hybrid, /backtest gmgn hybrid. Tap a row to adopt the exit strategy live. | | /backtest_hybrid | Alias for /backtest hybrid โ same source default, hybrid exit grid. | | /doctor | Run a health check from Telegram. Use this when the bot starts, after changing .env, or when something feels off. Mirrors npm run doctor. | | /ping | Live connectivity check: GMGN/OKX source status, Telegram delivery, OKX WSS state, and position tick health (flags โ ๏ธ if the tick loop appears stuck). | | /dismiss <mint> | Remove a ghost position from state without attempting a sell (use after a manual on-chain exit or to clear a failed buy that left stale state). | | /setup_status | Show a plain-English setup checklist: credentials, wallet, Telegram, OKX OnchainOS, and remaining fixes. | | /update | Check origin/main, show incoming commits, then pull + restart through pm2 after confirmation. Requires git and a pm2 process named moonbags. |
Notification behaviour
Sent to your Telegram chat as events happen. Dedupe is built in so you don't get spammed:
๐ข BUYโ every buy (mcap, spent, tx link)โก ARMEDโ when a position hits the configured trail arm threshold and trailing activates๐ค LLM watchingโ fires once per position when the LLM advisor first picks it up๐ค LLM tightened 55% โ 25%/๐ค LLM loosened 25% โ 55%โ only when the LLM actually changes the trail (โฅ1% delta), direction-aware copy๐ / ๐ / ๐ / ๐ <TOKEN> hit +100%โ milestone alerts with an inline sell button when a position crosses each configured PnL % (fires once per threshold). Tap the button to close in one tap.๐ข SELLโ every close, with reason + PnL. Includes the LLM's reasoning when it triggered the exit.โ BUY FAILEDโ when a swap couldn't land๐จ SELL STUCKโ after 10 sell retries failed (needs manual action)
Signal source modes
/sources controls what can create entry signals:
- Private Feed only โ only Private Feed alerts can buy.
- OKX Watch โ Private Feed still buys; OKX discovery signals are tracked and shown but never bought.
- Hybrid Live โ Private Feed, OKX discovery, and GMGN scanner signals can all buy.
- OKX only โ Private Feed keeps polling for health/status, but only OKX discovery signals can buy.
- GMGN Watch โ Private Feed still buys; GMGN scanner candidates are filtered, tracked, and shown but never bought.
- GMGN Live โ Private Feed alerts and GMGN scanner candidates can both buy after the baseline + tracking trigger passes.
- GMGN only โ Private Feed keeps polling for health/status, but only GMGN scanner candidates can buy.
dex-market-new-signal-openapi, the OnchainOS smart-money/KOL/whale signal feed. Historical onchainos signal list rows are used only to seed dedupe on startup, so old rows are not bought. Live OKX signals are treated as entry triggers in Hybrid/OKX-only modes only when they pass the OKX live-buy filter in state/settings.json: Smart Money/KOL wallet type and at least 500 holders by default. Pause, blacklist, duplicate/mint cooldown, max-position, and Jupiter execution safety still apply.
The OKX live-buy filter is intentionally separate from exit settings. OKX entries still use your current active exit strategy unless you change it in /settings.
GMGN uses a scanner/watchlist flow. Each scan pulls GMGN trending, Trenches, and signal data, applies a fast baseline filter, then tracks surviving mints across multiple scans. Defaults live in state/settings.json: holders at least 200, liquidity at least $10k, top-10 holders at most 50%, rug/bundler/bot rates capped, creator hold capped, wash trading rejected, then at least two scans with holder growth, stable liquidity, buy pressure, and smart/KOL confirmation. GMGN Watch is the safest first rollout because it records forward snapshots before allowing live buys.
Settings you can edit live via /settings:
BUYSIZESOLโ SOL per tradeMAXCONCURRENTPOSITIONSโ max open positions- Exit Strategy โ Trail, Fixed TP, TP Ladder, or LLM Managed
- TP targets โ typed as
50:25,100:25,200:25for +50% sell 25%, +100% sell 25%, etc. - Risk Controls โ trail arm, trail drawdown, hard stop, and max hold
- Runner / Moonbag โ keep a remainder after profit-taking and trail it separately
- Milestones โ notification thresholds with inline sell buttons
- LLM Managed โ let MiniMax manage profit exits when configured
- API keys (
JUPAPIKEY,HELIUSAPIKEY,OKXAPIKEY,OKXSECRETKEY,OKXPASSPHRASE,MINIMAXAPIKEY,TELEGRAMBOT_TOKEN) - Wallet key (
PRIV_B58) DRY_RUN(intentionally requires manual.envedit + restart)
LLM exit advisor
When you choose LLM Managed in Telegram, MoonBags consults MiniMax M2.7 every 30 seconds for each armed position.
What the LLM sees
For each armed position, it gets a compact JSON payload with:
- Position context: entry/current price (in SOL), PnL %, peak PnL %, drawdown from peak, current trail %, hold time
- Momentum: price + volume + tx count across 5m / 1h / 4h / 24h windows, holders, market cap, liquidity, % from ATH
- Trade flow (last 30 min): smart money, bundlers, dev, whales, insiders โ buys/sells/net flow in SOL
- Top 10 holders: holding %, average PnL, average buy/sell prices, trend (buy or sell)
- Liquidity pools: top 3 by USD value
- Risk profile: dev current holding %, dev sell status tag, LP burned %, top10 concentration, sniper status, token tags
- Recent signals: smart money / KOL / whale movements scoped to this token (last 60 min)
- Klines: 60 1m candles + 60 5m candles (closes + USD volumes)
- Deterministic evidence facts:
bundlerDistribution, smartMoneySelling, topHolderCapitulation, volumeCliff, roundTripRisk
- Proactive (sell-into-strength): priceAcceleration (parabolic 1m candle), volumeBlowoff (5m volume โฅ 3ร hourly avg), txRatioBurst (5m tx count โฅ 2ร hourly avg), pctFromAthSpike (price within 5% of ATH on a +100% winner)
- Memory: recent same-position decisions, global closed-trade track record, and similar historical cases when the same evidence facts appeared
What the LLM can decide
Four actions:
| Action | What happens | |--------|--------------| | hold | Nothing changes โ current trail logic continues | | set_trail | Changes trail % up or down within the configured ceiling. Effective on next 3s tick. | | partial_exit | Sells 10-75% of the current position and leaves the remainder open. | | exit_now | Sells the entire position immediately, bypassing the trail |
The LLM cannot buy more or override the hard stop. Aggressive actions are hard-gated: partialexit and exitnow are blocked unless the deterministic evidence object allows them, and trail tightening is blocked unless at least two evidence facts are active. Every non-hold reason must cite exact fact keys.
The gate has two unlock paths:
- Bearish path: 2+ reactive facts (existing behaviour)
- Proactive path: 2+ proactive facts OR 1 proactive + 1 bearish โ enables selling into strength at blow-off tops without waiting for on-chain confirmation of the reversal
state/llm_audits/<mint>.json with the exact prompt payload, evidence facts, similar-case memory, raw tool arguments, parsed decision, and gate result.
Telegram notifications
Per-position lifecycle with LLM enabled:
๐ข BUY YOLO โ buy fires
โก ARMED YOLO โ trailing active โ PnL hits +50%
๐ค LLM watching YOLO โ LLM picks up the position (once)
๐ค LLM tightened YOLO 55% โ 25% โ only on actual change
๐ฐ LLM partial YOLO sold 30% โ only when evidence gate allows
๐ข SELL YOLO โ llm โ exit triggered
PnL: +0.084 SOL (+420.0%)
peak: +680.5% | held: 8m 12s
LLM: "smart money flipped to selling, bundlers exit-stamping"
Polling cost: ~120 LLM calls per armed position per hour. The Starter plan (1500 / 5h) handles ~6 simultaneous armed positions comfortably.
Safety net
The configured hard stop is always active regardless of LLM state. If MiniMax goes down or returns garbage, the bot falls back to the existing trail logic. The LLM never gets to override the floor.
Milestone alerts
When a position's PnL crosses a threshold you configured, a Telegram message fires with an inline sell button โ one tap to take profit without opening /positions.
Defaults: 100, 200, 500, 1000 โ meaning +100% (๐ 2x), +200% (๐ 3x), +500% (๐ 6x), +1000% (๐ 11x).
How it works
- Every 3-second tick, after PnL is computed, the bot checks whether the current PnL just crossed any configured milestone threshold.
- The first time a position crosses a given threshold, a notification fires. Each threshold fires at most once per position (dedupe via
position.milestonesHit[], persisted across restarts). - The inline button uses the same
sell:<mint>callback as the sell buttons in/positions, so tapping it works instantly.
Sample notification
๐ ALLIN hit +200% (3.0x)
Now: +214.3% | Peak: +241.7%
Unrealized: +0.0426 SOL
[ ๐จ Sell ALLIN ]
Tier icons: ๐ for 2x, ๐ for 3x, ๐ for 5-9x, ๐ for 10x+.
Configuration
Editable live via /settings in Telegram:
| Setting | Description | |---------|-------------| | Enabled | Feature toggle (default ON) | | Thresholds | Comma-separated % thresholds, e.g. 100,200,500,1000. Max 10 values. |
Changes take effect on the next tick โ no restart needed.
Web dashboard
A live dashboard runs on http://localhost:8787/ (configurable via DASHBOARD_PORT). React/Vite SPA, polls /api/state every 2 seconds.
Signal stats page โ http://localhost:8787/stats โ auto-refreshing table of win rate by mcap tier, distribution stats (mean/median/stdev), and Pearson correlations. Same data as the Telegram /stats command.
Pepe-on-the-Moon theme โ Pepe green primary, Earth visor blue accent, coral for losses, on a true space-black surface with a faint star field and Earth-glow gradient.
Layout:
- Top bar โ ๐ MOONBAGS logo + LIVE/DRY pill, compact OPEN/REALIZED PNL/UPTIME stats
- Hero card โ massive 120px Pepe-green realized-PnL number, 8-bar cumulative-PnL sparkline (real, from
state/closed.json), 4 KPI tiles (WIN RATE, AVG PNL, BEST, WORST) - Open positions as rich cards (one per position):
/positions)
- Live feed โ compact mini cards for recent Private Feed alerts. Each shows token icon, GMGN/JUP links, organic-score chip, and an inline
CLOSED +420%badge if you've already traded that token (reads fromstate/closed.json). - Bottom config strip โ fixed 48px showing BUY, the structured exit block, moonbag controls, LLM, and DRY values. The "EDIT IN TELEGRAM /settings" link auto-resolves your bot username via
getMeand openshttps://t.me/<botname>in a new tab.
ssh -L 8787:localhost:8787 youruser@yourserver
Then open http://localhost:8787/ in your local browser.
Rebuilding the frontend
The dashboard is a React/Vite/Tailwind SPA in frontend/. If you edit anything in frontend/src/:
cd frontend
npm run build # outputs to ../public/
The backend serves the built artifacts from public/ โ no backend restart needed for frontend-only changes, just refresh the browser.
State files
The bot writes to state/ in the project directory:
| File | Purpose | |------|---------| | state/positions.json | Live position state โ restored on restart | | state/closed.json | Append-only log of all closed trades (used by /pnl, /history). Capped at 500 entries. | | state/settings.json | Live trading exit/settings state edited in Telegram; secrets still live in .env. | | state/poller.json | paused flag and blacklist โ survives restart | | state/stranded.json | Audit log of in-flight positions reconciled on boot. Worth manual review if anything appears here. |
Backup state/ periodically if you want to preserve PnL history.
Operating day-to-day
Normal flow
- Send
/doctorafter every install, update, or.envchange. - Send
/setup_statusif/doctorreports anything missing. - Check
/startfor current SOL balance + open positions. - Receive buy/arm/sell notifications as they happen.
- If something looks off in a position, tap the Sell button in
/positions. - Run
/pnlat the end of the day for a summary.
Tuning settings
Use /settings from Telegram. Common adjustments:
- Markets are crazy bullish: raise
BUYSIZESOLto deploy more capital per trade. - Bot is missing big runners: widen the Trail setting (e.g. 55% โ 70%) so wicks don't shake you out.
- Too many flat trades: loosen the Stop setting to give positions more room, or tighten it for lower risk.
- Pausing during macro events:
/pause, then/resumewhen ready.
Emergency: kill all positions
/sellall
CONFIRM
Sells every open position immediately via Jupiter. Confirms with a summary message.
Restarting the bot
pm2 restart moonbags
pm2 restart moonbags --update-env # after .env or PATH changes
or
sudo systemctl restart moonbags
State is preserved. Positions in flight at the moment of restart are reconciled from your wallet balance and logged to state/stranded.json.
Self-healing updates from Telegram
If the bot is running under pm2, /update can pull the latest origin/main, refresh packages when needed, and restart the bot for you:
npm install -g pm2
pm2 start "npm run start" --name moonbags
pm2 save
Then send /update in Telegram. The bot checks for git, refuses to update when the working tree has local edits or local-only commits, shows incoming commits, warns when positions are open, and requires a confirm tap before running git pull --ff-only origin main. If package.json or package-lock.json changed, it runs npm install before pm2 restart moonbags --update-env.
After the restart, send /doctor and /setup_status to confirm the bot came back healthy.
Backtesting
Two scripts ship with the bot for tuning your trading params and researching individual tokens.
Private Feed alert backtester โ src/_backtest.ts
Fetches the current Private Feed alert window from the configured direct endpoint, saves a snapshot to state/backtests/, then uses each alert's alerttime plus alertmcap as the simulated entry when Private Feed provides enough data to derive supply. If alert_mcap cannot be anchored, it falls back to the first usable post-signal candle. It pulls OHLCV after that signal and requires roughly 24 hours of post-signal runway before a token is eligible for the recommendation. The backtester compares deterministic exit modes that Telegram can adopt live:
- Trail โ arm, trailing drawdown, hard stop.
- Fixed TP โ sell the whole position at a fixed take-profit percent.
- TP Ladder โ sell partial chunks at multiple take-profit targets, then trail the remainder.
Run with defaults (5m bars, top 15 results):
npx tsx src/_backtest.ts
Customize via flags:
# Use hot-token mode as a market sanity check instead of the Private Feed alert window
npx tsx src/_backtest.ts --source hot
Show top 30 ranked combinations instead of 15
npx tsx src/_backtest.ts --top 30
Smoke-test the first 25 Private Feed alerts while developing
npx tsx src/_backtest.ts --tokens 25
Combine
npx tsx src/_backtest.ts --bar 5m --top 25 --source private
What you'll see โ a ranked table like this:
#1 TP Ladder balanced / ARM 50% / TRAIL 55% / STOP 40%
total +12,840% | avg +128% | 42W/31L/27H | win 58%
#2 Fixed TP +100% / STOP 40%
total +12,210% | avg +122% | 38W/35L/27H | win 52%
- TOTAL PnL โ sum of % returns across all simulated trades
- AVG/TRADE โ average % per trade
- W / L / H โ wins / losses / still-holding (trade hit neither stop nor trail by end of data)
- WIN% โ wins as % of completed trades (excluding holding)
- entries โ how many samples used Private Feed
alert_mcapversus the first usable candle as the entry basis
/backtest shows the same recommendation in chat and includes adopt buttons. Adopting saves to state/settings.json, switches the exit strategy if needed, and applies on the next position tick without a restart.
If your private feed endpoint does not expose pagination or arbitrary historical ranges, local snapshots in state/backtests/ are how MoonBags builds a replayable history going forward.
โ ๏ธ Past performance โ future results, especially in meme coins. The backtest is a sanity check, not a guarantee.
Ad-hoc on-chain snapshot โ src/_okxTest.ts
Pulls a full live snapshot of any token (price + 5m/1h/4h/24h momentum, smart-money / bundler / dev trade flow, top-10 holder PnL, liquidity pools, dev hold %, recent signals, 1m + 5m kline). Same data the LLM advisor sees, dumped to your terminal.
npx tsx src/_okxTest.ts <mint-address>
Useful for manually evaluating a token before whitelisting it, or debugging why the LLM made a particular decision.
Troubleshooting
See TROUBLESHOOTING.md for the practical recovery guide.
Fast checks:
npm run doctor
pm2 restart moonbags --update-env
pm2 logs moonbags
Telegram checks:
/doctor
/setup_status
Common fixes:
- OnchainOS missing:
npm run install:onchainos, then reopen terminal or add~/.local/binto PATH. - Bot running under old env:
pm2 restart moonbags --update-env. - Telegram quiet: send
/doctor, then/setup_status, then checkpm2 logs moonbags. - No entry signals: send
/ping. If it says the poller is alive but recent decisions are filtered, checkMAXALERTAGEMINS,MINLIQUIDITYUSD,MINSCORE, and the other alert filters in.env.0disables each numeric filter. Also check/mcapfilterโ if an mcap range is active, alerts outside it are silently dropped. - WSS off: this is normal unless you set
OKXWSSENABLED=true. WSS is only an acceleration layer for positions you already hold; Jupiter still confirms every exit. - Need latest version: send
/updatein Telegram afterpm2is set up.
Safety notes
- Use a dedicated wallet. Never put a wallet that holds anything important into
PRIV_B58. Funds in this wallet are exposed to whatever the bot does. - Start with
DRY_RUN=true. Watch logs for at least an hour before going live. - Start with small
BUYSIZESOL. 0.02 SOL is a safe starting point โ that's roughly $4 per trade at $200/SOL. Scale up only after seeing the bot perform. - Keep a stop loss set. This is your floor. The LLM and the trail can both be wrong; the hard stop saves you from total wipeout on a single trade.
- Don't expose the dashboard publicly. It has no authentication. Use SSH tunneling for remote access.
- Review
state/stranded.jsonafter every restart. Anything appearing there means the bot recovered an in-flight position from your wallet โ verify it's correct. - Test
/sellallonce in dry-run. Make sure you're comfortable with the confirmation flow before relying on it in an emergency. - The LLM can be wrong. It uses a strong prompt and good data, but meme-coin moves are noisy. Always have the hard stop as a backstop.
- Backups. Periodically save
state/somewhere safe so you don't lose PnL history. - This is not financial advice. Meme coins are extremely volatile. You will lose trades. The bot is a tool, not a guarantee.
Project structure
src/
โโโ main.ts โ entry point: wires everything together
โโโ config.ts โ env loading + live settings updates (mutable CONFIG)
โโโ types.ts โ shared TypeScript types
โโโ logger.ts โ pino logger
โโโ privateSignalPoller.ts โ polls Private Feed + pause/blacklist state (persisted)
โโโ positionManager.ts โ position lifecycle, tickPositions, tickLlmAdvisor
โโโ jupClient.ts โ Jupiter Ultra swap execution + wallet balance
โโโ jupTokensClient.ts โ Jupiter Tokens API enrichment (verification, organic score, audit)
โโโ priceFeed.ts โ OKX prices (primary) + Jupiter sell quote (fallback)
โโโ okxClient.ts โ onchainos CLI wrapper for the LLM data layer + dashboard kline
โโโ okxSignalSource.ts โ optional OKX discovery source for /sources
โโโ gmgnClient.ts โ GMGN OpenAPI client for optional GMGN source modes
โโโ gmgnSignalSource.ts โ GMGN scanner/watchlist source for /sources
โโโ llmExitAdvisor.ts โ MiniMax M2.7 client + tool calling (enablethinking, extrabody.reasoning_split)
โโโ notifier.ts โ Telegram notifications (with HTML escaping)
โโโ telegramBot.ts โ Telegram command handler (long polling, force_reply for edits)
โโโ server.ts โ localhost web dashboard backend (/api/state JSON, /api/stats signal stats, /stats HTML page, static serve)
โโโ _setup.ts โ interactive first-time setup wizard (npm run setup)
โโโ _backtest.ts โ grid-search backtester
โโโ _okxTest.ts โ ad-hoc on-chain snapshot tool
frontend/ โ React/Vite/Tailwind dashboard SPA (build โ public/) โโโ src/ โ โโโ App.tsx โ โโโ components/ โ โ โโโ TopBar.tsx โ glass header w/ MoonBags logo + stats โ โ โโโ HeroSection.tsx โ 120px PnL hero + sparkline + KPI tiles โ โ โโโ PositionsTable.tsx โ rich position cards (NOT a table) โ โ โโโ AlertsFeed.tsx โ compact alert mini-cards w/ closed-PnL inline โ โ โโโ BottomConfigStrip.tsx โ fixed config pills + Telegram deep-link โ โ โโโ TokenAvatar.tsx โ circular icon w/ initial-letter fallback โ โ โโโ TokenInfoBadges.tsx โ Jupiter verification + organic score + audit pills โ โ โโโ MiniPriceChart.tsx โ real SVG line chart from 1m OKX kline โ โ โโโ ui/ โ shadcn primitives (badge, button, card, etc.) โ โโโ lib/ โ โ โโโ format.ts โ truncMint, fmtSol, fmtUsd, fmtAge, fmtUptime โ โ โโโ sparkline.tsx โ SparkBars + heroBars helpers โ โโโ types.ts โ State, Position, Alert, ClosedTrade, TokenInfo โ โโโ index.css โ star field + pepe-glow + body backdrop gradients
state/ โ created at runtime, persisted across restarts โโโ positions.json โ open positions + realizedPnlSol โโโ closed.json โ every closed trade (capped at 500). Source for /pnl + /history โโโ poller.json โ paused flag + blacklist โโโ stranded.json โ audit log of in-flight positions reconciled on boot
License
Private. Do not redistribute without permission.