Solana meme-token auto-trading bot with LLM-powered exit decisions.
MoonBags is the execution and management layer on top of SCG Alpha's discovery system. SCG Alpha runs the alpha-filtering brain β they curate meme-coin signals from the firehose using the GMGN API and surface the ones worth acting on. MoonBags consumes that filtered alert stream, 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.
In other words: SCG Alpha picks the trades. MoonBags fires them, sizes them, watches them, and exits them.
You operate the bot through a Telegram bot (/start, /positions, /settings, /sellall, etc.) or a local web dashboard.
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 dependency β SCG Alpha (+ GMGN). MoonBags does not discover trades on its own. The entire signal layer is SCG Alpha's (@scg_alpha on X) β they run an alpha-filtering system on top of the GMGN API and feed the curated alerts into the public endpoint this bot polls. I do not own, operate, or control SCG Alpha or GMGN β all credit for signal quality goes to them. If either changes their API shape, rate-limits you, changes pricing, or shuts down, the bot's intake stops working until the code (or the upstream provider) is updated. You're also subject to whatever terms of service SCG Alpha and GMGN impose β please review them. If you want a different upstream, replace src/scgPoller.ts with your own integration; the rest of the bot is signal-source agnostic.
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.
- 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
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?
That problem is fully solved upstream by SCG Alpha. They run an alpha-filtering system that:
- Pulls a constant firehose of new mints, smart-money flow, holder data, and on-chain signals via the GMGN API
- Applies their own scoring + filtering logic (the secret sauce β that's their product, not mine)
- Surfaces only the alerts that pass their criteria via a public-facing alerts API
MoonBags is downstream of that. This bot does NOT decide which tokens are worth trading. It receives the already-curated alert stream from SCG Alpha and acts on it. Without SCG Alpha there is no MoonBags β the entire upstream "what should I buy?" question is theirs to answer.
If you want to see the signal quality firsthand, follow @scg_alpha. If you want to swap in your own discovery source, replace src/scgPoller.ts with your own integration β every other layer in this bot is signal-source agnostic.
- Receives filtered alerts from SCG Alpha's API every 3 seconds β these are pre-vetted by their alpha-filtering system on top of GMGN.
- 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:
- Static trail/stop logic (default) β sells when price drops X% from the peak after arming, or hits the hard stop.
- LLM advisor (optional) β every 30s after arming, sends an on-chain snapshot to MiniMax M2.7 which decides
hold/tighten_trail/exit_nowbased 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.
ββββββββββββββββββββββββββββββββββββββββββββ
β UPSTREAM β discovery / alpha source β
β (NOT part of this repo, NOT mine) β
ββββββββββββββββββββββββββββββββββββββββββββ€
β β
β ββββββββββββ ββββββββββββββ β
β β GMGN β βββββββΆ β SCG Alpha β β
β β API β signalsβ filtering β β
β β β + data β engine β β
β ββββββββββββ βββββββ¬βββββββ β
β β alerts β
ββββββββββββββββββββββββββββββββββΌββββββββββ
β
poll every 3s βΌ
+-------------+ +-------------------+ +----------------+
| 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 |
+----------------+
- 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, SCG Alpha, Telegram, OKX (free), and optionally MiniMax (paid)
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 | bashIf you already cloned the repo and are inside it:
MOONBAGS_DIR="$PWD" bash install.shIf your checkout does not include install.sh yet, run the same steps manually:
npm install
npm run install:onchainos
npm run setup
npm run doctorWhat 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.
Keep DRY_RUN=true until /doctor and /setup_status are clean and you are comfortable with the alerts.
For a first dry run:
npm run startFor a long-running install, use pm2:
npm install -g pm2
pm2 start "npm run start" --name moonbags
pm2 logs moonbags
pm2 saveUseful restart commands:
pm2 restart moonbags
pm2 restart moonbags --update-env # use after changing .env
pm2 logs moonbagsOnce 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.
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 β links to the dev portal and stores API key, secret key, and passphrase |
| 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:
/startThe 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.
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.jsonThen 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 BUY_SIZE_SOL plus ~0.0005 SOL in fees.
Solana's public RPC is rate-limited. You need a private endpoint.
- Sign up at dashboard.helius.dev (free tier works).
- Copy your API key.
- Set in
.env:HELIUS_API_KEY=your-key-here
Jupiter Ultra provides the swap routing. The free tier is sufficient.
- Get a key at developers.jup.ag/portal.
- Set in
.env:JUP_API_KEY=jup_xxxxxxxxxxxxxxx
This is a compiled Rust binary (npm package) that wraps OKX's on-chain data API. It's used by both the price feed and the LLM advisor for smart-money trades, dev wallet activity, holder PnL, and kline data.
npm run install:onchainosThat command runs OKX's official installer:
curl -sSL https://raw.githubusercontent.com/okx/onchainos-skills/main/install.sh | shThe 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"' >> ~/.zshrcThen restart the bot so it picks up the PATH:
pm2 restart moonbags --update-envVerify it works:
which onchainos
onchainos --version
onchainos token trending --help
onchainos market price --address So11111111111111111111111111111111111111112 --chain solanaUse 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:onchainosThen 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:
OKX_API_KEY=your-okx-api-key
OKX_SECRET_KEY=your-okx-secret-key
OKX_PASSPHRASE=your-okx-passphraseThe onchainos CLI expects OKX_PASSPHRASE. The bot also accepts the older local alias OKX_API_PASSPHRASE and passes it through as OKX_PASSPHRASE when spawning the CLI. It's IPv4-only.
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:
Find
https://api.telegram.org/bot<YOUR_BOT_TOKEN>/getUpdates"chat":{"id":XXXXXXX}in the JSON. That's your chat ID. - Set in
.env:TELEGRAM_BOT_TOKEN=8775xxxxxxx:AAGxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx TELEGRAM_CHAT_ID=518183629
The bot is gated to your chat ID only β random users who find it can't talk to it.
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:
π https://platform.minimax.io/subscribe/token-plan?code=K0Q2oDUiwK&source=link
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:MINIMAX_API_KEY=your-token-plan-key
Then choose LLM Managed from Telegram /settings β Exit Strategy.
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 ===
JUP_API_KEY=jup_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
HELIUS_API_KEY=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
OKX_API_KEY=your-okx-api-key
OKX_SECRET_KEY=your-okx-secret-key
OKX_PASSPHRASE=your-okx-passphrase
PRIV_B58=base58-encoded-solana-keypair-secret
# === RPC ===
RPC_URL=https://beta.helius-rpc.com?api-key=${HELIUS_API_KEY}
# === TRADING BASICS ===
# Exit strategy, TP ladder, trail, stop, moonbag, and milestones are edited
# in Telegram and saved to state/settings.json.
BUY_SIZE_SOL=0.02 # SOL per trade
MAX_CONCURRENT_POSITIONS=10 # max open positions
# === ALERT FILTERS (0 = disabled) ===
MAX_ALERT_AGE_MINS=0
MIN_LIQUIDITY_USD=0
MIN_SCORE=0
MAX_RUG_RATIO=0
MAX_BUNDLER_PCT=0
MAX_TOP10_PCT=0
REQUIRE_RISING_LIQ=false
MIN_ALERT_MCAP=0 # only buy alerts at or above this mcap ($). Also editable via /mcapfilter or /stats
MAX_ALERT_MCAP=0 # only buy alerts at or below this mcap ($). Also editable via /mcapfilter or /stats
# === POLLING ===
SCG_POLL_MS=3000 # how often to poll SCG for new alerts
PRICE_POLL_MS=3000 # how often to update prices for open positions
# === 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 ===
TELEGRAM_BOT_TOKEN=8775xxxxxxx:AAGxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
TELEGRAM_CHAT_ID=518183629
# === LLM EXIT ADVISOR ===
MINIMAX_API_KEY= # required for LLM Managed exit strategyOn 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).
DRY_RUN=true npm run startIn dry-run, the bot fetches alerts and prices but does not submit any swap transactions. Watch the logs to verify everything is wired correctly.
- Set
DRY_RUN=falsein.env. - Make sure your wallet has enough SOL (at least 10Γ your
BUY_SIZE_SOLplus ~0.01 SOL for fees). - Run:
npm run start
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 bootOption B β 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.targetsudo systemctl enable --now moonbags
journalctl -u moonbags -fYou 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.
Every command is gated to the TELEGRAM_CHAT_ID 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 MINIMAX_API_KEY is empty. |
/pause |
Stop taking new SCG 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 SCG 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 |
Run the SCG alert backtester. Uses each alert's alert_time as entry, requires post-signal OHLCV runway, compares Trail, Fixed TP, and TP Ladder, then lets you tap a row to adopt the exit strategy live. |
/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. |
/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. |
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)
Settings you can edit live via /settings:
BUY_SIZE_SOLβ SOL per tradeMAX_CONCURRENT_POSITIONSβ 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
NOT editable from Telegram (security boundary):
- API keys (
JUP_API_KEY,HELIUS_API_KEY,OKX_API_KEY,OKX_SECRET_KEY,OKX_PASSPHRASE,MINIMAX_API_KEY,TELEGRAM_BOT_TOKEN) - Wallet key (
PRIV_B58) DRY_RUN(intentionally requires manual.envedit + restart)
When you choose LLM Managed in Telegram, MoonBags consults MiniMax M2.7 every 30 seconds for each armed position.
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:
- Bearish (reactive):
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)
- Bearish (reactive):
- Memory: recent same-position decisions, global closed-trade track record, and similar historical cases when the same evidence facts appeared
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: partial_exit and exit_now 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
Each consult writes an audit record to state/llm_audits/<mint>.json with the exact prompt payload, evidence facts, similar-case memory, raw tool arguments, parsed decision, and gate result.
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.
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.
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).
- 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.
π 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+.
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.
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):
- Token icon (real Jupiter image) inside a colored ring (green/blue/coral by PnL)
- Name + $SYMBOL + ARMED chip when applicable
- GMGN + JUP external links + copy-mint button
- Jupiter enrichment badges: verification status, organic score, mint/freeze authority safety, top-10 holder concentration warnings, dev token history
- Big PnL %, drawdown-from-peak progress bar (the "drawdown limit" β fills shrink as we retrace from peak)
- Real 1m price chart β last ~60 minutes of OKX kline data as an SVG line + area, dashed reference line at entry price
- SELL button (currently a stub β manual sell via Telegram
/positions)
- Live feed β compact mini cards for recent SCG 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.
Localhost-only with no auth β don't expose it externally. For remote access, tunnel via SSH:
ssh -L 8787:localhost:8787 youruser@yourserverThen open http://localhost:8787/ in your local browser.
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.
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.
- 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.
Use /settings from Telegram. Common adjustments:
- Markets are crazy bullish: raise
BUY_SIZE_SOLto 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.
/sellall
CONFIRM
Sells every open position immediately via Jupiter. Confirms with a summary message.
pm2 restart moonbags
pm2 restart moonbags --update-env # after .env or PATH changes
# or
sudo systemctl restart moonbagsRelease History
| Version | Changes | Urgency | Date |
|---|---|---|---|
| 0.0.0 | No release found β using repo HEAD | High | 4/20/2026 |
