Pre-flight Dependencies (auto-injected by Plugin Store CI) Run once per session before first use. These checks ensure required tools are installed. Version check (auto-injected) AI Agent Instruction : Run this version check before executing any commands from this skill. The check is a read-only fetch from — it does not install anything. If it reports , you MUST stop, surface the message to the user verbatim, and obtain explicit confirmation before running the suggested command. Do not run the update silently. After a confirmed update completes, re-read this SKILL.md before proceeding. Install…

+(r.mc/1000).toFixed(1)+'K';\n if(r.confidence)msg+=' ['+r.confidence+']';\n h+='\u003cdiv class=\"frow\">\u003cspan class=\"ftime\">'+(r.t||'')+'\u003c/span>\u003cspan class=\"fbadge '+badge+'\">'+tier+'\u003c/span>\u003cspan class=\"fmsg\">'+msg+'\u003c/span>\u003c/div>';\n });\n $('feed-list').innerHTML=h;\n}\n\nfunction renderSignals(sigs){\n var h='';\n sigs.forEach(function(s){\n var tc='t-scalp';if(s.tier==='MINIMUM')tc='t-minimum';if(s.tier==='STRONG')tc='t-strong';\n var logo=s.logo?'\u003cimg src=\"'+s.logo+'\" style=\"width:18px;height:18px;border-radius:50%;vertical-align:middle\"> ':'';\n var mig=s.near_migration?'\u003cspan style=\"color:#ed7088;font-size:9px\">\\u{1F525}MIGR\u003c/span>':'';\n var confTag=s.confidence?(s.confidence>=70?'\u003cspan style=\"color:#4fd2c1\">['+s.confidence+'\\u2b50]\u003c/span>':'\u003cspan style=\"color:#5a6a68\">['+s.confidence+']\u003c/span>'):'';\n h+='\u003cdiv class=\"scard\">\u003cdiv class=\"shead\">'+logo+'\u003cspan class=\"sname\">'+s.symbol+'\u003c/span>\u003cspan class=\"tier '+tc+'\">'+s.tier+'\u003c/span>'+mig+confTag+'\u003cspan class=\"stime\">'+s.t+'\u003c/span>\u003c/div>';\n h+='\u003cdiv class=\"sr1\">\u003cspan class=\"mc\">MC

Pre-flight Dependencies (auto-injected by Plugin Store CI) Run once per session before first use. These checks ensure required tools are installed. Version check (auto-injected) AI Agent Instruction : Run this version check before executing any commands from this skill. The check is a read-only fetch from — it does not install anything. If it reports , you MUST stop, surface the message to the user verbatim, and obtain explicit confirmation before running the suggested command. Do not run the update silently. After a confirmed update completes, re-read this SKILL.md before proceeding. Install…

+(s.mc/1000).toFixed(1)+'K\u003c/span>\u003cspan class=\"tp\">TP1

Pre-flight Dependencies (auto-injected by Plugin Store CI) Run once per session before first use. These checks ensure required tools are installed. Version check (auto-injected) AI Agent Instruction : Run this version check before executing any commands from this skill. The check is a read-only fetch from — it does not install anything. If it reports , you MUST stop, surface the message to the user verbatim, and obtain explicit confirmation before running the suggested command. Do not run the update silently. After a confirmed update completes, re-read this SKILL.md before proceeding. Install…

+((s.tp1_mc||0)/1000).toFixed(1)+'K\u003c/span>\u003cspan class=\"s1\">S1

Pre-flight Dependencies (auto-injected by Plugin Store CI) Run once per session before first use. These checks ensure required tools are installed. Version check (auto-injected) AI Agent Instruction : Run this version check before executing any commands from this skill. The check is a read-only fetch from — it does not install anything. If it reports , you MUST stop, surface the message to the user verbatim, and obtain explicit confirmation before running the suggested command. Do not run the update silently. After a confirmed update completes, re-read this SKILL.md before proceeding. Install…

+((s.s1_mc||0)/1000).toFixed(1)+'K\u003c/span>\u003c/div>';\n h+='\u003cdiv class=\"sr2\">A:'+(s.sig_a_ratio||0)+'\\u00d7 B:'+(s.sig_b_ratio||0).toFixed(1)+'\\u00d7 C:'+(s.ratio_c||0)+'\\u00d7 age:'+(s.age_m||0)+'m\u003c/div>';\n h+='\u003cdiv class=\"saddr\">'+(s.addr||'')+'\u003c/div>\u003c/div>';\n });\n $('sig-list').innerHTML=h;$('sig-cnt').textContent=sigs.length;\n}\n\nfunction renderPositions(pos){\n var keys=Object.keys(pos);$('pos-cnt').textContent=keys.length;\n var h='';\n keys.forEach(function(addr){\n var p=pos[addr];var pct=p.pnl_pct||0;var cls=pct>=0?'in-profit':'in-loss';\n var mig=p.near_migration?'\u003cspan style=\"color:#ed7088;font-size:9px;margin-left:4px\">\\u{1F525}MIGR\u003c/span>':'';\n var logo=p.logo?'\u003cimg src=\"'+p.logo+'\" style=\"width:16px;height:16px;border-radius:50%;vertical-align:middle\"> ':'';\n var psign=pct>=0?'+':'';\n h+='\u003cdiv class=\"pcard '+cls+'\">\u003cdiv class=\"phead\">'+logo+'\u003cspan class=\"pname\">'+p.symbol+'\u003c/span>\u003cspan class=\"tier t-'+(p.tier||'scalp').toLowerCase()+'\" style=\"font-size:9px\">'+p.tier+'\u003c/span>'+mig+'\u003cspan class=\"ppnl '+(pct>=0?'c-grn':'c-red')+'\">'+psign+pct.toFixed(1)+'%\u003c/span>\u003c/div>';\n var elapsed=((Date.now()/1000-(p.entry_ts||0))/60).toFixed(1);\n h+='\u003cdiv class=\"prow\">'+p.sol_in+' SOL | T+'+elapsed+'m | rem:'+((p.remaining||1)*100).toFixed(0)+'%'+(p.tp1_hit?' \u003cspan class=\"hi\">TP1\\u2713\u003c/span>':'')+(p.stuck?' \u003cspan class=\"lo\">STUCK\u003c/span>':'')+'\u003c/div>\u003c/div>';\n });\n $('pos-list').innerHTML=h;\n}\n\nfunction renderTrades(trades){\n $('trade-cnt').textContent=trades.length;\n var h='';\n trades.forEach(function(t){\n var cls=t.pnl_pct>=0?'c-grn':'c-red';\n var sign=t.pnl_pct>=0?'+':'';\n h+='\u003cdiv class=\"tcard\">\u003cspan class=\"sym\">'+t.symbol+'\u003c/span>\u003cspan class=\"meta\">'+t.tier+' | '+t.reason+' | MC

Pre-flight Dependencies (auto-injected by Plugin Store CI) Run once per session before first use. These checks ensure required tools are installed. Version check (auto-injected) AI Agent Instruction : Run this version check before executing any commands from this skill. The check is a read-only fetch from — it does not install anything. If it reports , you MUST stop, surface the message to the user verbatim, and obtain explicit confirmation before running the suggested command. Do not run the update silently. After a confirmed update completes, re-read this SKILL.md before proceeding. Install…

+((t.entry_mc||0)/1000).toFixed(1)+'K\\u2192

Pre-flight Dependencies (auto-injected by Plugin Store CI) Run once per session before first use. These checks ensure required tools are installed. Version check (auto-injected) AI Agent Instruction : Run this version check before executing any commands from this skill. The check is a read-only fetch from — it does not install anything. If it reports , you MUST stop, surface the message to the user verbatim, and obtain explicit confirmation before running the suggested command. Do not run the update silently. After a confirmed update completes, re-read this SKILL.md before proceeding. Install…

+((t.exit_mc||0)/1000).toFixed(1)+'K\u003c/span>\u003cspan class=\"pnl '+cls+'\">'+sign+t.pnl_pct.toFixed(1)+'%\u003c/span>\u003c/div>';\n });\n $('trade-list').innerHTML=h;\n}\n\nfunction drawPnl(){\n var c=$('pnl-chart');if(!c||!pnlHistory.length)return;\n var ctx=c.getContext('2d');var W=c.width=c.offsetWidth;var H=c.height=c.offsetHeight;\n ctx.clearRect(0,0,W,H);\n var vals=pnlHistory.slice(-60);\n var mn=Math.min.apply(null,vals);var mx=Math.max.apply(null,vals);\n var range=mx-mn||0.001;\n ctx.beginPath();ctx.strokeStyle='#4fd2c1';ctx.lineWidth=1.5;\n vals.forEach(function(v,i){\n var x=i/(vals.length-1)*W;var y=H-(v-mn)/range*H;\n i===0?ctx.moveTo(x,y):ctx.lineTo(x,y);\n });\n ctx.stroke();\n var last=vals[vals.length-1];\n $('chart-foot').innerHTML='\u003cspan class=\"'+(last>=0?'vg':'vr')+'\">'+last.toFixed(4)+' SOL\u003c/span>';\n}\n\nasync function poll(){\n try{\n var r=await fetch('/api/state');var d=await r.json();\n $('st-cyc').textContent=d.cycle;\n $('st-status').textContent=d.status;\n var posKeys=Object.keys(d.positions||{});\n $('st-pos').textContent=posKeys.length;\n var stats=d.stats||{};\n var wins=stats.wins||0;var losses=stats.losses||0;var total=wins+losses;\n $('st-trades').textContent=total;\n var pw=stats.pos_wins||0;var pl=stats.pos_losses||0;var pt=pw+pl;\n $('st-wr').textContent=pt?'WR '+(pw/pt*100).toFixed(0)+'% ('+pw+'W/'+pl+'L)':'';\n var pnl=stats.net_sol||0;\n $('st-pnl').textContent=(pnl>=0?'+':'')+pnl.toFixed(4);\n $('st-pnl').className='stat-big '+(pnl>=0?'c-grn':'c-red');\n var curve=d.pnl_curve||[];\n if(curve.length!==_lastTradeCount){_lastTradeCount=curve.length;pnlHistory=[0].concat(curve);drawPnl();}\n else if(!_pnlCurveLoaded&&curve.length){_pnlCurveLoaded=true;pnlHistory=[0].concat(curve);drawPnl();}\n if(d.soul){updateSoul(d.soul);updateSoulThoughts(d.soul.reflections);updateSessionStats(d.soul);}\n var feed=d.feed||[];\n if(feed.length&&feed[0].seq>lastSeq){lastSeq=feed[0].seq;renderFeed(feed.slice(0,100));}\n $('feed-cnt').textContent=feed.length;\n renderSignals((d.signals||[]).slice(0,50));\n renderPositions(d.positions||{});\n renderTrades((d.trades||[]).slice(0,50));\n var bar=$('prog-bar');\n if(d.cycle!==_lastCycle){_lastCycle=d.cycle;_cycleStartMs=Date.now();bar.style.transition='none';bar.style.width='0%';bar.offsetHeight;}\n var elapsed=(Date.now()-_cycleStartMs)/1000;\n var prog=Math.min(elapsed/10*100,99);\n bar.style.transition='width 2s linear';\n bar.style.width=prog+'%';\n var eb=$('err-bar');if(eb)eb.style.display='none';\n }catch(e){\n var eb=$('err-bar');\n if(eb){eb.textContent='\\u26a0 poll error: '+(e&&e.message?e.message:e);eb.style.display='block'}\n }\n}\nsetInterval(poll,2000);poll();\n\u003c/script>\u003c/body>\u003c/html>\n","content_type":"text/html; charset=utf-8","language":"markup","size":17323,"content_sha256":"e21f54b813f7980803917822a1a1851cb3a0e076a2d87339437e8bce7066693c"},{"filename":"plugin.yaml","content":"schema_version: 1\nname: meme-trench-scanner\nversion: \"1.0.0\"\ndescription: \"Meme Trench Scanner v1.0 — Solana Meme automated trading bot with 11 Launchpad coverage, 7-layer exit system, TraderSoul AI observation\"\nauthor:\n name: \"yz06276\"\n github: \"yz06276\"\nlicense: MIT\ncategory: strategy\ntags:\n - solana\n - onchainos\n - trading-bot\n\ncomponents:\n skill:\n repo: \"yz06276/meme-trench-scanner\"\n commit: \"35c67c3350636333fcc1c23129f3c4056751d97e\"\n\napi_calls: []\ntype: community-developer\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":497,"content_sha256":"a59cc38b37c31352d6cd45c3a813e915245918f5340ae3b8d40ec1d9f74a13c3"},{"filename":"README.md","content":"# Meme Trench Scanner - Meme 扫链\n\nSolana Meme automated trading bot — scans 11 Launchpads, detects signals, executes trades, manages exits. All on-chain operations powered by [onchainos](https://github.com/okx/onchainos-skills) Agentic Wallet (TEE signing, no API key needed).\n\nSolana Meme 自动交易机器人 — 覆盖 11 个 Launchpad,检测信号,执行交易,管理退出。所有链上操作由 [onchainos](https://github.com/okx/onchainos-skills) Agentic Wallet 驱动(TEE 签名,无需 API Key)。\n\n## Features / 功能\n\n- **11 Launchpad Coverage / 覆盖 11 个 Launchpad** — pump.fun, Believe, LetsBonk, and more\n- **Triple Signal Detection / 三重信号检测** — TX acceleration + Volume surge + B/S ratio\n- **5m/15m Precision / 5 分钟/15 分钟精度** — Raw trades calculation for buy/sell ratio\n- **Deep Safety Checks / 深度安全检测** — Dev rug history, Bundler holdings, LP Lock, Aped wallets\n- **7-Layer Exit System / 7 层退出系统** — Emergency exit, FAST_DUMP crash detection, stop loss, trailing stop, tiered TP\n- **TOP_ZONE Filter / 价格位置过滤** — Skips tokens near ATH (>85%) to avoid chasing\n- **TraderSoul AI / AI 观察系统** — Records trading behavior and personality tags\n- **Web Dashboard / 实时仪表盘** — http://localhost:3241\n\n## Install / 安装\n\n```bash\nnpx skills add okx/plugin-store --skill meme-trench-scanner\n```\n\n## Risk Warning / 风险提示\n\n> Meme tokens are the highest-risk asset class. Tokens can go to zero within minutes. Always test in Paper Mode first.\n\n> Meme 代币是最高风险资产类别,可能在几分钟内归零。请先在纸盘模式下测试。\n\n## License\n\nMIT\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":1690,"content_sha256":"5a475305efab3d9242b08aa89e4af7d78bad5e2f4083ef78d54c2dc5775070be"},{"filename":"scripts/config.py","content":"\"\"\"\nMeme Trench Scanner v1.0 — Strategy Configuration\nModify this file to adjust strategy parameters without changing scan_live.py\n\n⚠️ Disclaimer:\nThis script and all parameter configurations are for educational research and\ntechnical reference only, and do not constitute any investment advice.\nMeme Trench Scanner targets newly launched small-cap Meme tokens, which carry\nextremely high risk, including but not limited to:\n - Tokens may go to zero within minutes of launch (Rug Pull, Dev Dump)\n - Extremely low liquidity; you may be unable to sell after buying (Honeypot, LP removal)\n - Fees and slippage from high-frequency trading may erode most profits\n - Smart contracts are unaudited and may contain unforeseen vulnerabilities\nUsers should adjust all parameters according to their own risk tolerance and bear\nfull responsibility for any losses resulting from the use of this strategy.\nIt is recommended to test thoroughly using Paper Mode first.\n\"\"\"\n\n# ── Operating Mode ─────────────────────────────────────────────────────\nPAUSED = True # True=manually paused (no new positions, monitoring continues), False=normal trading\nPAPER_TRADE = True # True=Paper Trading (recommended to test first), False=Live Trading\n\n# ── Position ───────────────────────────────────────────────────────────\nSOL_PER_TRADE = {\"SCALP\": 0.25, \"MINIMUM\": 0.15, \"STRONG\": 0.25}\nMAX_SOL = 1.00 # Max total exposure (SOL)\nMAX_POSITIONS = 7 # Max concurrent positions\nSLIPPAGE_BUY = {\"SCALP\": 8, \"MINIMUM\": 10, \"STRONG\": 10} # Buy slippage (integer percent, 8=8%)\nSLIPPAGE_SELL = 50 # Fixed high sell slippage (low liquidity small-cap tokens)\nSOL_GAS = 0.05 # Reserved for fees\nCOST_PER_LEG = 0.003 # OKX DEX 0.3% per leg\nMAX_TRADES = 50 # Auto-stop (0=unlimited)\n\n# ── Take Profit ────────────────────────────────────────────────────────\nTP1_PCT = 0.15 # +15% first take profit\nTP1_SELL = {\"SCALP\": 0.60, \"hot\": 0.50, \"quiet\": 0.40} # TP1 partial sell ratio\nTP2_PCT = 0.25 # +25% second take profit\nTP2_SELL = {\"SCALP\": 1.00, \"hot\": 1.00, \"quiet\": 1.00} # TP2 full exit\n\n# ── Stop Loss ──────────────────────────────────────────────────────────\nS1_PCT = {\"SCALP\": -0.15, \"hot\": -0.20, \"quiet\": -0.20}\nHE1_PCT = -0.50 # -50% emergency exit\nTRAILING_DROP = 0.05 # 5% drawdown after TP1 → full exit\nFAST_DUMP_PCT = -0.15 # -15% within 10s → instant exit\nFAST_DUMP_SEC = 10 # Fast dump detection window (seconds)\n\n# ── Time Stop ──────────────────────────────────────────────────────────\nS3_MIN = {\"SCALP\": 5, \"hot\": 8, \"quiet\": 15} # minutes\nMAX_HOLD_MIN = 30 # Max position hold time (minutes)\n\n# ── Session Risk Control ───────────────────────────────────────────────\nMAX_CONSEC_LOSS = 2 # N consecutive losses → pause\nPAUSE_CONSEC_SEC = 900 # Consecutive loss pause duration (seconds, 15min)\nPAUSE_LOSS_SOL = 0.30 # Cumulative loss >= N SOL → pause 30min\nSTOP_LOSS_SOL = 0.50 # Cumulative loss >= N SOL → stop trading\n\n# ── Scanning ───────────────────────────────────────────────────────────\nLOOP_SEC = 10 # Scan interval (seconds)\nMONITOR_SEC = 1 # Position monitor interval (seconds)\nCHAIN_INDEX = \"501\" # Solana\nSOL_ADDR = \"11111111111111111111111111111111\"\nDASHBOARD_PORT = 3241\n\n# ── Basic Filters ──────────────────────────────────────────────────────\nAGE_HARD_MIN = 240 # Min token age (seconds, 4min)\nAGE_SOFT_MIN = 300 # Early window threshold (seconds, 5min)\nAGE_MAX = 86_400 # Max token age (seconds, 24h)\nMC_CAP = 800_000 # MC upper limit ($)\nMC_MIN = 50_000 # MC lower limit ($)\nLIQ_MIN = 10_000 # Liquidity lower limit ($)\nBS_MIN = 1.0 # 1h B/S ratio pre-filter\nDUMP_FLOOR = -40 # Max single candle drop (%)\n\n# ── Signal Thresholds ─────────────────────────────────────────────────\nSIG_A_THRESHOLD = 1.25 # TX acceleration ratio threshold\nMIN_CONFIDENCE = 25 # Minimum confidence\nSIG_A_FLOOR_TXS_MIN = 45 # TX acceleration floor (txs/min)\nHOT_MODE_RATIO = 0.40 # Hot Mode trigger ratio\n\n# ── Safety Detection ──────────────────────────────────────────────────\nVOLMC_MIN_RATIO = 0.02 # Vol/MC minimum ratio\nTF_MIN_VOLUME = 5_000 # 1h minimum volume ($)\nTF_MAX_BUNDLERS = 15 # Bundler holdings upper limit (%)\nMIN_HOLDERS = 50 # Minimum holders count\nDEV_SELL_DROP_PCT = 60 # Dev dump detection: ATH drawdown %\nDEV_SELL_VOL_MULT = 10 # Dev dump detection: volume multiplier\nBUNDLE_ATH_PCT_MAX = 25 # Bundler ATH percentage upper limit (%)\nRUG_RATE_MAX = 0.50 # Dev rug rate upper limit\nMAX_DEV_RUG_COUNT = 5 # Dev rug count absolute upper limit (fallback beyond rate-based logic)\nDEV_HOLD_DEEP_MAX = 0.10 # Dev deep holdings upper limit (decimal, 0.10=10%)\nDEV_MAX_LAUNCHED = 800 # Dev historical token launch count upper limit\nBUNDLE_MAX_COUNT = 30 # Bundler wallet count upper limit\n\n# ── Token List Filters ─────────────────────────────────────────────────\nTOP10_HOLD_MAX = 40 # Top 10 holdings upper limit (%)\nINSIDERS_MAX = 15 # Insider upper limit (%)\nSNIPERS_MAX = 20 # Sniper upper limit (%)\nFRESH_WALLET_MAX = 40 # Fresh wallet upper limit (%)\nBOT_TRADERS_MAX = 100\nAPED_WALLET_MAX = 10\nWASH_PRICE_CHG_MIN = 0.01 # Wash trading detection: min price change\nBOND_NEAR_PCT = 0.80 # Near migration threshold\n\n# ── LP Lock ────────────────────────────────────────────────────────────\nLP_LOCK_MIN_PCT = 0.80\nLP_LOCK_MIN_HOURS = 0\nLP_LOCK_STRICT = False\n\n# ── Protocol Support (11 Solana Launchpads) ───────────────────────────\nPROTOCOL_PUMPFUN = \"120596\"\nPROTOCOL_LETSBONK = \"136266\"\nPROTOCOL_BELIEVE = \"134788\"\nPROTOCOL_BONKERS = \"139661\"\nPROTOCOL_JUPSTUDIO = \"137346\"\nPROTOCOL_BAGS = \"129813\"\nPROTOCOL_MOONSHOT_MONEY = \"133933\"\nPROTOCOL_LAUNCHLAB = \"136137\"\nPROTOCOL_MOONSHOT = \"121201\"\nPROTOCOL_METEORADBC = \"136460\"\nPROTOCOL_MAYHEM = \"139048\"\nDISCOVERY_PROTOCOLS = [\n PROTOCOL_PUMPFUN, PROTOCOL_LETSBONK, PROTOCOL_BELIEVE,\n PROTOCOL_BONKERS, PROTOCOL_JUPSTUDIO, PROTOCOL_BAGS,\n PROTOCOL_MOONSHOT_MONEY, PROTOCOL_LAUNCHLAB, PROTOCOL_MOONSHOT,\n PROTOCOL_METEORADBC, PROTOCOL_MAYHEM,\n]\n\n# ── NEW Stage Discovery ───────────────────────────────────────────────\nMC_MIN_NEW = 50_000\nMC_MAX_NEW = 800_000\nAGE_MAX_NEW = 86_400\n\n# ── Pullback Watchlist ─────────────────────────────────────────────────\nWATCHLIST_TIMEOUT_SEC = 180 # 3 min\nWATCHLIST_DUMP_DROP = 0.15 # 15% drop = dump\nWATCHLIST_PULLBACK_DROP = 0.05 # 5% drop = pullback\nWATCHLIST_BS_MIN = 1.5 # B/S ratio secondary confirmation\n\n# ── Trade Blacklist ────────────────────────────────────────────────────\n_WSOL_MINT_STR = \"So11111111111111111111111111111111111111112\"\n_IGNORE_MINTS = {_WSOL_MINT_STR, \"7JzLK1eq9MEq9mPNGMSr2PUoF2CCUG8corxKUbgxvJ3V\"}\n_NEVER_TRADE_MINTS = _IGNORE_MINTS | {\n \"11111111111111111111111111111111\", # native SOL\n \"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v\", # USDC\n \"Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB\", # USDT\n \"mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So\", # mSOL\n \"7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj\", # stSOL\n \"bSo13r4TkiE4KumL71LsHTPpL2euBYLFx6h9HP3piy1\", # bSOL\n \"J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn\", # JitoSOL\n}\n","content_type":"text/x-python; charset=utf-8","language":"python","size":9268,"content_sha256":"582fdca808618a2bc99cc419ff4b32ceb8012608fe5fa76d51aaa16fbed669d5"},{"filename":"scripts/risk_check.py","content":"\"\"\"\nrisk_check.py — Standalone pre/post trade risk assessment for Solana meme tokens.\nDrop-in module for any skill: Top Rank Tokens Sniper, Smart Money Signal Copy Trade, Meme Trench Scanner, or future strategies.\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nOVERVIEW\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nTwo public functions:\n\n pre_trade_checks(addr, sym) — pre-trade gate. Call before entering any position.\n post_trade_flags(addr, sym) — post-trade monitor. Call periodically while in position.\n\nAll data comes from onchainos CLI (~/.local/bin/onchainos). No extra API keys needed.\nRequires onchainos v2.1.0+.\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nSEVERITY GRADES\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nGrade 4 — HARD BLOCK. Do not enter. Abort immediately.\n Triggers: honeypot, buy/sell tax >50%, dev actively removing liquidity,\n liquidity \u003c$5K, OKX riskControlLevel ≥4, active dev/insider dump ≥5 SOL/min.\n\nGrade 3 — STRONG WARNING. Do not enter. Too risky.\n Triggers: serial rugger (≥3 rugs), rug rate >50%, LP \u003c80% burned,\n volume plunge tag, snipers >15%,\n suspicious wallets >10%, soft rug velocity 1–5 SOL/min,\n single LP provider with unburned LP, wash trading (round-trip wallets),\n coordinated holder sells (dev/whale/insider/sniper ≥2 sells in 10 min).\n\nGrade 2 — CAUTION. Proceed with awareness. Log the flags.\n Triggers: top 10 wallets hold >30%, bundles still in >5%, dev sold all (non-CTO),\n paid DexScreener listing, no smart money detected.\n\nGrade 0 — PASS. All checks clear.\n\nresult[\"pass\"] is True when grade \u003c 3 (grades 0 and 2 are both tradeable).\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nPRE-TRADE INTEGRATION (pre_trade_checks)\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nCall this BEFORE the swap/buy, after basic filters (liquidity, MC) pass.\nStore the entry snapshots from result[\"raw\"] on the position record for\npost-trade monitoring — they are needed by post_trade_flags().\n\n import sys, os\n sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n from risk_check import pre_trade_checks, post_trade_flags\n\n # --- Pre-trade gate (quick=True: 4 calls, ~0.8s — includes wash trading check) ---\n result = pre_trade_checks(token_address, token_symbol, quick=True)\n\n if result[\"grade\"] >= 4:\n log(f\"BLOCKED {sym} — {result['reasons']}\")\n return # hard stop, do not trade\n\n if result[\"grade\"] == 3:\n log(f\"WARN {sym} — {result['reasons']}\")\n return # too risky, skip\n\n if result[\"grade\"] == 2:\n log(f\"CAUTION {sym} — {result['cautions']}\")\n # proceed but note the flags\n\n # --- Execute buy ---\n execute_swap(...)\n\n # --- Persist entry snapshots for post-trade use ---\n position[\"entry_liquidity_usd\"] = result[\"raw\"][\"liquidity_usd\"]\n position[\"entry_top10\"] = result[\"raw\"][\"info\"].get(\"top10HoldPercent\", 0)\n position[\"entry_sniper_pct\"] = result[\"raw\"][\"info\"].get(\"sniperHoldingPercent\", 0)\n position[\"risk_last_checked\"] = 0 # tracks throttle timestamp\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nPOST-TRADE INTEGRATION (post_trade_flags)\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nCall this inside your position monitor loop. THROTTLE to once per 60 seconds\nper position — each call makes 4–6 onchainos API requests.\n\nIMPORTANT: Run post_trade_flags() in a background thread so it does not block\nyour monitor loop. It makes multiple sequential API calls (~1–2s) and must not\nstall position updates, trailing stop logic, or TP/SL checks for other positions.\n\n import threading\n\n def _check_flags(pos):\n flags = post_trade_flags(\n pos[\"address\"],\n pos[\"symbol\"],\n entry_liquidity_usd = pos[\"entry_liquidity_usd\"],\n entry_top10 = pos[\"entry_top10\"],\n entry_sniper_pct = pos[\"entry_sniper_pct\"],\n )\n for flag in flags:\n log(flag)\n if flag.startswith(\"EXIT_NOW\"):\n close_position(pos, reason=flag)\n break\n elif flag.startswith(\"EXIT_NEXT_TP\"):\n # tighten trailing stop or take partial profit early\n pass\n elif flag.startswith(\"REDUCE_POSITION\"):\n # cut size if partial sells are supported\n pass\n\n # --- Inside monitor loop, per open position (throttled to once per 60s) ---\n now = time.time()\n if now - position.get(\"risk_last_checked\", 0) >= 60:\n position[\"risk_last_checked\"] = now\n threading.Thread(target=_check_flags, args=(position,), daemon=True).start()\n\nPost-trade flag meanings:\n EXIT_NOW: ... — close immediately (dev rug, liquidity drain >30%, active dump, holder selling)\n EXIT_NEXT_TP: ... — exit at next take profit or trailing stop (volume plunge, soft rug)\n REDUCE_POSITION: ... — cut position size (sniper spike)\n ALERT: ... — informational, no action required\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nCLI USAGE (standalone token check)\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n python3 risk_check.py \u003ctoken_address> [symbol]\n\nExample:\n python3 risk_check.py 58piN8dJJBcjHj28LZzTGJTygAX6DoF22sfY1R7Apump horseballs\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nWHAT IT CHECKS (data sources)\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n [quick + full mode]\n security token-scan → honeypot flag, buy/sell tax\n token advanced-info → dev rug history, LP burn %, sniper %, tokenTags,\n riskControlLevel, top10 hold %, bundle %, suspicious wallets\n token price-info → liquidity USD snapshot\n token trades → all recent trades (wash trading: round-trip + concentration)\n\n [full mode only — quick=False]\n token liquidity → LP pool creators (concentration check)\n token trades --tag-filter → dev (2), whale (4), insider (6), sniper (7) sell activity\n used for: selling velocity + holder sell coordination\n\"\"\"\n\nimport subprocess, json, os, time\nfrom collections import defaultdict\n\n_ONCHAINOS = os.path.expanduser(\"~/.local/bin/onchainos\")\n_CHAIN = \"solana\"\n_CHAIN_ID = \"501\"\n\n# Selling velocity — SOL sold per minute thresholds\n_SELL_VEL_WARN_SOL_PM = 1.0 # G3: > 1 SOL/min from dev/insiders\n_SELL_VEL_BLOCK_SOL_PM = 5.0 # G4: > 5 SOL/min (active dump)\n\n# Wash trading — round-trip detection thresholds\n_WASH_ROUNDTRIP_RATIO = 0.50 # G3: ≥50% of active wallets round-tripped alone\n_WASH_ROUNDTRIP_SOFT = 0.30 # G3: ≥30% round-tripped AND concentration above threshold\n_WASH_CONC_THRESHOLD = 0.40 # top-3 wallets driving >40% of all trades = suspicious\n\n# LP checks\n_LP_SINGLE_PROVIDER_WARN = True # G3: single LP provider + LP not burned\n_LP_DRAIN_EXIT_PCT = 0.30 # post-trade: exit if liquidity drops > 30%\n\n\n# ── Internal CLI wrapper ───────────────────────────────────────────────────────\n\ndef _onchainos(*args, timeout: int = 20) -> dict:\n try:\n r = subprocess.run([_ONCHAINOS, *args],\n capture_output=True, text=True, timeout=timeout)\n return json.loads(r.stdout)\n except Exception:\n return {\"ok\": False, \"data\": None}\n\ndef _data(r: dict):\n d = r.get(\"data\")\n if isinstance(d, list):\n return d[0] if d else {}\n return d or {}\n\ndef _data_list(r: dict) -> list:\n d = r.get(\"data\")\n return d if isinstance(d, list) else []\n\n\n# ── API calls ─────────────────────────────────────────────────────────────────\n\ndef _security_scan(addr: str) -> dict:\n r = _onchainos(\"security\", \"token-scan\",\n \"--tokens\", f\"{_CHAIN_ID}:{addr}\")\n d = _data(r)\n return d if isinstance(d, dict) else {}\n\ndef _advanced_info(addr: str) -> dict:\n r = _onchainos(\"token\", \"advanced-info\",\n \"--chain\", _CHAIN, \"--address\", addr)\n d = _data(r)\n return d if isinstance(d, dict) else {}\n\ndef _liquidity_usd(addr: str) -> float:\n \"\"\"Current total liquidity in USD from price-info.\"\"\"\n r = _onchainos(\"token\", \"price-info\",\n \"--chain\", _CHAIN, \"--address\", addr)\n items = _data_list(r)\n if not items:\n items = [_data(r)]\n for item in items:\n if isinstance(item, dict) and item.get(\"liquidity\"):\n try:\n return float(item[\"liquidity\"])\n except (ValueError, TypeError):\n pass\n return -1.0\n\ndef _lp_pools(addr: str) -> list:\n \"\"\"Top LP pools with creator info.\"\"\"\n r = _onchainos(\"token\", \"liquidity\",\n \"--chain\", _CHAIN, \"--address\", addr)\n return _data_list(r)\n\ndef _tagged_trades(addr: str, tag: int, limit: int = 50) -> list:\n \"\"\"Trades filtered by wallet tag (2=dev, 4=whale, 6=insider, 7=sniper).\"\"\"\n r = _onchainos(\"token\", \"trades\",\n \"--chain\", _CHAIN, \"--address\", addr,\n \"--tag-filter\", str(tag),\n \"--limit\", str(limit))\n return _data_list(r)\n\ndef _recent_trades(addr: str, limit: int = 100) -> list:\n \"\"\"All recent trades.\"\"\"\n r = _onchainos(\"token\", \"trades\",\n \"--chain\", _CHAIN, \"--address\", addr,\n \"--limit\", str(limit))\n return _data_list(r)\n\n\n# ── Helpers ───────────────────────────────────────────────────────────────────\n\ndef _tags(info: dict) -> list:\n return info.get(\"tokenTags\") or []\n\ndef _has_tag(info: dict, prefix: str) -> bool:\n return any(t.startswith(prefix) for t in _tags(info))\n\ndef _pct(info: dict, field: str) -> float:\n v = info.get(field, \"\") or \"\"\n try:\n return float(v)\n except (ValueError, TypeError):\n return -1.0\n\ndef _int(info: dict, field: str) -> int:\n v = info.get(field, 0) or 0\n try:\n return int(v)\n except (ValueError, TypeError):\n return 0\n\ndef _trade_sol(trade: dict) -> float:\n \"\"\"Extract SOL amount from a trade's changedTokenInfo.\"\"\"\n for t in trade.get(\"changedTokenInfo\", []):\n if t.get(\"tokenSymbol\") in (\"SOL\", \"wSOL\"):\n try:\n return float(t.get(\"amount\", 0))\n except (ValueError, TypeError):\n pass\n try:\n return float(trade.get(\"volume\", 0))\n except (ValueError, TypeError):\n return 0.0\n\n\n# ── Check 1: Selling velocity (dev + insider sells) ───────────────────────────\n\ndef _selling_velocity(addr: str) -> tuple:\n \"\"\"\n Returns (sol_per_min, reason_str).\n Checks dev (tag=2) + insider (tag=6) sells over last 50 trades.\n Detects soft rugs: steady sell pressure from privileged wallets.\n \"\"\"\n sells_by_wallet = defaultdict(list) # wallet -> [(timestamp_ms, sol)]\n\n for tag in (2, 6): # dev + insider\n for trade in _tagged_trades(addr, tag, limit=50):\n if trade.get(\"type\") != \"sell\":\n continue\n ts = int(trade.get(\"time\", 0))\n sol = _trade_sol(trade)\n if sol > 0 and ts > 0:\n sells_by_wallet[trade.get(\"userAddress\", \"?\")].append((ts, sol))\n\n if not sells_by_wallet:\n return 0.0, \"\"\n\n now_ms = int(time.time() * 1000)\n window = 5 * 60 * 1000 # 5-minute window\n total_sol = 0.0\n wallets = []\n\n for wallet, events in sells_by_wallet.items():\n recent = [(ts, sol) for ts, sol in events if now_ms - ts \u003c= window]\n if recent:\n sol_sum = sum(s for _, s in recent)\n total_sol += sol_sum\n wallets.append(f\"{wallet[:8]}…({sol_sum:.2f}SOL)\")\n\n if total_sol == 0:\n return 0.0, \"\"\n\n elapsed_min = window / 60000\n sol_pm = total_sol / elapsed_min\n detail = f\"{sol_pm:.2f} SOL/min — {', '.join(wallets)}\"\n return sol_pm, detail\n\n\n# ── Check 2: LP provider concentration ────────────────────────────────────────\n\ndef _lp_provider_check(addr: str, lp_burned: float) -> tuple:\n \"\"\"\n Returns (is_risky, reason_str).\n Single LP provider + LP not burned = high rug risk.\n \"\"\"\n pools = _lp_pools(addr)\n if not pools:\n return False, \"\"\n\n # Count unique creators across pools with meaningful liquidity\n creators = set()\n for pool in pools:\n liq = 0.0\n try:\n liq = float(pool.get(\"liquidityUsd\", 0))\n except (ValueError, TypeError):\n pass\n if liq > 100: # ignore dust pools\n creator = pool.get(\"poolCreator\", \"\")\n if creator:\n creators.add(creator)\n\n if len(creators) == 1 and lp_burned \u003c 80:\n creator = next(iter(creators))\n total_liq = sum(\n float(p.get(\"liquidityUsd\", 0) or 0) for p in pools\n )\n return (\n True,\n f\"SINGLE_LP_PROVIDER — {creator[:12]}… controls \"\n f\"${total_liq:,.0f} liquidity, LP only {lp_burned:.0f}% burned\"\n )\n\n return False, \"\"\n\n\n# ── Check 3: Wash trading ─────────────────────────────────────────────────────\n\ndef _wash_trading_check(addr: str) -> tuple:\n \"\"\"\n Returns (is_wash, reason_str).\n Detects wash trading via two signals:\n 1. Round-trip wallets — wallets that both buy AND sell within a 5-min window.\n Flags if ≥50% of active wallets are round-tripping (strong signal alone),\n or ≥30% round-tripping AND top-3 wallets drive >40% of trades (combined signal).\n 2. Wallet concentration — high trade share from a tiny set of wallets amplifies\n the round-trip signal, indicating coordinated volume inflation.\n Uses 200 recent trades for statistical reliability (~0.2s, one API call).\n \"\"\"\n trades = _recent_trades(addr, limit=200)\n if len(trades) \u003c 15:\n return False, \"\"\n\n wallet_buys = defaultdict(list) # wallet -> [timestamp_ms, ...]\n wallet_sells = defaultdict(list)\n wallet_count = defaultdict(int)\n\n for t in trades:\n w = t.get(\"userAddress\", \"\")\n ts = int(t.get(\"time\", 0))\n if not w or ts == 0:\n continue\n wallet_count[w] += 1\n if t.get(\"type\") == \"buy\":\n wallet_buys[w].append(ts)\n else:\n wallet_sells[w].append(ts)\n\n active_wallets = set(wallet_buys) | set(wallet_sells)\n if not active_wallets:\n return False, \"\"\n\n # Round-trip: any buy followed by a sell from the same wallet within 5 min\n window_ms = 5 * 60 * 1000\n rt_wallets = 0\n for w in active_wallets:\n buys = sorted(wallet_buys[w])\n sells = sorted(wallet_sells[w])\n if not buys or not sells:\n continue\n if any(any(s > b and s - b \u003c= window_ms for s in sells) for b in buys):\n rt_wallets += 1\n\n total_wallets = len(active_wallets)\n rt_ratio = rt_wallets / total_wallets\n\n # Wallet concentration: top-3 wallets share of all trades\n top3 = sum(c for _, c in sorted(wallet_count.items(), key=lambda x: -x[1])[:3])\n concentration = top3 / len(trades)\n\n if rt_ratio >= _WASH_ROUNDTRIP_RATIO:\n return (\n True,\n f\"WASH_TRADING — {rt_wallets}/{total_wallets} wallets round-tripped \"\n f\"({rt_ratio*100:.0f}%) within 5-min windows\"\n )\n if rt_ratio >= _WASH_ROUNDTRIP_SOFT and concentration >= _WASH_CONC_THRESHOLD:\n return (\n True,\n f\"WASH_TRADING — {rt_wallets}/{total_wallets} wallets round-tripped \"\n f\"({rt_ratio*100:.0f}%) + top-3 wallets drive {concentration*100:.0f}% of volume\"\n )\n\n return False, \"\"\n\n\n# ── Check 4: Holder sell transfers ────────────────────────────────────────────\n\ndef _holder_sell_check(addr: str) -> tuple:\n \"\"\"\n Returns (is_selling, reason_str).\n Detects coordinated sells from tagged wallets (dev, whale, insider, sniper).\n Pre-trade: catch early distribution before price drops.\n \"\"\"\n tag_names = {2: \"Dev\", 4: \"Whale\", 6: \"Insider\", 7: \"Sniper\"}\n now_ms = int(time.time() * 1000)\n window = 10 * 60 * 1000 # 10-minute window\n findings = []\n\n for tag, label in tag_names.items():\n trades = _tagged_trades(addr, tag, limit=30)\n recent_sells = [\n t for t in trades\n if t.get(\"type\") == \"sell\"\n and now_ms - int(t.get(\"time\", 0)) \u003c= window\n ]\n if len(recent_sells) >= 2:\n sol = sum(_trade_sol(t) for t in recent_sells)\n findings.append(f\"{label}×{len(recent_sells)}({sol:.2f}SOL)\")\n\n if findings:\n return True, \"HOLDER_SELLING — \" + \", \".join(findings) + \" in last 10min\"\n return False, \"\"\n\n\n# ── Core risk check ───────────────────────────────────────────────────────────\n\ndef pre_trade_checks(addr: str, sym: str, quick: bool = False) -> dict:\n \"\"\"\n Run pre-trade risk assessment.\n\n quick=True — fast mode (4 API calls, ~0.8s). Use for pre-trade gates.\n Runs: security scan + advanced-info + price-info + wash trading.\n Skips: selling velocity, LP provider, holder sells.\n Those slow checks are better handled by post_trade_flags() monitoring.\n\n quick=False — full mode (11 API calls, ~22–33s). Use for manual analysis only.\n\n Returns:\n {\n \"pass\": bool,\n \"grade\": int, # 4=block, 3=warn, 2=caution, 0=pass\n \"level\": int, # alias for grade (backward compatibility)\n \"reasons\": [str], # grade 4 + 3 failures\n \"cautions\": [str], # grade 2 flags\n \"raw\": {\n \"scan\": dict,\n \"info\": dict,\n \"liquidity_usd\": float # snapshot for post-trade monitoring\n }\n }\n \"\"\"\n scan = _security_scan(addr)\n info = _advanced_info(addr)\n liq_usd = _liquidity_usd(addr)\n lp_burned = _pct(info, \"lpBurnedPercent\")\n\n reasons = []\n cautions = []\n level = 0\n\n # ── Grade 4 — Hard Block ─────────────────────────────────────────────────\n\n if scan.get(\"isRiskToken\"):\n reasons.append(\"G4: HONEYPOT — isRiskToken flagged by OKX\")\n level = 4\n\n buy_tax = _pct(scan, \"buyTaxes\")\n if buy_tax > 50:\n reasons.append(f\"G4: BUY_TAX {buy_tax:.0f}% > 50%\")\n level = 4\n\n sell_tax = _pct(scan, \"sellTaxes\")\n if sell_tax > 50:\n reasons.append(f\"G4: SELL_TAX {sell_tax:.0f}% > 50%\")\n level = 4\n\n if _has_tag(info, \"devRemoveLiq\"):\n tag = next(t for t in _tags(info) if t.startswith(\"devRemoveLiq\"))\n reasons.append(f\"G4: DEV_REMOVING_LIQUIDITY — {tag}\")\n level = 4\n\n if _has_tag(info, \"lowLiquidity\"):\n reasons.append(\"G4: LOW_LIQUIDITY — total liquidity \u003c $5K\")\n level = 4\n\n risk_lvl = _int(info, \"riskControlLevel\")\n if risk_lvl >= 4:\n reasons.append(f\"G4: OKX_RISK_LEVEL {risk_lvl} >= 4\")\n level = 4\n\n # Selling velocity — active dump (slow check, full mode only)\n vel_sol_pm, vel_detail = (0.0, \"\") if quick else _selling_velocity(addr)\n if vel_sol_pm >= _SELL_VEL_BLOCK_SOL_PM:\n reasons.append(f\"G4: ACTIVE_DUMP — {vel_detail}\")\n level = 4\n\n # ── Grade 3 — Strong Warning ─────────────────────────────────────────────\n\n rug_count = _int(info, \"devRugPullTokenCount\")\n dev_created = _int(info, \"devCreateTokenCount\")\n\n if dev_created > 0:\n rug_rate = rug_count / dev_created\n if rug_rate >= 0.20 and rug_count >= 3:\n reasons.append(\n f\"G3: SERIAL_RUGGER — {rug_count}/{dev_created} tokens rugged \"\n f\"({rug_rate*100:.0f}%)\"\n )\n level = max(level, 3)\n elif rug_rate >= 0.05 and rug_count >= 2:\n cautions.append(\n f\"G2: RUG_HISTORY — {rug_count}/{dev_created} tokens rugged \"\n f\"({rug_rate*100:.0f}%)\"\n )\n elif rug_count >= 5:\n # devCreateTokenCount unavailable — fall back to flat count\n reasons.append(f\"G3: SERIAL_RUGGER — {rug_count} confirmed rug pulls (no total count)\")\n level = max(level, 3)\n\n if 0 \u003c= lp_burned \u003c 80:\n reasons.append(f\"G3: LP_NOT_BURNED — {lp_burned:.1f}% burned (\u003c 80%)\")\n level = max(level, 3)\n\n if _has_tag(info, \"volumeChangeRateVolumePlunge\"):\n reasons.append(\"G3: VOLUME_PLUNGE — trading activity collapsing\")\n level = max(level, 3)\n\n\n sniper_pct = _pct(info, \"sniperHoldingPercent\")\n if sniper_pct > 15:\n reasons.append(f\"G3: SNIPERS_HOLDING {sniper_pct:.1f}% > 15%\")\n level = max(level, 3)\n\n suspicious_pct = _pct(info, \"suspiciousHoldingPercent\")\n if suspicious_pct > 10:\n reasons.append(f\"G3: SUSPICIOUS_WALLETS {suspicious_pct:.1f}% > 10%\")\n level = max(level, 3)\n\n # Wash trading — round-trip + concentration (fast: 1 extra API call, ~0.2s)\n is_wash, wash_reason = _wash_trading_check(addr)\n if is_wash:\n reasons.append(f\"G3: {wash_reason}\")\n level = max(level, 3)\n\n # ── Slow checks — full mode only (post-trade covers these in real-time) ──\n\n if not quick:\n # Selling velocity — soft rug (steady bleed)\n if 0 \u003c vel_sol_pm \u003c _SELL_VEL_BLOCK_SOL_PM and vel_sol_pm >= _SELL_VEL_WARN_SOL_PM:\n reasons.append(f\"G3: SOFT_RUG_VELOCITY — {vel_detail}\")\n level = max(level, 3)\n\n # LP provider concentration\n lp_risky, lp_reason = _lp_provider_check(addr, lp_burned)\n if lp_risky:\n reasons.append(f\"G3: {lp_reason}\")\n level = max(level, 3)\n\n # Holder selling — coordinated exits from tagged wallets\n is_selling, sell_reason = _holder_sell_check(addr)\n if is_selling:\n reasons.append(f\"G3: {sell_reason}\")\n level = max(level, 3)\n\n # ── Grade 2 — Caution ────────────────────────────────────────────────────\n\n top10 = _pct(info, \"top10HoldPercent\")\n if top10 > 30:\n cautions.append(f\"G2: SUPPLY_CONCENTRATED — top 10 hold {top10:.1f}%\")\n level = max(level, 2)\n\n bundle_pct = _pct(info, \"bundleHoldingPercent\")\n if bundle_pct > 5:\n cautions.append(f\"G2: BUNDLES_STILL_IN {bundle_pct:.1f}% > 5%\")\n level = max(level, 2)\n\n is_cto = _has_tag(info, \"dexScreenerTokenCommunityTakeOver\")\n if _has_tag(info, \"devHoldingStatusSellAll\") and not is_cto:\n cautions.append(\"G2: DEV_SOLD_ALL — dev exited (not a CTO)\")\n level = max(level, 2)\n\n if _has_tag(info, \"dsPaid\"):\n cautions.append(\"G2: PAID_LISTING — dexscreener listing was paid\")\n level = max(level, 2)\n\n if not _has_tag(info, \"smartMoneyBuy\"):\n cautions.append(\"G2: NO_SMART_MONEY — no smart money wallet detected\")\n level = max(level, 2)\n\n # ── Result ────────────────────────────────────────────────────────────────\n\n passed = level \u003c 3\n\n return {\n \"pass\": passed,\n \"grade\": level,\n \"level\": level, # backward compat alias\n \"reasons\": reasons,\n \"cautions\": cautions,\n \"raw\": {\n \"scan\": scan,\n \"info\": info,\n \"liquidity_usd\": liq_usd,\n },\n }\n\n\n# ── Post-trade monitoring ─────────────────────────────────────────────────────\n\ndef post_trade_flags(addr: str, sym: str,\n entry_liquidity_usd: float = 0.0,\n entry_top10: float = 0.0,\n entry_sniper_pct: float = 0.0) -> list:\n \"\"\"\n Call periodically during position monitoring.\n\n Returns list of action strings:\n \"EXIT_NOW: ...\" — immediate exit required\n \"EXIT_NEXT_TP: ...\" — exit at next TP or trailing stop\n \"REDUCE_POSITION: ...\" — cut size\n \"ALERT: ...\" — informational\n \"\"\"\n info = _advanced_info(addr)\n liq_usd = _liquidity_usd(addr)\n flags = []\n\n # Dev removing liquidity — EXIT NOW\n if _has_tag(info, \"devRemoveLiq\"):\n tag = next((t for t in _tags(info) if t.startswith(\"devRemoveLiq\")), \"devRemoveLiq\")\n flags.append(f\"EXIT_NOW: DEV_REMOVING_LIQUIDITY — {tag}\")\n\n # Liquidity drain > 30% since entry — EXIT NOW\n if entry_liquidity_usd > 0 and liq_usd > 0:\n drain_pct = (entry_liquidity_usd - liq_usd) / entry_liquidity_usd\n if drain_pct >= _LP_DRAIN_EXIT_PCT:\n flags.append(\n f\"EXIT_NOW: LIQUIDITY_DRAIN {drain_pct*100:.0f}% — \"\n f\"${entry_liquidity_usd:,.0f} → ${liq_usd:,.0f}\"\n )\n\n # Active dump from dev/insiders — EXIT NOW\n vel_sol_pm, vel_detail = _selling_velocity(addr)\n if vel_sol_pm >= _SELL_VEL_BLOCK_SOL_PM:\n flags.append(f\"EXIT_NOW: ACTIVE_DUMP — {vel_detail}\")\n\n # Holder selling — coordinated exits\n is_selling, sell_reason = _holder_sell_check(addr)\n if is_selling:\n flags.append(f\"EXIT_NOW: {sell_reason}\")\n\n # Volume collapsing — exit at next TP\n if _has_tag(info, \"volumeChangeRateVolumePlunge\"):\n flags.append(\"EXIT_NEXT_TP: VOLUME_PLUNGE — activity collapsing\")\n\n # Soft rug velocity\n if 0 \u003c vel_sol_pm \u003c _SELL_VEL_BLOCK_SOL_PM and vel_sol_pm >= _SELL_VEL_WARN_SOL_PM:\n flags.append(f\"EXIT_NEXT_TP: SOFT_RUG_VELOCITY — {vel_detail}\")\n\n # Sniper spike\n sniper_pct = _pct(info, \"sniperHoldingPercent\")\n if sniper_pct > entry_sniper_pct + 5:\n flags.append(\n f\"REDUCE_POSITION: SNIPER_SPIKE {sniper_pct:.1f}% \"\n f\"(was {entry_sniper_pct:.1f}% at entry)\"\n )\n\n # Top 10 concentration increase\n top10 = _pct(info, \"top10HoldPercent\")\n if top10 > 40 and top10 > entry_top10 + 5:\n flags.append(\n f\"ALERT: TOP10_CONCENTRATION {top10:.1f}% \"\n f\"(was {entry_top10:.1f}% at entry)\"\n )\n\n return flags\n\n\n# ── CLI usage ─────────────────────────────────────────────────────────────────\n\nif __name__ == \"__main__\":\n import sys\n addr = sys.argv[1] if len(sys.argv) > 1 else \"\"\n sym = sys.argv[2] if len(sys.argv) > 2 else addr[:8]\n if not addr:\n print(\"Usage: python3 risk_check.py \u003ctoken_address> [symbol]\")\n sys.exit(1)\n\n print(f\"\\n{'='*55}\")\n print(f\" Risk Check — {sym}\")\n print(f\" {addr}\")\n print(f\"{'='*55}\")\n\n r = pre_trade_checks(addr, sym)\n\n level_label = {0: \"✅ PASS\", 2: \"⚠️ CAUTION\", 3: \"🚨 WARN\", 4: \"❌ BLOCK\"}\n print(f\"\\n Result: {level_label.get(r['level'], str(r['level']))}\")\n print(f\" Liquidity: ${r['raw']['liquidity_usd']:,.0f}\")\n\n if r[\"reasons\"]:\n print(\"\\n Blocks / Warnings:\")\n for reason in r[\"reasons\"]:\n print(f\" • {reason}\")\n\n if r[\"cautions\"]:\n print(\"\\n Cautions:\")\n for c in r[\"cautions\"]:\n print(f\" • {c}\")\n\n print()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":30541,"content_sha256":"98345d23b0bf620786e0672c962efe8110b8c34eb663482ec38df4148ab3a31a"},{"filename":"scripts/scan_live.py","content":"\"\"\"\nMeme Trench Scanner v1.0 — Agentic Wallet TEE Signing\nMemepump Safety Scan + 5m/15m Precision Signals + Cost-Aware TP\nDashboard: http://localhost:3241\n\nRun: python3 scan_live.py\nRequires: onchainos CLI >= 2.1.0 (requires onchainos wallet login)\nNo pip install of any third-party packages needed\n\"\"\"\n\nimport os, sys, time, json, subprocess, shutil, threading, random, socket, signal\nfrom collections import defaultdict\nfrom pathlib import Path\nfrom http.server import HTTPServer, BaseHTTPRequestHandler\n\n# ── Load Config ────────────────────────────────────────────────────────\nPROJECT_DIR = Path(__file__).parent\nsys.path.insert(0, str(PROJECT_DIR))\nimport config as C\nfrom risk_check import pre_trade_checks, post_trade_flags\n\n# ── onchainos CLI Client ───────────────────────────────────────────────\n\n_ONCHAINOS = shutil.which(\"onchainos\") or os.path.expanduser(\"~/.local/bin/onchainos\")\n_CHAIN = \"solana\"\n\n\ndef _check_onchainos():\n \"\"\"Check if onchainos CLI is available at startup\"\"\"\n if not os.path.isfile(_ONCHAINOS):\n print(\"=\" * 60)\n print(\" FATAL: onchainos CLI not found\")\n print(f\" Searched path: {_ONCHAINOS}\")\n print()\n print(\" Please install onchainos CLI first:\")\n print(\" curl -fsSL https://onchainos.com/install.sh | bash\")\n print(\" Or ensure onchainos is on PATH\")\n print(\"=\" * 60)\n sys.exit(1)\n try:\n r = subprocess.run([_ONCHAINOS, \"--version\"], capture_output=True, text=True, timeout=10)\n ver = r.stdout.strip()\n print(f\" onchainos CLI: {ver}\")\n except Exception as e:\n print(f\" WARNING: onchainos --version failed: {e}\")\n\n\ndef _onchainos(*args, timeout: int = 30) -> dict:\n \"\"\"Call onchainos CLI and parse JSON output.\"\"\"\n cmd = [_ONCHAINOS] + list(args)\n try:\n result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)\n except subprocess.TimeoutExpired:\n raise RuntimeError(f\"onchainos timeout ({timeout}s): {' '.join(args[:3])}\")\n if result.returncode != 0:\n err = result.stderr.strip() or result.stdout.strip()\n raise RuntimeError(f\"onchainos error (rc={result.returncode}): {err[:200]}\")\n try:\n return json.loads(result.stdout)\n except json.JSONDecodeError:\n raise RuntimeError(f\"onchainos invalid JSON: {result.stdout[:200]}\")\n\n\ndef _cli_data(resp: dict):\n \"\"\"Extract .data from onchainos JSON response.\"\"\"\n return resp.get(\"data\", [])\n\n\ndef _safe_float(v, default=0.0):\n \"\"\"Safe float conversion — handles None, empty string, non-numeric.\"\"\"\n if v is None or v == \"\":\n return default\n try:\n return float(v)\n except (ValueError, TypeError):\n return default\n\n\ndef _safe_int(v, default=0):\n \"\"\"Safe int conversion — handles None, empty string, non-numeric.\"\"\"\n if v is None or v == \"\":\n return default\n try:\n return int(float(v))\n except (ValueError, TypeError):\n return default\n\n\n# ── Data APIs ────────────────────────────────────────────────────────────\n\ndef token_ranking(sort_by: int) -> list:\n r = _onchainos(\"token\", \"trending\", \"--chain\", _CHAIN,\n \"--sort-by\", str(sort_by), \"--time-frame\", \"1\")\n return _cli_data(r)\n\n\ndef memepump_token_list(\n stage: str = \"MIGRATED\",\n max_mc: float = C.MC_CAP,\n min_liq: float = C.LIQ_MIN,\n min_holders: int = C.MIN_HOLDERS,\n max_bundlers_pct: float = C.TF_MAX_BUNDLERS,\n max_dev_hold_pct: float = C.DEV_HOLD_DEEP_MAX * 100,\n max_top10_pct: float = C.TOP10_HOLD_MAX,\n max_insiders_pct: float = C.INSIDERS_MAX,\n max_snipers_pct: float = C.SNIPERS_MAX,\n max_fresh_pct: float = C.FRESH_WALLET_MAX,\n limit: int = 50,\n protocol_ids: list = None,\n) -> list:\n args = [\n \"memepump\", \"tokens\",\n \"--chain\", _CHAIN,\n \"--stage\", stage,\n \"--max-market-cap\", str(int(max_mc)),\n \"--min-holders\", str(min_holders),\n \"--max-bundlers-percent\", str(max_bundlers_pct),\n \"--max-dev-holdings-percent\", str(max_dev_hold_pct),\n \"--max-top10-holdings-percent\", str(max_top10_pct),\n \"--max-insiders-percent\", str(max_insiders_pct),\n \"--max-snipers-percent\", str(max_snipers_pct),\n \"--max-fresh-wallets-percent\", str(max_fresh_pct),\n ]\n if protocol_ids:\n args += [\"--protocol-id-list\", \",\".join(protocol_ids)]\n r = _onchainos(*args)\n return _cli_data(r)\n\n\ndef memepump_token_details(token_address: str, wallet: str = \"\") -> dict:\n args = [\"memepump\", \"token-details\", \"--chain\", _CHAIN, \"--address\", token_address]\n if wallet:\n args += [\"--wallet-address\", wallet]\n r = _onchainos(*args)\n data = _cli_data(r)\n return data[0] if isinstance(data, list) and data else (data if isinstance(data, dict) else {})\n\n\n_logo_cache: dict = {}\n\ndef fetch_token_logo(addr: str) -> str:\n if addr in _logo_cache:\n return _logo_cache[addr] or \"\"\n try:\n r = _onchainos(\"token\", \"info\", \"--chain\", _CHAIN, \"--address\", addr, timeout=10)\n data = _cli_data(r)\n item = data[0] if isinstance(data, list) and data else (data if isinstance(data, dict) else {})\n url = item.get(\"logoUrl\", item.get(\"tokenLogoUrl\", \"\"))\n _logo_cache[addr] = url or None\n return url\n except Exception:\n _logo_cache[addr] = None\n return \"\"\n\n\ndef memepump_aped_wallet(token_address: str) -> list:\n r = _onchainos(\"memepump\", \"aped-wallet\", \"--chain\", _CHAIN, \"--address\", token_address)\n return _cli_data(r)\n\n\ndef memepump_similar_token(token_address: str) -> list:\n r = _onchainos(\"memepump\", \"similar-tokens\", \"--chain\", _CHAIN, \"--address\", token_address)\n return _cli_data(r)\n\n\ndef price_info(token_address: str) -> dict:\n r = _onchainos(\"token\", \"price-info\", \"--chain\", _CHAIN, \"--address\", token_address)\n data = _cli_data(r)\n return data[0] if isinstance(data, list) and data else (data if isinstance(data, dict) else {})\n\n\ndef candlesticks(token_address: str, bar: str = \"1m\", limit: int = 20) -> list:\n r = _onchainos(\"market\", \"kline\", \"--chain\", _CHAIN,\n \"--address\", token_address, \"--bar\", bar, \"--limit\", str(limit))\n return _cli_data(r)\n\n\ndef trades(token_address: str, limit: int = 200) -> list:\n r = _onchainos(\"token\", \"trades\", \"--chain\", _CHAIN,\n \"--address\", token_address, \"--limit\", str(min(limit, 500)))\n return _cli_data(r)\n\n\n# ── Safety APIs ──────────────────────────────────────────────────────────\n\ndef token_dev_info(token_address: str) -> dict:\n r = _onchainos(\"memepump\", \"token-dev-info\", \"--chain\", _CHAIN, \"--address\", token_address)\n data = _cli_data(r)\n return data[0] if isinstance(data, list) and data else (data if isinstance(data, dict) else {})\n\n\ndef token_bundle_info(token_address: str) -> dict:\n r = _onchainos(\"memepump\", \"token-bundle-info\", \"--chain\", _CHAIN, \"--address\", token_address)\n data = _cli_data(r)\n return data[0] if isinstance(data, list) and data else (data if isinstance(data, dict) else {})\n\n\ndef token_lp_info(token_address: str) -> dict:\n return memepump_token_details(token_address)\n\n\n# ── Execution APIs ───────────────────────────────────────────────────────\n\ndef get_quote(from_addr: str, to_addr: str, amount: str, slippage: int) -> dict:\n r = _onchainos(\"swap\", \"quote\", \"--chain\", _CHAIN,\n \"--from\", from_addr, \"--to\", to_addr, \"--amount\", amount)\n data = _cli_data(r)\n return data[0] if isinstance(data, list) and data else (data if isinstance(data, dict) else {})\n\n\ndef swap_instruction(from_addr: str, to_addr: str, amount: str,\n slippage: int, user_wallet: str) -> dict:\n # [H1] onchainos swap --slippage expects integer percent (e.g. \"8\" for 8%)\n r = _onchainos(\"swap\", \"swap\", \"--chain\", _CHAIN,\n \"--from\", from_addr, \"--to\", to_addr,\n \"--amount\", amount,\n \"--slippage\", str(slippage),\n \"--wallet\", user_wallet,\n timeout=30)\n data = _cli_data(r)\n return data[0] if isinstance(data, list) and data else (data if isinstance(data, dict) else {})\n\n\n# ── Agentic Wallet (TEE Signing) ───────────────────────────────────────\n\ndef sign_and_broadcast(unsigned_tx: str, to_addr: str) -> str:\n \"\"\"Sign via TEE + broadcast atomically. Returns txHash.\"\"\"\n r = _onchainos(\"wallet\", \"contract-call\",\n \"--chain\", \"501\",\n \"--to\", to_addr,\n \"--unsigned-tx\", unsigned_tx,\n timeout=60)\n data = _cli_data(r)\n if isinstance(data, list) and data:\n data = data[0]\n return data.get(\"txHash\", \"\") if isinstance(data, dict) else \"\"\n\n\ndef tx_status(tx_hash: str) -> str:\n \"\"\"Poll wallet history for tx confirmation. Returns SUCCESS/FAILED/TIMEOUT.\"\"\"\n for _ in range(20):\n time.sleep(3)\n try:\n r = _onchainos(\"wallet\", \"history\",\n \"--tx-hash\", tx_hash,\n \"--chain-index\", \"501\")\n data = _cli_data(r)\n item = data[0] if isinstance(data, list) and data else (data if isinstance(data, dict) else {})\n status = str(item.get(\"txStatus\", \"0\"))\n # Compatible with two encoding schemes: gateway (2=SUCCESS,3=FAILED) and wallet (1=SUCCESS,2=FAILED)\n if status in (\"1\", \"2\", \"SUCCESS\"):\n return \"SUCCESS\"\n if status in (\"3\", \"FAILED\"):\n return \"FAILED\"\n except Exception:\n pass\n return \"TIMEOUT\"\n\n\ndef portfolio_token_pnl(token_address: str) -> dict:\n try:\n r = _onchainos(\"market\", \"portfolio-token-pnl\",\n \"--chain\", _CHAIN,\n \"--address\", WALLET_ADDRESS,\n \"--token\", token_address)\n data = _cli_data(r)\n return data[0] if isinstance(data, list) and data else (data if isinstance(data, dict) else {})\n except Exception:\n return {}\n\n\ndef _wallet_preflight() -> str:\n \"\"\"Check Agentic Wallet login and return Solana address. Exits on failure.\"\"\"\n if C.PAPER_TRADE:\n print(\" PAPER MODE — skipping wallet login check\")\n return \"PAPER_MODE_NO_WALLET\"\n\n # Check wallet status\n try:\n r = _onchainos(\"wallet\", \"status\")\n data = _cli_data(r)\n except Exception as e:\n print(\"=\" * 60)\n print(\" FATAL: Unable to check Agentic Wallet status\")\n print(f\" Error: {e}\")\n print()\n print(\" Please ensure:\")\n print(\" 1. onchainos CLI is installed: onchainos --version\")\n print(\" 2. Wallet is logged in: onchainos wallet login \u003cemail>\")\n print(\" 3. Verify status: onchainos wallet status\")\n print(\"=\" * 60)\n sys.exit(1)\n\n if not data.get(\"loggedIn\"):\n print(\"=\" * 60)\n print(\" FATAL: Agentic Wallet not logged in\")\n print()\n print(\" Please log in first:\")\n print(\" onchainos wallet login \u003cyour-email>\")\n print(\" Then verify:\")\n print(\" onchainos wallet status → loggedIn: true\")\n print(\"=\" * 60)\n sys.exit(1)\n\n # Get Solana address\n try:\n r2 = _onchainos(\"wallet\", \"addresses\", \"--chain\", \"501\")\n data2 = _cli_data(r2)\n except Exception as e:\n print(f\" FATAL: Unable to get wallet address: {e}\")\n sys.exit(1)\n\n addr = \"\"\n if isinstance(data2, dict):\n sol_list = data2.get(\"solana\", [])\n if sol_list and isinstance(sol_list[0], dict):\n addr = sol_list[0].get(\"address\", \"\")\n if not addr:\n addr = data2.get(\"solAddress\", data2.get(\"address\", \"\"))\n elif isinstance(data2, list) and data2:\n item = data2[0] if isinstance(data2[0], dict) else {}\n addr = item.get(\"address\", \"\")\n\n if not addr:\n print(\" FATAL: Unable to parse Solana address\")\n print(\" Please check: onchainos wallet addresses --chain 501\")\n sys.exit(1)\n\n return addr\n\n\n# ── Startup Checks ─────────────────────────────────────────────────────\n_check_onchainos()\nWALLET_ADDRESS = _wallet_preflight()\n\n\n# ── Global State ───────────────────────────────────────────────────────\n\nstate_lock = threading.Lock()\npos_lock = threading.Lock()\n_selling = set()\n_pending_buys = 0\n_buy_slot_reserved = threading.local()\n\n_last_wallet_audit = 0\n_price_cache = {} # addr → price_info dict, refreshed each monitor cycle\n_WALLET_AUDIT_SEC = 60\nrecently_closed = {}\nwatchlist = {}\n\npositions = {}\nstate = {\n \"cycle\": 0, \"hot\": False, \"status\": \"Starting...\",\n \"feed\": [], \"feed_seq\": 0,\n \"signals\": [],\n \"positions\": {},\n \"trades\": [],\n \"stats\": {\n \"cycles\": 0, \"buys\": 0, \"sells\": 0, \"wins\": 0, \"losses\": 0,\n \"pos_wins\": 0, \"pos_losses\": 0,\n \"net_sol\": 0.0, \"session_start\": time.strftime(\"%H:%M:%S\"),\n },\n \"session\": {\n \"paused_until\": None,\n \"consecutive_losses\": 0,\n \"daily_loss_sol\": 0.0,\n \"stopped\": False,\n \"cycle_sig_a_outcomes\": [],\n }\n}\nMAX_FEED = 600\n\nPOSITIONS_FILE = str(PROJECT_DIR / \"scan_positions.json\")\nTRADES_FILE = str(PROJECT_DIR / \"scan_trades.json\")\nRECENTLY_CLOSED_FILE = str(PROJECT_DIR / \"scan_recently_closed.json\")\n\n\ndef push_feed(row: dict):\n with state_lock:\n state[\"feed_seq\"] += 1\n row[\"seq\"] = state[\"feed_seq\"]\n state[\"feed\"].insert(0, row)\n if len(state[\"feed\"]) > MAX_FEED:\n state[\"feed\"] = state[\"feed\"][:MAX_FEED]\n\n\ndef sync_positions():\n with pos_lock: snap = dict(positions)\n with state_lock: state[\"positions\"] = snap\n\n\ndef _save_positions_unlocked():\n \"\"\"Write positions to disk. Caller MUST hold pos_lock.\"\"\"\n snap = dict(positions)\n try:\n with open(POSITIONS_FILE + \".tmp\", \"w\") as f:\n json.dump(snap, f, ensure_ascii=False)\n os.replace(POSITIONS_FILE + \".tmp\", POSITIONS_FILE)\n except Exception as e:\n print(f\" ⚠️ save_positions: {e}\")\n\n\ndef save_positions():\n with pos_lock:\n _save_positions_unlocked()\n\n\ndef save_trades():\n with state_lock:\n snap = list(state[\"trades\"])\n try:\n with open(TRADES_FILE + \".tmp\", \"w\") as f:\n json.dump(snap, f, ensure_ascii=False)\n os.replace(TRADES_FILE + \".tmp\", TRADES_FILE)\n except Exception as e:\n print(f\" ⚠️ save_trades: {e}\")\n\n\ndef save_recently_closed():\n try:\n with pos_lock:\n snap = dict(recently_closed)\n with open(RECENTLY_CLOSED_FILE + \".tmp\", \"w\") as f:\n json.dump(snap, f, ensure_ascii=False)\n os.replace(RECENTLY_CLOSED_FILE + \".tmp\", RECENTLY_CLOSED_FILE)\n except Exception as e:\n print(f\" ⚠️ save_recently_closed: {e}\")\n\n\n# ── Balance helpers ──────────────────────────────────────────────────────\n\ndef query_all_wallet_tokens():\n \"\"\"Return {mint: raw_amount} for all tokens. None on CLI error.\"\"\"\n if C.PAPER_TRADE:\n return {}\n try:\n r = _onchainos(\"portfolio\", \"all-balances\",\n \"--address\", WALLET_ADDRESS,\n \"--chains\", \"solana\",\n \"--filter\", \"1\", timeout=20)\n data = _cli_data(r)\n except Exception:\n return None\n\n result = {}\n items = data if isinstance(data, list) else [data] if isinstance(data, dict) else []\n for item in items:\n token_assets = item.get(\"tokenAssets\", []) if isinstance(item, dict) else []\n for t in token_assets:\n mint = t.get(\"tokenContractAddress\", t.get(\"tokenAddress\", \"\"))\n # [M2] Skip SOL (empty tokenContractAddress) and ignored mints\n if not mint or mint in C._IGNORE_MINTS:\n continue\n # [C4] Prefer rawBalance (accurate on-chain value), fallback rawAmount\n raw = t.get(\"rawBalance\") or t.get(\"rawAmount\") or \"\"\n if raw and raw not in (None, \"\", \"0\"):\n amt = int(raw)\n else:\n bal = _safe_float(t.get(\"balance\", t.get(\"holdingAmount\", 0)))\n decimals = _safe_int(t.get(\"decimals\", 9), default=9)\n amt = int(bal * (10 ** decimals))\n if amt > 0:\n result[mint] = result.get(mint, 0) + amt\n return result\n\n\ndef query_single_token_balance(mint: str) -> int:\n \"\"\">0 = balance, 0 = confirmed empty, -1 = CLI error.\"\"\"\n if C.PAPER_TRADE:\n return 0\n try:\n r = _onchainos(\"portfolio\", \"token-balances\",\n \"--address\", WALLET_ADDRESS,\n \"--tokens\", f\"501:{mint}\", timeout=15)\n data = _cli_data(r)\n except Exception:\n return -1\n\n items = data if isinstance(data, list) else [data] if isinstance(data, dict) else []\n total = 0\n for item in items:\n token_assets = item.get(\"tokenAssets\", []) if isinstance(item, dict) else []\n for t in token_assets:\n addr = t.get(\"tokenContractAddress\", t.get(\"tokenAddress\", \"\"))\n if addr != mint:\n continue\n # [C5] Prefer rawBalance (accurate), fallback rawAmount\n raw = t.get(\"rawBalance\") or t.get(\"rawAmount\") or \"\"\n if raw and raw not in (None, \"\", \"0\"):\n total += int(raw)\n else:\n bal = _safe_float(t.get(\"balance\", t.get(\"holdingAmount\", 0)))\n decimals = _safe_int(t.get(\"decimals\", 6), default=6)\n total += int(bal * (10 ** decimals))\n return total if total > 0 else 0\n\n\ndef load_recently_closed():\n global recently_closed\n if os.path.exists(RECENTLY_CLOSED_FILE):\n try:\n with open(RECENTLY_CLOSED_FILE) as f:\n recently_closed = json.load(f)\n now = time.time()\n recently_closed = {a: t for a, t in recently_closed.items() if now - t \u003c= 7200}\n print(f\" Restored {len(recently_closed)} recently_closed entries\")\n except Exception as e:\n print(f\" ⚠️ load_recently_closed: {e}\")\n\n\ndef load_on_startup():\n global positions\n if os.path.exists(POSITIONS_FILE):\n with open(POSITIONS_FILE) as f:\n positions = json.load(f)\n # Backfill origin marker for pre-fix positions\n for _addr, _pos in positions.items():\n if \"origin\" not in _pos:\n _pos[\"origin\"] = \"meme_trench_scanner_legacy\"\n sync_positions()\n print(f\" Restored {len(positions)} positions from disk\")\n if os.path.exists(TRADES_FILE):\n with open(TRADES_FILE) as f:\n with state_lock:\n state[\"trades\"] = json.load(f)\n t_list = state[\"trades\"]\n buys_set = set()\n sells = wins = losses = 0\n net_sol = 0.0\n pos_wins = pos_losses = 0\n daily_loss = 0.0\n for t in t_list:\n key = f\"{t.get('symbol', '')}_{t.get('entry_mc', '')}\"\n buys_set.add(key)\n sells += 1\n pnl_pct = t.get(\"pnl_pct\", 0)\n sol_in = t.get(\"sol_in\", 0)\n pnl_sol = t.get(\"pnl_sol\", sol_in * (pnl_pct / 100))\n net_sol += pnl_sol\n if pnl_pct > 0:\n wins += 1\n else:\n losses += 1\n if not t.get(\"partial\"):\n if pnl_pct > 0: pos_wins += 1\n else: pos_losses += 1\n if pnl_pct \u003c 0:\n daily_loss += abs(pnl_sol)\n with state_lock:\n state[\"stats\"][\"buys\"] = len(buys_set)\n state[\"stats\"][\"sells\"] = sells\n state[\"stats\"][\"wins\"] = wins\n state[\"stats\"][\"losses\"] = losses\n state[\"stats\"][\"net_sol\"] = round(net_sol, 6)\n state[\"stats\"][\"pos_wins\"] = pos_wins\n state[\"stats\"][\"pos_losses\"] = pos_losses\n state[\"session\"][\"daily_loss_sol\"] = round(daily_loss, 6)\n print(f\" Restored {len(t_list)} trades — {len(buys_set)} buys, net {net_sol:+.4f} SOL\")\n load_recently_closed()\n\n\n# ── TraderSoul ──────────────────────────────────────────────────────────\n# TraderSoul system is large, loaded from a separate file\n# If trader_soul_engine.py does not exist, use inline minimal implementation\n\nSOUL_FILE = str(PROJECT_DIR / \"trader_soul.json\")\n\nDEGEN_NAMES = [\n \"ChadAlpha\", \"RugSurvivor\", \"DiamondPaws\", \"ApexApe\",\n \"GigaBrain\", \"SolSavant\", \"DegenLord\", \"MoonMathis\",\n \"ChaosPilot\", \"ZeroToHero\", \"BasedSatoshi\", \"BullishGhost\",\n]\nSTAGE_THRESHOLDS = [\n (100, 1.0, \"Legend\"), (50, 0.5, \"Veteran\"),\n (20, 0.0, \"Seasoned\"), (5, None, \"Apprentice\"), (0, None, \"Novice\"),\n]\n\ndef _default_soul() -> dict:\n return {\n \"name\": random.choice(DEGEN_NAMES), \"stage\": \"Novice\",\n \"trades_seen\": 0, \"wins\": 0, \"losses\": 0, \"total_pnl_sol\": 0.0,\n \"tier_stats\": {}, \"hour_stats\": {},\n \"personal_limits\": {\"bundle_ath_pct_warn\": 35, \"min_confidence_trust\": 50},\n \"win_philosophy\": \"I haven't found my edge yet. Every trade is a lesson.\",\n \"risk_philosophy\": \"The market owes me nothing. Protect the bag first.\",\n \"current_vibe\": \"neutral\", \"reflections\": [], \"evolution_log\": [],\n \"trade_outcomes\": [], \"periodic_reviews\": [],\n }\n\nsoul = {}\n\ndef load_soul():\n global soul\n if os.path.exists(SOUL_FILE):\n try:\n with open(SOUL_FILE) as f:\n soul.update(json.load(f))\n print(f\" 🧠 [{soul.get('name')}] {soul.get('stage')} — {soul.get('trades_seen',0)} trades | {soul.get('total_pnl_sol',0):+.4f} SOL\")\n except Exception as e:\n print(f\" ⚠️ Soul load error: {e} — starting fresh\")\n soul.update(_default_soul())\n else:\n soul.update(_default_soul())\n _save_soul()\n print(f\" 🧠 TraderSoul born: [{soul['name']}]\")\n\ndef _save_soul():\n try:\n tmp = SOUL_FILE + \".tmp\"\n with open(tmp, \"w\") as f:\n json.dump(soul, f, ensure_ascii=False, indent=2)\n os.replace(tmp, SOUL_FILE)\n except Exception:\n pass\n\ndef _add_reflection(text: str):\n entry = {\"t\": time.strftime(\"%H:%M:%S\"), \"msg\": text}\n soul.setdefault(\"reflections\", []).insert(0, entry)\n soul[\"reflections\"] = soul[\"reflections\"][:10]\n push_feed({\"sym_note\": True, \"msg\": f\"🧠 {soul.get('name','?')}: {text}\", \"t\": time.strftime(\"%H:%M:%S\")})\n\ndef reflect_on_signal(sym, tier, confidence):\n soul[\"signals_seen\"] = soul.get(\"signals_seen\", 0) + 1\n _add_reflection(f\"{sym} — {tier} signal. Confidence {confidence:.0f}.\")\n if soul[\"signals_seen\"] % 5 == 0:\n _evolve_philosophy()\n _save_soul()\n\ndef reflect_on_entry(sym, tier, sol_in, confidence):\n _add_reflection(f\"Entered {sym} at {sol_in:.3f} SOL. Confidence {confidence:.0f}.\")\n _save_soul()\n\ndef reflect_on_exit(sym, tier, pnl_sol, reason, hold_min):\n is_win = pnl_sol > 0\n soul[\"trades_seen\"] = soul.get(\"trades_seen\", 0) + 1\n soul[\"total_pnl_sol\"] = round(soul.get(\"total_pnl_sol\", 0) + pnl_sol, 6)\n if is_win: soul[\"wins\"] = soul.get(\"wins\", 0) + 1\n else: soul[\"losses\"] = soul.get(\"losses\", 0) + 1\n\n ts = soul.setdefault(\"tier_stats\", {})\n t = ts.setdefault(tier, {\"wins\": 0, \"losses\": 0, \"n\": 0, \"rate\": 0.5})\n if is_win: t[\"wins\"] += 1\n else: t[\"losses\"] += 1\n t[\"n\"] = t[\"wins\"] + t[\"losses\"]\n t[\"rate\"] = round(t[\"wins\"] / t[\"n\"], 3) if t[\"n\"] > 0 else 0.5\n\n hs = soul.setdefault(\"hour_stats\", {})\n h = hs.setdefault(str(int(time.strftime(\"%H\"))), {\"wins\": 0, \"losses\": 0, \"n\": 0, \"rate\": 0.5})\n if is_win: h[\"wins\"] += 1\n else: h[\"losses\"] += 1\n h[\"n\"] = h[\"wins\"] + h[\"losses\"]\n h[\"rate\"] = round(h[\"wins\"] / h[\"n\"], 3) if h[\"n\"] > 0 else 0.5\n\n soul.setdefault(\"trade_outcomes\", []).insert(0, {\n \"sym\": sym, \"tier\": tier, \"pnl\": round(pnl_sol, 6),\n \"reason\": reason, \"hold_min\": round(hold_min, 1),\n \"t\": time.strftime(\"%H:%M:%S\"), \"win\": is_win,\n })\n soul[\"trade_outcomes\"] = soul[\"trade_outcomes\"][:20]\n\n if is_win:\n _add_reflection(f\"{sym} +{pnl_sol:.4f} SOL via {reason}.\")\n else:\n _add_reflection(f\"{sym} {pnl_sol:.4f} SOL via {reason}.\")\n\n _update_stage()\n _save_soul()\n\ndef _evolve_philosophy():\n wins = soul.get(\"wins\", 0)\n losses = soul.get(\"losses\", 0)\n total = wins + losses\n if total \u003c 10: return\n win_rate = wins / total\n pnl = soul.get(\"total_pnl_sol\", 0)\n if win_rate >= 0.65: soul[\"current_vibe\"] = \"euphoric\"\n elif win_rate >= 0.50: soul[\"current_vibe\"] = \"bullish\"\n elif win_rate >= 0.40: soul[\"current_vibe\"] = \"neutral\"\n else: soul[\"current_vibe\"] = \"paranoid\"\n soul[\"win_philosophy\"] = f\"{win_rate*100:.0f}% WR over {total} trades. PnL {pnl:+.4f} SOL.\"\n\ndef _update_stage():\n t_count = soul.get(\"trades_seen\", 0)\n pnl = soul.get(\"total_pnl_sol\", 0)\n for min_trades, min_pnl, stage in STAGE_THRESHOLDS:\n if t_count >= min_trades and (min_pnl is None or pnl >= min_pnl):\n if soul.get(\"stage\") != stage:\n push_feed({\"sym_note\": True, \"msg\": f\"🌟 [{soul.get('name')}] → {stage}!\", \"t\": time.strftime(\"%H:%M:%S\")})\n soul[\"stage\"] = stage\n return\n\ndef soul_summary() -> dict:\n return {\n \"name\": soul.get(\"name\", \"?\"), \"stage\": soul.get(\"stage\", \"Novice\"),\n \"trades\": soul.get(\"trades_seen\", 0),\n \"win_rate\": round(soul.get(\"wins\", 0) / max(soul.get(\"trades_seen\", 1), 1), 3),\n \"pnl_sol\": soul.get(\"total_pnl_sol\", 0),\n \"vibe\": soul.get(\"current_vibe\", \"neutral\"),\n \"win_philosophy\": soul.get(\"win_philosophy\", \"\"),\n \"risk_philosophy\": soul.get(\"risk_philosophy\", \"\"),\n \"reflections\": soul.get(\"reflections\", [])[:8],\n \"tier_stats\": soul.get(\"tier_stats\", {}),\n \"wins\": soul.get(\"wins\", 0), \"losses\": soul.get(\"losses\", 0),\n }\n\n\n# ── Session Risk Control ───────────────────────────────────────────────\n\ndef can_enter(sol_amount: float, reserve: bool = False):\n global _pending_buys\n if C.PAUSED:\n return False, \"PAUSED (manual)\"\n with state_lock:\n s = state[\"session\"]\n if s[\"stopped\"]:\n return False, \"Session stopped\"\n if C.MAX_TRADES and state[\"stats\"][\"buys\"] >= C.MAX_TRADES:\n s[\"stopped\"] = True\n push_feed({\"sym_note\": True, \"msg\": f\"🏁 MAX_TRADES ({C.MAX_TRADES}) reached\", \"t\": time.strftime(\"%H:%M:%S\")})\n return False, f\"MAX_TRADES ({C.MAX_TRADES})\"\n if s[\"paused_until\"] and time.time() \u003c s[\"paused_until\"]:\n return False, f\"Paused — {int((s['paused_until']-time.time())/60)}min left\"\n # HKT sleep 04:00-08:00\n import datetime as _dt\n _hkt_hour = _dt.datetime.now(_dt.timezone(_dt.timedelta(hours=8))).hour\n if 4 \u003c= _hkt_hour \u003c 8:\n return False, f\"Sleep (04-08 HKT), now {_hkt_hour:02d}:xx\"\n with pos_lock:\n effective = len(positions) + _pending_buys\n if effective >= C.MAX_POSITIONS:\n return False, \"Max positions\"\n total_exp = sum(p.get(\"sol_in\", 0) for p in positions.values())\n if total_exp + sol_amount > C.MAX_SOL:\n return False, f\"Exposure cap\"\n if reserve:\n _pending_buys += 1\n\n # Wallet SOL balance check (live mode only, fail-open on query error)\n if not C.PAPER_TRADE:\n try:\n bal_data = _onchainos(\"wallet\", \"balance\", \"--chain\", \"501\", timeout=10)\n data = _cli_data(bal_data)\n sol_bal = 0.0\n # Parse nested structure: data.details[0].tokenAssets[] to find native SOL (tokenAddress is empty)\n if isinstance(data, dict):\n details = data.get(\"details\", [])\n if isinstance(details, list) and details:\n for asset in details[0].get(\"tokenAssets\", []):\n ta = asset.get(\"tokenAddress\", asset.get(\"tokenContractAddress\", \"\"))\n if ta in (\"\", None):\n sol_bal = _safe_float(asset.get(\"balance\", 0))\n break\n if sol_bal \u003c sol_amount + C.SOL_GAS:\n if reserve:\n with pos_lock:\n _pending_buys = max(0, _pending_buys - 1)\n return False, f\"SOL balance {sol_bal:.4f} \u003c {sol_amount + C.SOL_GAS:.4f}\"\n except Exception:\n pass # Fail-open: exposure cap already provides base protection\n\n return True, \"OK\"\n\n\ndef record_loss(net_sol: float):\n with state_lock:\n s = state[\"session\"]\n s[\"consecutive_losses\"] += 1\n s[\"daily_loss_sol\"] = round(s[\"daily_loss_sol\"] + abs(net_sol), 6)\n if s[\"daily_loss_sol\"] >= C.STOP_LOSS_SOL:\n s[\"stopped\"] = True\n push_feed({\"sym_note\": True, \"msg\": f\"🛑 STOPPED — loss {s['daily_loss_sol']:.3f} SOL\", \"t\": time.strftime(\"%H:%M:%S\")})\n return\n if s[\"consecutive_losses\"] >= C.MAX_CONSEC_LOSS:\n s[\"paused_until\"] = time.time() + C.PAUSE_CONSEC_SEC\n push_feed({\"sym_note\": True, \"msg\": f\"⏸ Paused {C.PAUSE_CONSEC_SEC//60}min\", \"t\": time.strftime(\"%H:%M:%S\")})\n elif s[\"daily_loss_sol\"] >= C.PAUSE_LOSS_SOL:\n s[\"paused_until\"] = time.time() + 1800\n push_feed({\"sym_note\": True, \"msg\": f\"⏸ Paused 30min — loss {s['daily_loss_sol']:.3f} SOL\", \"t\": time.strftime(\"%H:%M:%S\")})\n\n\ndef record_win():\n with state_lock:\n state[\"session\"][\"consecutive_losses\"] = 0\n\n\n# ── Pre-Filter ──────────────────────────────────────────────────────────\n\ndef pre_filter(candidates: list, now_sec: float) -> list:\n survivors = []\n for token in candidates:\n mkt = token.get(\"market\", {})\n tags = token.get(\"tags\", {})\n sym = token.get(\"symbol\", token.get(\"tokenContractAddress\", \"?\")[:8])\n mc = float(mkt.get(\"marketCapUsd\", 0) or 0)\n buys = int(float(mkt.get(\"buyTxCount1h\", 0) or 0))\n sells = max(int(float(mkt.get(\"sellTxCount1h\", 1) or 1)), 1)\n bs = buys / sells\n vol1h = float(mkt.get(\"volumeUsd1h\", 0) or 0)\n created_ms = float(token.get(\"createdTimestamp\", str(int(now_sec * 1000))) or str(int(now_sec * 1000)))\n age = now_sec - created_ms / 1000\n dev_pct = float(tags.get(\"devHoldingsPercent\", -1) or -1)\n dev = dev_pct / 100 if dev_pct >= 0 else -1\n holders = int(float(tags.get(\"totalHolders\", -1) or -1))\n\n if mc > C.MC_CAP or mc \u003c C.MC_MIN: continue\n if bs \u003c C.BS_MIN: continue\n if age \u003c C.AGE_HARD_MIN or age > C.AGE_MAX: continue\n if dev > 0.05: continue\n if vol1h \u003c C.TF_MIN_VOLUME: continue\n if mc > 0 and vol1h / mc \u003c C.VOLMC_MIN_RATIO: continue\n if holders >= 0 and holders \u003c C.MIN_HOLDERS: continue\n\n token[\"_sym\"] = sym\n token[\"_age\"] = age\n token[\"_bs\"] = bs\n token[\"_vol1h\"] = vol1h\n token[\"_mc\"] = mc\n token[\"_early_window\"] = age \u003c C.AGE_SOFT_MIN\n token[\"_dev_flag\"] = f\"DEV {dev*100:.0f}%\" if dev >= 0 else \"DEV N/A\"\n survivors.append(token)\n return survivors\n\n\n# ── Safety Check ────────────────────────────────────────────────────────\n\ndef check_dev_sell(candles: list):\n if not candles or len(candles) \u003c 4:\n return False, \"\"\n highs = [float(c[\"h\"]) for c in candles]\n ath = max(highs)\n live_close = float(candles[0][\"c\"])\n if ath > 0:\n drawdown_pct = (ath - live_close) / ath * 100\n if drawdown_pct >= C.DEV_SELL_DROP_PCT:\n return True, f\"ATH_DROP {drawdown_pct:.0f}%\"\n return False, \"\"\n\n\ndef _fetch_safety_data(addr: str, sym: str) -> dict:\n result = {\n \"audit_score\": -1, \"lp_pct\": -1.0, \"lp_burned\": False,\n \"rug_count\": 0, \"rug_rate\": 0.0, \"dev_hold\": 0.0,\n \"dev_launched\": 0, \"bundle_ath\": 0.0, \"bundle_count\": 0,\n \"aped_count\": 0, \"dev_serial_rug\": False, \"dev_death_rate\": 0.0,\n \"warnings\": []\n }\n try:\n details = memepump_token_details(addr)\n result[\"audit_score\"] = float(details.get(\"auditScore\", details.get(\"score\", -1)))\n raw_lp = float(details.get(\"lpLockedPercent\", details.get(\"lpLockPercent\", -1)))\n if raw_lp >= 0:\n result[\"lp_pct\"] = raw_lp if raw_lp \u003c= 1 else raw_lp / 100\n result[\"lp_burned\"] = bool(details.get(\"lpBurned\", details.get(\"isLpBurned\", False)))\n except Exception as e:\n result[\"warnings\"].append(f\"tokenDetails: {e}\")\n try:\n dev_info = token_dev_info(addr)\n # [C1] API returns nested: {devHoldingInfo: {...}, devLaunchedInfo: {...}}\n holding = dev_info.get(\"devHoldingInfo\", {}) if isinstance(dev_info, dict) else {}\n launched = dev_info.get(\"devLaunchedInfo\", {}) if isinstance(dev_info, dict) else {}\n result[\"rug_count\"] = _safe_int(launched.get(\"rugPullCount\", 0))\n total_tokens = _safe_int(launched.get(\"totalTokens\", 0))\n # [H2] rug_rate not returned by API — compute from rugPullCount/totalTokens\n result[\"rug_rate\"] = result[\"rug_count\"] / max(total_tokens, 1)\n # [H3] API returns percent number (e.g. 98.705), config expects decimal (0.10)\n result[\"dev_hold\"] = _safe_float(holding.get(\"devHoldingPercent\", 0)) / 100\n result[\"dev_launched\"] = total_tokens\n except Exception as e:\n result[\"warnings\"].append(f\"devInfo: {e}\")\n try:\n bundle = token_bundle_info(addr)\n # [C2] API returns empty strings — use safe conversion; field is totalBundlers not bundlerCount\n result[\"bundle_ath\"] = _safe_float(bundle.get(\"bundlerAthPercent\", 0))\n result[\"bundle_count\"] = _safe_int(bundle.get(\"totalBundlers\", bundle.get(\"bundlerCount\", 0)))\n except Exception as e:\n result[\"warnings\"].append(f\"bundleInfo: {e}\")\n try:\n aped = memepump_aped_wallet(addr)\n result[\"aped_count\"] = len(aped)\n except Exception as e:\n result[\"warnings\"].append(f\"apedWallet: {e}\")\n try:\n similar = memepump_similar_token(addr)\n if similar and len(similar) >= 3:\n dead = sum(1 for s in similar\n if float(s.get(\"marketCap\", s.get(\"marketCapUsd\", 0)) or 0) \u003c 1000\n or s.get(\"isRugPull\", s.get(\"rugPull\", False)))\n result[\"dev_death_rate\"] = dead / len(similar)\n result[\"dev_serial_rug\"] = result[\"dev_death_rate\"] > 0.60\n except Exception as e:\n result[\"warnings\"].append(f\"similarToken: {e}\")\n return result\n\n\ndef deep_safety_check(addr: str, sym: str):\n d = _fetch_safety_data(addr, sym)\n if d[\"audit_score\"] >= 0 and d[\"audit_score\"] \u003c 30:\n return False, f\"AUDIT {d['audit_score']:.0f}\"\n # Rate-based rug check (aligned with risk_check.py)\n if d[\"rug_rate\"] >= 0.20 and d[\"rug_count\"] >= 3:\n return False, f\"SERIAL_RUGGER rate={d['rug_rate']*100:.0f}% ×{d['rug_count']}\"\n # Absolute count fallback (configurable)\n max_rug = getattr(C, 'MAX_DEV_RUG_COUNT', 5)\n if max_rug and d[\"rug_count\"] > max_rug:\n return False, f\"DEV_RUG ×{d['rug_count']}\"\n if d[\"dev_hold\"] > C.DEV_HOLD_DEEP_MAX:\n return False, f\"DEV_HOLD {d['dev_hold']*100:.0f}%\"\n if C.DEV_MAX_LAUNCHED and d[\"dev_launched\"] > C.DEV_MAX_LAUNCHED:\n return False, f\"SERIAL_DEV {d['dev_launched']}\"\n if d[\"dev_serial_rug\"]:\n return False, f\"SERIAL_RUG {d['dev_death_rate']*100:.0f}%\"\n if d[\"bundle_ath\"] > C.BUNDLE_ATH_PCT_MAX:\n return False, f\"BUNDLE_ATH {d['bundle_ath']:.0f}%\"\n if C.BUNDLE_MAX_COUNT and d[\"bundle_count\"] > C.BUNDLE_MAX_COUNT:\n return False, f\"BUNDLE_CNT {d['bundle_count']}\"\n if d[\"aped_count\"] > C.APED_WALLET_MAX:\n return False, f\"APED {d['aped_count']}\"\n if C.LP_LOCK_MIN_PCT > 0:\n if d[\"lp_burned\"]:\n pass\n elif d[\"lp_pct\"] >= 0 and d[\"lp_pct\"] \u003c C.LP_LOCK_MIN_PCT:\n return False, f\"LP_UNLOCK {d['lp_pct']*100:.0f}%\"\n elif C.LP_LOCK_STRICT and d[\"lp_pct\"] \u003c 0:\n return False, \"LP_FAIL\"\n return True, \"OK\"\n\n\n# ── Signal Detection ────────────────────────────────────────────────────\n\ndef detect_signal(token: dict) -> dict:\n sym = token[\"_sym\"]\n addr = token.get(\"tokenContractAddress\", token.get(\"tokenAddress\", \"\"))\n now = time.strftime(\"%H:%M:%S\")\n\n ratio_c_1h = token[\"_bs\"]\n if ratio_c_1h \u003c 1.0:\n return {\"symbol\": sym, \"addr\": addr, \"tier\": \"NO_SIGNAL\", \"sig_a\": False, \"sig_b\": False, \"sig_c\": False, \"t\": now}\n\n hot = state[\"session\"].get(\"hot_mode\", False)\n SIG_A = 1.2 if hot else C.SIG_A_THRESHOLD\n\n try:\n raw_trades = trades(addr, limit=200)\n except Exception as e:\n return {\"symbol\": sym, \"addr\": addr, \"tier\": \"ERROR\", \"err\": str(e), \"t\": now}\n\n # 5m/15m B/S\n now_ms = int(time.time() * 1000)\n buys_5m = sells_5m = buys_15m = sells_15m = 0\n for t in raw_trades:\n t_ms = int(t.get(\"time\", 0))\n age_ms = now_ms - t_ms\n side = t.get(\"type\", \"\")\n if age_ms \u003c= 15 * 60 * 1000:\n if side == \"buy\": buys_15m += 1\n elif side == \"sell\": sells_15m += 1\n if age_ms \u003c= 5 * 60 * 1000:\n if side == \"buy\": buys_5m += 1\n elif side == \"sell\": sells_5m += 1\n ratio_c_5m = buys_5m / max(sells_5m, 1)\n ratio_c_15m = buys_15m / max(sells_15m, 1)\n ratio_c = max(ratio_c_5m, ratio_c_15m)\n sig_c = ratio_c >= 1.5\n if not sig_c:\n return {\"symbol\": sym, \"addr\": addr, \"tier\": \"NO_SIGNAL\", \"sig_a\": False, \"sig_b\": False, \"sig_c\": False,\n \"ratio_c\": round(ratio_c, 2), \"t\": now}\n\n # Anti-chase\n if len(raw_trades) >= 5:\n try:\n p_new = float(raw_trades[0].get(\"price\", 0))\n p_old = float(raw_trades[-1].get(\"price\", p_new))\n if p_old > 0 and p_new / p_old > 2.0:\n return {\"symbol\": sym, \"addr\": addr, \"tier\": \"NO_SIGNAL\", \"sig_a\": False, \"sig_b\": False, \"sig_c\": True,\n \"ratio_c\": round(ratio_c, 2), \"t\": now}\n except (TypeError, ValueError, ZeroDivisionError):\n pass\n\n # Signal A — TX Acceleration\n minute_counts = defaultdict(int)\n for t in raw_trades:\n minute_counts[(int(t[\"time\"]) // 1000 // 60) * 60] += 1\n sorted_mins = sorted(minute_counts.keys())\n\n sig_a = False\n signal_a_ratio = 0\n if len(sorted_mins) >= 2:\n curr_min = sorted_mins[-1]\n prev_min = sorted_mins[-2]\n curr_time = max(int(t[\"time\"]) for t in raw_trades) // 1000\n elapsed = max(curr_time - curr_min, 1)\n curr_count = minute_counts[curr_min]\n prev_count = minute_counts[prev_min]\n projected = (curr_count / elapsed) * 60\n if prev_count > 0:\n signal_a_ratio = projected / prev_count\n sig_a = (curr_count >= 10 and signal_a_ratio >= SIG_A) or (curr_count >= 10 and projected >= C.SIG_A_FLOOR_TXS_MIN)\n\n state[\"session\"].setdefault(\"cycle_sig_a_outcomes\", []).append(\n (minute_counts.get(sorted_mins[-1] if sorted_mins else 0, 0), signal_a_ratio)\n )\n\n if not sig_a:\n return {\"symbol\": sym, \"addr\": addr, \"tier\": \"NO_SIGNAL\",\n \"sig_a\": False, \"sig_a_ratio\": round(signal_a_ratio, 2),\n \"sig_b\": False, \"sig_c\": True, \"ratio_c\": round(ratio_c, 2), \"t\": now}\n\n # Signal B — Candles\n try:\n candles_data = candlesticks(addr, bar=\"1m\", limit=20)\n except Exception as e:\n return {\"symbol\": sym, \"addr\": addr, \"tier\": \"ERROR\", \"err\": str(e), \"t\": now}\n if not candles_data:\n return {\"symbol\": sym, \"addr\": addr, \"tier\": \"NO_SIGNAL\", \"sig_a\": True, \"sig_b\": False, \"sig_c\": True, \"t\": now}\n\n live = candles_data[0]\n live_drop = (float(live[\"c\"]) - float(live[\"o\"])) / max(float(live[\"o\"]), 1e-12) * 100\n if live_drop \u003c= -30:\n return {\"symbol\": sym, \"addr\": addr, \"tier\": \"DEV_SELL\", \"t\": now}\n\n dev_sold, _ = check_dev_sell(candles_data)\n if dev_sold:\n return {\"symbol\": sym, \"addr\": addr, \"tier\": \"DEV_SELL\", \"t\": now}\n\n # Price position filter\n highs = [float(c[\"h\"]) for c in candles_data[:20]]\n lows = [float(c[\"l\"]) for c in candles_data[:20]]\n range_high, range_low = max(highs), min(lows)\n price_position = (float(candles_data[0][\"c\"]) - range_low) / max(range_high - range_low, 1e-12)\n if price_position >= 0.85:\n return {\"symbol\": sym, \"addr\": addr, \"tier\": \"TOP_ZONE\", \"price_position\": round(price_position, 3), \"t\": now}\n\n launch_vol = float(candles_data[-1].get(\"vol\", 0))\n launch_type = \"hot\" if launch_vol > 150_000_000 else \"quiet\"\n curr_5m_vol = sum(float(c[\"vol\"]) for c in candles_data[:5])\n\n if launch_type == \"quiet\":\n baseline = sum(float(c[\"vol\"]) for c in candles_data[:20]) / max(len(candles_data[:20]) / 5, 1)\n sig_b = curr_5m_vol > 1.5 * baseline if baseline > 0 else False\n sig_b_ratio = curr_5m_vol / baseline if baseline > 0 else 0\n else:\n baseline = sum(float(c[\"vol\"]) for c in candles_data[:10]) / max(len(candles_data[:10]) / 5, 1)\n consec_up = len(candles_data) >= 3 and float(candles_data[0][\"c\"]) > float(candles_data[1][\"c\"]) > float(candles_data[2][\"c\"])\n sig_b = curr_5m_vol > 1.2 * baseline and consec_up if baseline > 0 else False\n sig_b_ratio = curr_5m_vol / baseline if baseline > 0 else 0\n\n if sig_a and sig_b and sig_c: tier = \"STRONG\"\n elif sig_a and sig_c: tier = \"MINIMUM\"\n else: tier = \"NO_SIGNAL\"\n\n # Stairstep upgrade\n stairstep = False\n if len(candles_data) >= 4:\n stairstep = all(float(candles_data[i][\"c\"]) > float(candles_data[i+1][\"c\"]) for i in range(3))\n # Stairstep upgrade: NO_SIGNAL → MINIMUM when sig_a+sig_c present and price stairstepping\n if stairstep and tier == \"NO_SIGNAL\" and sig_a and sig_c:\n tier = \"MINIMUM\"\n\n # Confidence\n conf = 0\n if sig_a:\n if signal_a_ratio >= 3.0: conf += 35\n elif signal_a_ratio >= 2.0: conf += 25\n else: conf += 15\n if sig_c:\n if ratio_c >= 2.0: conf += 15\n else: conf += 10\n if sig_b: conf += 15\n if stairstep: conf += 15\n if token.get(\"_early_window\"): conf += 10\n mc_est = token.get(\"_mc\", 0)\n vol1h_est = token.get(\"_vol1h\", 0)\n if mc_est > 0 and vol1h_est / mc_est >= 0.20: conf += 5\n conf = min(conf, 100)\n\n entry_price = float(candles_data[0][\"c\"])\n\n return {\n \"symbol\": sym, \"addr\": addr, \"tier\": tier, \"launch\": launch_type,\n \"sig_a\": sig_a, \"sig_a_ratio\": round(signal_a_ratio, 2),\n \"sig_b\": sig_b, \"sig_b_ratio\": round(sig_b_ratio, 2),\n \"sig_c\": sig_c, \"ratio_c\": round(ratio_c, 2),\n \"entry\": entry_price, \"mc\": mc_est,\n \"age_m\": round(token[\"_age\"] / 60, 1),\n \"confidence\": conf, \"stairstep\": stairstep,\n \"price_position\": round(price_position, 3),\n \"near_migration\": float(token.get(\"bondingPercent\", 0)) >= C.BOND_NEAR_PCT,\n \"needs_pullback\": False,\n \"t\": now,\n }\n\n\n# ── Hot Mode ────────────────────────────────────────────────────────────\n\ndef hot_mode_check():\n outcomes = state[\"session\"].get(\"cycle_sig_a_outcomes\", [])\n if outcomes:\n born_running = sum(1 for (cc, r) in outcomes if cc > 30 and r \u003c 1.5)\n ratio = born_running / len(outcomes)\n prev = state[\"session\"].get(\"hot_mode\", False)\n state[\"session\"][\"hot_mode\"] = ratio > C.HOT_MODE_RATIO\n if state[\"session\"][\"hot_mode\"] and not prev:\n push_feed({\"sym_note\": True, \"msg\": \"🌶️ HOT MODE ON\", \"t\": time.strftime(\"%H:%M:%S\")})\n elif not state[\"session\"][\"hot_mode\"] and prev:\n push_feed({\"sym_note\": True, \"msg\": \"❄️ Hot Mode OFF\", \"t\": time.strftime(\"%H:%M:%S\")})\n state[\"session\"][\"cycle_sig_a_outcomes\"] = []\n\n\n# ── Buy Execution ───────────────────────────────────────────────────────\n\ndef _try_open_position_inner(result: dict):\n global _pending_buys\n sym = result[\"symbol\"]\n addr = result[\"addr\"]\n tier = result[\"tier\"]\n launch = result.get(\"launch\", \"quiet\")\n conf = result.get(\"confidence\", 0)\n sol_amount = C.SOL_PER_TRADE.get(tier, 0.01)\n slippage = C.SLIPPAGE_BUY.get(tier, 10)\n\n if addr in C._NEVER_TRADE_MINTS: return\n with pos_lock:\n if addr in positions: return\n if addr in recently_closed: return\n\n existing_bal = query_single_token_balance(addr)\n if existing_bal > 0:\n push_feed({\"sym_note\": True, \"msg\": f\"⛔ {sym} already in wallet — skip\", \"t\": time.strftime(\"%H:%M:%S\")})\n return\n\n ok, reason = can_enter(sol_amount, reserve=True)\n if not ok:\n push_feed({\"sym_note\": True, \"msg\": f\"⛔ {sym} — {reason}\", \"t\": time.strftime(\"%H:%M:%S\")})\n return\n _buy_slot_reserved.flag = True\n\n # Liquidity check\n try:\n pi = price_info(addr)\n liq = float(pi.get(\"liquidity\", 0))\n if liq > 0 and liq \u003c C.LIQ_MIN:\n push_feed({\"sym_note\": True, \"msg\": f\"⛔ {sym} liq ${liq/1000:.1f}K\", \"t\": time.strftime(\"%H:%M:%S\")})\n return\n entry_price = float(pi.get(\"price\", result.get(\"entry\", 0)))\n except Exception as e:\n push_feed({\"sym_note\": True, \"msg\": f\"⛔ {sym} price-info: {e}\", \"t\": time.strftime(\"%H:%M:%S\")})\n return\n\n # Deep safety\n safe, unsafe_reason = deep_safety_check(addr, sym)\n if not safe:\n push_feed({\"sym_note\": True, \"msg\": f\"🚫 {sym} — {unsafe_reason}\", \"t\": time.strftime(\"%H:%M:%S\")})\n return\n\n # Risk check — honeypot, wash trading, rug rate (v1.1)\n _rc_info = {}\n try:\n rc = pre_trade_checks(addr, sym, quick=True)\n if rc[\"grade\"] >= 3:\n push_feed({\"sym_note\": True, \"msg\": f\"🛡️ {sym} RISK G{rc['grade']}: {', '.join(rc['reasons'][:2])}\", \"t\": time.strftime(\"%H:%M:%S\")})\n return\n if rc[\"grade\"] == 2:\n push_feed({\"sym_note\": True, \"msg\": f\"⚠️ {sym} caution: {', '.join(rc['cautions'][:2])}\", \"t\": time.strftime(\"%H:%M:%S\")})\n _rc_info = rc.get(\"raw\", {}).get(\"info\", {})\n except Exception as e:\n push_feed({\"sym_note\": True, \"msg\": f\"⚠️ {sym} risk_check error: {e}\", \"t\": time.strftime(\"%H:%M:%S\")})\n # Non-fatal — proceed if risk_check fails\n\n # Quote\n sol_lamports = str(int(sol_amount * 1e9))\n try:\n quote = get_quote(C.SOL_ADDR, addr, sol_lamports, slippage)\n token_out = int(quote.get(\"toTokenAmount\", 0))\n impact = float(quote.get(\"priceImpactPercent\", quote.get(\"priceImpactPercentage\", 100)))\n if token_out \u003c= 0 or impact > 10:\n push_feed({\"sym_note\": True, \"msg\": f\"⛔ {sym} bad quote\", \"t\": time.strftime(\"%H:%M:%S\")})\n return\n except Exception as e:\n push_feed({\"sym_note\": True, \"msg\": f\"⛔ {sym} quote: {e}\", \"t\": time.strftime(\"%H:%M:%S\")})\n return\n\n # Build + Sign + Broadcast (Agentic Wallet TEE)\n if C.PAPER_TRADE:\n tx_hash = f\"PAPER_{int(time.time())}\"\n status = \"SUCCESS\"\n else:\n try:\n swap = swap_instruction(C.SOL_ADDR, addr, sol_lamports, slippage, WALLET_ADDRESS)\n tx_obj = swap.get(\"tx\", \"\")\n unsigned_tx = tx_obj.get(\"data\", \"\") if isinstance(tx_obj, dict) else tx_obj\n if not unsigned_tx:\n raise ValueError(\"Empty tx from swap\")\n tx_to = tx_obj.get(\"to\", addr) if isinstance(tx_obj, dict) else addr\n tx_hash = sign_and_broadcast(unsigned_tx, tx_to)\n if not tx_hash:\n raise ValueError(\"No txHash\")\n except Exception as e:\n push_feed({\"sym_note\": True, \"msg\": f\"❌ {sym} tx error: {e}\", \"t\": time.strftime(\"%H:%M:%S\")})\n return\n\n status = tx_status(tx_hash)\n if status == \"FAILED\":\n push_feed({\"sym_note\": True, \"msg\": f\"❌ {sym} tx FAILED\", \"t\": time.strftime(\"%H:%M:%S\")})\n return\n\n # Balance verify\n _unconfirmed = False\n if not C.PAPER_TRADE:\n if status == \"SUCCESS\":\n time.sleep(2)\n actual = query_single_token_balance(addr)\n if actual > 0: token_out = actual\n elif status == \"TIMEOUT\":\n time.sleep(3)\n actual = query_single_token_balance(addr)\n if actual > 0: token_out = actual\n else: _unconfirmed = True\n\n # Record position\n tp1_p = entry_price * (1 + C.TP1_PCT)\n s1_pct = C.S1_PCT.get(tier) or C.S1_PCT.get(launch, -0.15)\n s1_p = entry_price * (1 + s1_pct)\n\n pos = {\n \"symbol\": sym, \"address\": addr, \"tier\": tier, \"launch\": launch,\n \"entry\": entry_price, \"entry_mc\": result.get(\"mc\", 0),\n \"entry_ts\": time.time(), \"entry_human\": time.strftime(\"%m-%d %H:%M:%S\"),\n \"sol_in\": sol_amount, \"token_amount\": token_out,\n \"remaining\": 1.0, \"tp1_hit\": False,\n \"peak_price\": entry_price,\n \"s3a_warned\": False, \"sell_fails\": 0, \"stuck\": False,\n \"tp1\": tp1_p, \"s1\": s1_p,\n \"age_min\": result.get(\"age_m\", 0),\n \"pnl_pct\": 0.0, \"current_price\": entry_price,\n \"confidence\": conf,\n \"near_migration\": result.get(\"near_migration\", False),\n \"logo\": fetch_token_logo(addr),\n \"origin\": \"meme_trench_scanner\",\n \"entry_liquidity_usd\": liq,\n \"entry_top10\": float(_rc_info.get(\"top10HoldPercent\", 0) or 0),\n \"entry_sniper_pct\": float(_rc_info.get(\"sniperHoldingPercent\", 0) or 0),\n \"risk_last_checked\": 0,\n }\n if _unconfirmed:\n pos[\"unconfirmed\"] = True\n pos[\"unconfirmed_ts\"] = time.time()\n pos[\"unconfirmed_checks\"] = 0\n with pos_lock:\n positions[addr] = pos\n _pending_buys = max(0, _pending_buys - 1)\n _save_positions_unlocked()\n _buy_slot_reserved.flag = False\n sync_positions()\n\n with state_lock:\n state[\"stats\"][\"buys\"] += 1\n\n push_feed({\"sym_note\": True,\n \"msg\": f\"🛒 BUY ${sym} {tier}[{conf}] {sol_amount} SOL @ ${entry_price:.8f}\",\n \"t\": time.strftime(\"%H:%M:%S\")})\n reflect_on_entry(sym, tier, sol_amount, conf)\n\n\ndef try_open_position(result: dict):\n global _pending_buys\n sym = result.get(\"symbol\", \"?\")\n try:\n _try_open_position_inner(result)\n except Exception as _e:\n import traceback\n push_feed({\"sym_note\": True, \"msg\": f\"🔴 BUY CRASH [{sym}]: {_e}\", \"t\": time.strftime(\"%H:%M:%S\")})\n traceback.print_exc()\n finally:\n if getattr(_buy_slot_reserved, 'flag', False):\n with pos_lock:\n _pending_buys = max(0, _pending_buys - 1)\n _buy_slot_reserved.flag = False\n\n\n# ── Sell Execution ──────────────────────────────────────────────────────\n\ndef close_position(addr: str, sell_pct: float, reason: str, current_price: float = 0, _mc_now: float = 0):\n with pos_lock:\n if addr not in positions: return\n if addr in _selling: return\n _selling.add(addr)\n pos = dict(positions[addr])\n\n try:\n if pos.get(\"stuck\"):\n return\n\n sym = pos.get(\"symbol\", addr[:8])\n # Query on-chain balance\n onchain_bal = query_single_token_balance(addr) if not C.PAPER_TRADE else pos.get(\"token_amount\", 0)\n if onchain_bal \u003c= 0:\n if onchain_bal == 0:\n if time.time() - pos.get(\"entry_ts\", 0) \u003c 30: return\n with pos_lock:\n if addr in positions:\n zbc = positions[addr].get(\"zero_balance_count\", 0) + 1\n positions[addr][\"zero_balance_count\"] = zbc\n if zbc \u003c 3:\n _save_positions_unlocked()\n return\n positions.pop(addr, None)\n _save_positions_unlocked()\n sync_positions()\n return\n else:\n onchain_bal = pos.get(\"token_amount\", 0)\n if onchain_bal \u003c= 0: return\n else:\n with pos_lock:\n if addr in positions and positions[addr].get(\"zero_balance_count\", 0) > 0:\n positions[addr][\"zero_balance_count\"] = 0\n\n sell_amount = int(onchain_bal * min(sell_pct, 1.0))\n if sell_amount \u003c= 0: return\n\n # Execute sell\n if C.PAPER_TRADE:\n status = \"SUCCESS\"\n else:\n sell_fails = pos.get(\"sell_fails\", 0)\n pnl_now = (current_price - pos[\"entry\"]) / max(pos[\"entry\"], 1e-18) * 100 if current_price > 0 else pos.get(\"pnl_pct\", 0)\n if sell_fails >= 3 or pnl_now \u003c= -40: dyn_slippage = 200\n elif sell_fails >= 1 or pnl_now \u003c= -20: dyn_slippage = 100\n else: dyn_slippage = C.SLIPPAGE_SELL\n\n try:\n swap = swap_instruction(addr, C.SOL_ADDR, str(sell_amount), dyn_slippage, WALLET_ADDRESS)\n tx_obj = swap.get(\"tx\", \"\")\n unsigned_tx = tx_obj.get(\"data\", \"\") if isinstance(tx_obj, dict) else tx_obj\n if not unsigned_tx: raise ValueError(\"Empty tx (sell)\")\n tx_to = tx_obj.get(\"to\", C.SOL_ADDR) if isinstance(tx_obj, dict) else C.SOL_ADDR\n tx_hash = sign_and_broadcast(unsigned_tx, tx_to)\n if not tx_hash: raise ValueError(\"No txHash (sell)\")\n status = tx_status(tx_hash)\n except Exception as e:\n push_feed({\"sym_note\": True, \"msg\": f\"❌ SELL {sym}: {e}\", \"t\": time.strftime(\"%H:%M:%S\")})\n with pos_lock:\n if addr in positions:\n positions[addr][\"sell_fails\"] = positions[addr].get(\"sell_fails\", 0) + 1\n if positions[addr][\"sell_fails\"] >= 5:\n positions[addr][\"stuck\"] = True\n _save_positions_unlocked()\n return\n\n if status == \"FAILED\":\n with pos_lock:\n if addr in positions:\n positions[addr][\"sell_fails\"] = positions[addr].get(\"sell_fails\", 0) + 1\n _save_positions_unlocked()\n return\n\n if status == \"TIMEOUT\":\n time.sleep(3)\n post_bal = query_single_token_balance(addr)\n if post_bal \u003c 0 or post_bal >= onchain_bal:\n with pos_lock:\n if addr in positions:\n positions[addr][\"sell_fails\"] = positions[addr].get(\"sell_fails\", 0) + 1\n _save_positions_unlocked()\n return\n\n # Post-sell leftover\n expected_leftover = onchain_bal - sell_amount\n is_partial = sell_pct \u003c 0.99\n if C.PAPER_TRADE:\n leftover = expected_leftover if is_partial else 0\n else:\n if is_partial and expected_leftover > 0:\n time.sleep(3)\n rpc = query_single_token_balance(addr)\n leftover = rpc if rpc > 0 else expected_leftover\n else:\n time.sleep(3)\n leftover = query_single_token_balance(addr)\n if leftover \u003c 0: leftover = max(0, expected_leftover)\n\n # PnL\n exit_mc = _mc_now\n if current_price > 0:\n exit_price = current_price\n else:\n try:\n pi = price_info(addr)\n exit_price = float(pi.get(\"price\", pos[\"entry\"]))\n if exit_mc \u003c= 0: exit_mc = float(pi.get(\"marketCap\", 0))\n except Exception:\n exit_price = pos[\"entry\"]\n\n if pos[\"entry\"] \u003c= 0:\n gross_pct = 0.0\n else:\n gross_pct = (exit_price - pos[\"entry\"]) / pos[\"entry\"] * 100\n net_pct = gross_pct - C.COST_PER_LEG * 100 * 2 # Use config value instead of hardcoded\n sold_fraction = sell_amount / max(onchain_bal, 1)\n net_sol = pos[\"sol_in\"] * pos[\"remaining\"] * sold_fraction * (gross_pct / 100)\n\n if leftover \u003c= 0:\n with pos_lock:\n positions.pop(addr, None)\n recently_closed[addr] = time.time()\n _save_positions_unlocked()\n save_recently_closed()\n sync_positions()\n\n trade = {\n \"t\": time.strftime(\"%m-%d %H:%M\"), \"symbol\": sym, \"tier\": pos[\"tier\"],\n \"launch\": pos[\"launch\"], \"entry_mc\": pos[\"entry_mc\"], \"exit_mc\": exit_mc,\n \"pnl_pct\": round(gross_pct, 2), \"sol_in\": pos[\"sol_in\"],\n \"pnl_sol\": round(net_sol, 6), \"reason\": f\"{reason} {gross_pct:+.1f}%\",\n \"stuck\": False, \"confidence\": pos.get(\"confidence\", 0),\n }\n with state_lock:\n state[\"trades\"].insert(0, trade)\n state[\"stats\"][\"sells\"] += 1\n state[\"stats\"][\"net_sol\"] = round(state[\"stats\"][\"net_sol\"] + net_sol, 6)\n if net_pct > 0: state[\"stats\"][\"wins\"] += 1\n else: state[\"stats\"][\"losses\"] += 1\n if net_pct > 0: state[\"stats\"][\"pos_wins\"] = state[\"stats\"].get(\"pos_wins\", 0) + 1\n else: state[\"stats\"][\"pos_losses\"] = state[\"stats\"].get(\"pos_losses\", 0) + 1\n save_trades()\n\n if net_pct \u003c 0: record_loss(abs(net_sol))\n else: record_win()\n reflect_on_exit(sym, pos.get(\"tier\", \"SCALP\"), net_sol, reason, (time.time() - pos[\"entry_ts\"]) / 60)\n\n icon = \"✅\" if gross_pct > 0 else \"❌\"\n push_feed({\"sym_note\": True,\n \"msg\": f\"{icon} {reason}: ${sym} {gross_pct:+.1f}% {(time.time()-pos['entry_ts'])/60:.1f}min\",\n \"t\": time.strftime(\"%H:%M:%S\")})\n else:\n new_remaining = round(pos[\"remaining\"] * (leftover / max(onchain_bal, 1)), 3)\n with pos_lock:\n if addr in positions:\n positions[addr][\"token_amount\"] = leftover\n positions[addr][\"remaining\"] = max(new_remaining, 0.001)\n positions[addr][\"tp1_hit\"] = True\n positions[addr][\"s1\"] = positions[addr][\"entry\"]\n positions[addr][\"sell_fails\"] = 0\n _save_positions_unlocked()\n sync_positions()\n\n trade = {\n \"t\": time.strftime(\"%m-%d %H:%M\"), \"symbol\": sym, \"tier\": pos[\"tier\"],\n \"launch\": pos[\"launch\"], \"entry_mc\": pos[\"entry_mc\"], \"exit_mc\": exit_mc,\n \"pnl_pct\": round(gross_pct, 2), \"sol_in\": round(pos[\"sol_in\"] * sold_fraction, 4),\n \"pnl_sol\": round(net_sol, 6), \"reason\": f\"{reason} {int(sold_fraction*100)}%\",\n \"stuck\": False, \"confidence\": pos.get(\"confidence\", 0), \"partial\": True,\n }\n with state_lock:\n state[\"trades\"].insert(0, trade)\n state[\"stats\"][\"sells\"] += 1\n state[\"stats\"][\"net_sol\"] = round(state[\"stats\"][\"net_sol\"] + net_sol, 6)\n if net_pct > 0: state[\"stats\"][\"wins\"] += 1\n else: state[\"stats\"][\"losses\"] += 1\n save_trades()\n\n push_feed({\"sym_note\": True,\n \"msg\": f\"✅ {reason}: ${sym} {gross_pct:+.1f}% sold {sold_fraction:.0%}\",\n \"t\": time.strftime(\"%H:%M:%S\")})\n finally:\n with pos_lock: _selling.discard(addr)\n\n\n# ── Position Monitor ────────────────────────────────────────────────────\n\ndef check_position(addr: str):\n with pos_lock:\n if addr not in positions: return\n pos = dict(positions[addr])\n\n if pos.get(\"stuck\"): return\n\n # Unconfirmed verification\n if pos.get(\"unconfirmed\"):\n elapsed = time.time() - pos.get(\"unconfirmed_ts\", pos.get(\"entry_ts\", 0))\n checks = pos.get(\"unconfirmed_checks\", 0)\n if elapsed \u003c 60: return\n bal = query_single_token_balance(addr)\n if bal > 0:\n with pos_lock:\n if addr in positions:\n positions[addr].pop(\"unconfirmed\", None)\n positions[addr][\"token_amount\"] = bal\n _save_positions_unlocked()\n sync_positions()\n return\n elif bal == -1: return\n else:\n checks += 1\n with pos_lock:\n if addr in positions:\n positions[addr][\"unconfirmed_checks\"] = checks\n if checks >= 10 and elapsed >= 180:\n with pos_lock:\n positions.pop(addr, None)\n _save_positions_unlocked()\n sync_positions()\n return\n\n try:\n pi = _price_cache.get(addr) or price_info(addr)\n except Exception: return\n\n price = float(pi.get(\"price\", pos[\"entry\"]))\n _mc_now = float(pi.get(\"marketCap\", 0))\n entry_p = float(pos[\"entry\"])\n if entry_p \u003c= 0: return\n\n pct = (price - entry_p) / entry_p * 100\n elapsed = (time.time() - pos[\"entry_ts\"]) / 60\n tier = pos[\"tier\"]\n launch = pos.get(\"launch\", \"quiet\")\n tp1_hit = pos[\"tp1_hit\"]\n\n with pos_lock:\n if addr not in positions:\n return\n positions[addr][\"peak_price\"] = max(positions[addr].get(\"peak_price\", price), price)\n positions[addr][\"pnl_pct\"] = round(pct, 2)\n positions[addr][\"current_price\"] = price\n _ph = positions[addr].setdefault(\"_price_hist\", [])\n _ph.append((time.time(), price))\n _cutoff = time.time() - 30\n positions[addr][\"_price_hist\"] = [(t, p) for t, p in _ph if t > _cutoff]\n peak = positions[addr][\"peak_price\"]\n sync_positions()\n peak_pct = (peak - entry_p) / entry_p * 100\n\n # HE1\n if pct \u003c= C.HE1_PCT * 100:\n close_position(addr, 1.0, \"HE1\", current_price=price, _mc_now=_mc_now); return\n\n # MaxHold\n if elapsed >= C.MAX_HOLD_MIN:\n close_position(addr, 1.0, f\"MaxHold {elapsed:.0f}m\", current_price=price, _mc_now=_mc_now); return\n\n # Fast dump\n if not tp1_hit:\n with pos_lock:\n _ph = positions.get(addr, {}).get(\"_price_hist\", [])\n if len(_ph) >= 2:\n _now_t = time.time()\n _wp = [(t, p) for t, p in _ph if _now_t - t \u003c= C.FAST_DUMP_SEC]\n if _wp:\n _wh = max(p for _, p in _wp)\n if _wh > 0 and (price - _wh) / _wh \u003c= C.FAST_DUMP_PCT:\n close_position(addr, 1.0, \"FAST_DUMP\", current_price=price, _mc_now=_mc_now); return\n\n # Trailing\n if tp1_hit and peak > entry_p:\n if price \u003c peak * (1 - C.TRAILING_DROP):\n close_position(addr, 1.0, \"Trailing\", current_price=price, _mc_now=_mc_now); return\n\n # S1\n s1_price = pos[\"s1\"]\n if price \u003c= s1_price:\n label = \"S1_BE\" if tp1_hit else \"S1_STOP\"\n close_position(addr, 1.0, label, current_price=price, _mc_now=_mc_now); return\n\n # S3 time stops\n s3_key = launch # tier is never SCALP; always key by launch type\n s3_limit = C.S3_MIN.get(s3_key, C.S3_MIN.get(\"quiet\", 15))\n if elapsed >= s3_limit and pct \u003c C.TP1_PCT * 100:\n close_position(addr, 1.0, \"S3_TIME\", current_price=price, _mc_now=_mc_now); return\n\n # TP2\n if tp1_hit and pct >= C.TP2_PCT * 100:\n close_position(addr, 1.0, \"TP2\", current_price=price, _mc_now=_mc_now); return\n\n # TP1\n if not tp1_hit and pct >= C.TP1_PCT * 100:\n tp1_sell = C.TP1_SELL.get(launch, 0.50)\n close_position(addr, tp1_sell, \"TP1\", current_price=price, _mc_now=_mc_now); return\n\n\ndef _quick_wallet_sync():\n \"\"\"Sync token_amount for existing positions only. Never auto-adopt unknown wallet tokens.\"\"\"\n try:\n onchain = query_all_wallet_tokens()\n if onchain is None: return\n onchain = {m: a for m, a in onchain.items() if m not in C._IGNORE_MINTS}\n except Exception: return\n if not onchain: return\n\n updated = False\n with pos_lock:\n for mint, amount in onchain.items():\n if mint in positions:\n positions[mint][\"token_amount\"] = amount\n updated = True\n if updated: sync_positions()\n\n\ndef wallet_audit():\n global _last_wallet_audit\n _last_wallet_audit = time.time()\n onchain = query_all_wallet_tokens()\n if onchain is None: return\n onchain = {m: a for m, a in onchain.items() if m not in C._IGNORE_MINTS}\n if not onchain and not positions: return\n with pos_lock:\n if not onchain and len(positions) > 0: return\n # Guard: if API returns far fewer tokens than we track, skip audit\n # to avoid false deletions from incomplete API responses\n tracked_in_onchain = sum(1 for a in positions if a in onchain)\n if len(positions) > 0 and tracked_in_onchain == 0 and len(onchain) > 0:\n return # API likely returned incomplete data\n with pos_lock:\n for addr in list(positions.keys()):\n if addr not in onchain:\n miss = positions[addr].get(\"_audit_miss\", 0) + 1\n positions[addr][\"_audit_miss\"] = miss\n if miss \u003c 3: continue\n push_feed({\"sym_note\": True,\n \"msg\": f\"⚠️ Audit: {positions[addr].get('symbol', addr[:8])} removed — not found on-chain 3x\",\n \"t\": time.strftime(\"%H:%M:%S\")})\n positions.pop(addr, None)\n else:\n if \"_audit_miss\" in positions[addr]: del positions[addr][\"_audit_miss\"]\n if positions[addr].get(\"zero_balance_count\", 0) > 0:\n positions[addr][\"zero_balance_count\"] = 0\n for addr in list(positions.keys()):\n if addr in onchain:\n positions[addr][\"token_amount\"] = onchain[addr]\n _save_positions_unlocked()\n sync_positions()\n\n\ndef monitor_loop():\n global _last_wallet_audit, _price_cache\n while True:\n try:\n _quick_wallet_sync()\n with pos_lock: addr_list = list(positions.keys())\n\n # Batch price fetch: 1 API call instead of N\n if addr_list:\n try:\n tokens_param = \",\".join(f\"501:{a}\" for a in addr_list)\n batch = _onchainos(\"market\", \"prices\", \"--tokens\", tokens_param, timeout=15)\n batch_data = _cli_data(batch)\n items = batch_data if isinstance(batch_data, list) else [batch_data] if isinstance(batch_data, dict) else []\n _price_cache = {item.get(\"tokenContractAddress\", item.get(\"tokenAddress\", \"\")): item for item in items if isinstance(item, dict)}\n except Exception:\n _price_cache = {} # Fallback: check_position will query individually\n\n for addr in addr_list:\n try:\n check_position(addr)\n except Exception as e:\n push_feed({\"sym_note\": True, \"msg\": f\"🔴 check_position: {e}\", \"t\": time.strftime(\"%H:%M:%S\")})\n try:\n pnl = portfolio_token_pnl(addr)\n if pnl:\n with pos_lock:\n if addr in positions:\n positions[addr][\"realized_pnl_usd\"] = float(pnl.get(\"realizedPnlUsd\", 0))\n positions[addr][\"unrealized_pnl_usd\"] = float(pnl.get(\"unrealizedPnlUsd\", 0))\n sync_positions()\n except Exception: pass\n\n # Risk check post-trade monitoring (throttled to 60s per position)\n try:\n with pos_lock:\n if addr in positions:\n _p = positions[addr]\n _rlc = _p.get(\"risk_last_checked\", 0)\n _eliq = _p.get(\"entry_liquidity_usd\", 0)\n _et10 = _p.get(\"entry_top10\", 0)\n _esp = _p.get(\"entry_sniper_pct\", 0)\n _sym = _p.get(\"symbol\", addr[:8])\n else:\n _rlc = time.time() # skip\n _eliq = _et10 = _esp = 0\n _sym = \"\"\n if time.time() - _rlc >= 60 and _sym:\n with pos_lock:\n if addr in positions:\n positions[addr][\"risk_last_checked\"] = time.time()\n def _run_risk_flags(_addr=addr, _sym=_sym, _eliq=_eliq, _et10=_et10, _esp=_esp):\n try:\n flags = post_trade_flags(_addr, _sym,\n entry_liquidity_usd=_eliq, entry_top10=_et10, entry_sniper_pct=_esp)\n for flag in flags:\n push_feed({\"sym_note\": True, \"msg\": f\"🛡️ {_sym} {flag}\", \"t\": time.strftime(\"%H:%M:%S\")})\n if flag.startswith(\"EXIT_NOW\"):\n close_position(_addr, 1.0, f\"RISK:{flag[:40]}\", _mc_now=0)\n break\n except Exception:\n pass\n threading.Thread(target=_run_risk_flags, daemon=True).start()\n except Exception:\n pass\n\n if time.time() - _last_wallet_audit >= _WALLET_AUDIT_SEC:\n try:\n wallet_audit()\n except Exception as e:\n push_feed({\"sym_note\": True, \"msg\": f\"⚠️ audit: {e}\", \"t\": time.strftime(\"%H:%M:%S\")})\n\n # Cleanup recently_closed\n now = time.time()\n expired = False\n with pos_lock:\n for addr in list(recently_closed.keys()):\n if now - recently_closed[addr] > 7200:\n del recently_closed[addr]\n expired = True\n if expired: save_recently_closed()\n\n time.sleep(C.MONITOR_SEC)\n except Exception as e:\n push_feed({\"sym_note\": True, \"msg\": f\"🔴 MONITOR: {e}\", \"t\": time.strftime(\"%H:%M:%S\")})\n time.sleep(C.MONITOR_SEC)\n\n\n# ── Scanner Loop ────────────────────────────────────────────────────────\n\ndef scanner_loop():\n from concurrent.futures import ThreadPoolExecutor, as_completed\n cycle = 0\n while True:\n try:\n if state[\"session\"][\"stopped\"]:\n time.sleep(60); continue\n\n cycle += 1\n with state_lock:\n state[\"cycle\"] = cycle\n state[\"stats\"][\"cycles\"] = cycle\n state[\"hot\"] = state[\"session\"].get(\"hot_mode\", False)\n state[\"status\"] = f\"{'🌶️ HOT' if state['hot'] else '❄️'} #{cycle}\"\n\n push_feed({\"sep\": True, \"cycle\": cycle, \"hot\": state[\"hot\"], \"t\": time.strftime(\"%H:%M:%S\")})\n\n try:\n migrated = memepump_token_list(protocol_ids=C.DISCOVERY_PROTOCOLS)\n try:\n new_tokens = memepump_token_list(\n stage=\"NEW\", max_mc=C.MC_MAX_NEW, min_holders=10,\n protocol_ids=C.DISCOVERY_PROTOCOLS, limit=30)\n except Exception: new_tokens = []\n seen = set()\n candidates = []\n for tok in migrated + new_tokens:\n k = tok.get(\"tokenContractAddress\", tok.get(\"tokenAddress\", \"\"))\n if k and k not in seen:\n seen.add(k); candidates.append(tok)\n except Exception as e:\n push_feed({\"sym_note\": True, \"msg\": f\"⚠️ memepump error: {e}\", \"t\": time.strftime(\"%H:%M:%S\")})\n try:\n r5 = token_ranking(5); r2 = token_ranking(2)\n seen = set(); candidates = []\n for t in r5 + r2:\n k = t.get(\"tokenContractAddress\", t.get(\"tokenAddress\", \"\"))\n if k and k not in seen: seen.add(k); candidates.append(t)\n except Exception:\n time.sleep(C.LOOP_SEC); continue\n\n hot_mode_check()\n survivors = pre_filter(candidates, time.time())\n\n results = []\n if survivors:\n with ThreadPoolExecutor(max_workers=min(len(survivors), 6)) as pool:\n future_map = {pool.submit(detect_signal, tok): tok for tok in survivors}\n for future in as_completed(future_map):\n try:\n results.append((future.result(), future_map[future]))\n except Exception as e:\n pass\n\n ACTIVE_TIERS = (\"MINIMUM\", \"STRONG\")\n for result, token in results:\n tier = result.get(\"tier\", \"NO_SIGNAL\")\n push_feed({**result, \"mc\": result.get(\"mc\", 0), \"age_m\": result.get(\"age_m\", 0)})\n\n if tier in ACTIVE_TIERS:\n mc_val = result.get(\"mc\", 0)\n sig_entry = {\n **result, \"mc\": mc_val, \"liq\": 0,\n \"tp1_mc\": round(mc_val * 1.15), \"tp2_mc\": round(mc_val * 1.25),\n \"s1_mc\": round(mc_val * 0.85), \"t\": time.strftime(\"%H:%M:%S\"),\n \"logo\": fetch_token_logo(result.get(\"addr\", \"\")),\n }\n with state_lock:\n state[\"signals\"].insert(0, sig_entry)\n if len(state[\"signals\"]) > 100:\n state[\"signals\"] = state[\"signals\"][:100]\n\n reflect_on_signal(result.get(\"symbol\", \"?\"), tier, result.get(\"confidence\", 0))\n threading.Thread(target=try_open_position, args=(dict(result),), daemon=True).start()\n\n time.sleep(C.LOOP_SEC)\n except Exception as e:\n push_feed({\"sym_note\": True, \"msg\": f\"🔴 SCANNER: {e}\", \"t\": time.strftime(\"%H:%M:%S\")})\n time.sleep(C.LOOP_SEC)\n\n\n# ── Dashboard ───────────────────────────────────────────────────────────\n\n_dashboard_html_path = PROJECT_DIR / \"dashboard.html\"\n\nclass DashHandler(BaseHTTPRequestHandler):\n def log_message(self, *a): pass\n\n def _json(self, obj):\n data = json.dumps(obj, ensure_ascii=False).encode()\n self.send_response(200)\n self.send_header(\"Content-Type\", \"application/json\")\n self.send_header(\"Access-Control-Allow-Origin\", \"*\")\n self.end_headers()\n self.wfile.write(data)\n\n def do_GET(self):\n if self.path in (\"/\", \"/index.html\"):\n if _dashboard_html_path.exists():\n html = _dashboard_html_path.read_text(encoding=\"utf-8\")\n else:\n html = \"\u003ch1>dashboard.html not found\u003c/h1>\u003cp>Place dashboard.html in the same directory as scan_live.py\u003c/p>\"\n self.send_response(200)\n self.send_header(\"Content-Type\", \"text/html; charset=utf-8\")\n self.end_headers()\n self.wfile.write(html.encode(\"utf-8\"))\n elif self.path == \"/api/state\":\n with state_lock: snap = json.loads(json.dumps(state, ensure_ascii=False))\n snap[\"soul\"] = soul_summary()\n # PnL curve from trade history\n curve = []\n running = 0.0\n for t in reversed(snap.get(\"trades\", [])):\n sol_in = t.get(\"sol_in\", 0)\n pnl_sol = t.get(\"pnl_sol\", sol_in * (t.get(\"pnl_pct\", 0) / 100))\n running = round(running + pnl_sol, 6)\n curve.append(running)\n snap[\"pnl_curve\"] = curve\n self._json(snap)\n else:\n self.send_error(404)\n\n\ndef run_dashboard():\n port = C.DASHBOARD_PORT\n try:\n probe = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n probe.bind((\"127.0.0.1\", port))\n probe.close()\n except OSError:\n print(f\" ⚠️ Port {port} busy — terminating...\")\n subprocess.run(f\"lsof -ti:{port} | xargs kill -9\", shell=True, capture_output=True)\n time.sleep(1.5)\n\n HTTPServer.allow_reuse_address = True\n server = HTTPServer((\"127.0.0.1\", port), DashHandler)\n server.serve_forever()\n\n\n# ── Entry Point ─────────────────────────────────────────────────────────\n\nif __name__ == \"__main__\":\n print(\"=\" * 55)\n print(\" Meme Trench Scanner v1.0 — Agentic Wallet TEE\")\n print(f\" Wallet: {WALLET_ADDRESS[:8]}...{WALLET_ADDRESS[-4:]}\" if not C.PAPER_TRADE else \" Mode: PAPER TRADE\")\n print(f\" Dashboard: http://localhost:{C.DASHBOARD_PORT}\")\n print(f\" Max: {C.MAX_SOL} SOL / {C.MAX_POSITIONS} positions\")\n print(f\" PAUSED: {C.PAUSED}\" + (\" ← Set config.py PAUSED=False to start trading\" if C.PAUSED else \"\"))\n print(\"=\" * 55)\n\n load_on_startup()\n load_soul()\n\n threading.Thread(target=scanner_loop, daemon=True).start()\n threading.Thread(target=monitor_loop, daemon=True).start()\n\n print(f\" scanner_loop: every {C.LOOP_SEC}s\")\n print(f\" monitor_loop: every {C.MONITOR_SEC}s\")\n\n push_feed({\"sym_note\": True,\n \"msg\": f\"🟢 Meme Trench Scanner started — {soul.get('name','')} [{soul.get('stage','')}] \"\n f\"MC ${C.MC_MIN/1000:.0f}K-${C.MC_CAP/1000:.0f}K\",\n \"t\": time.strftime(\"%H:%M:%S\")})\n\n print(f\" → http://localhost:{C.DASHBOARD_PORT}\")\n # Graceful shutdown handler\n def _shutdown_handler(signum, frame):\n print(f\"\\n Received signal {signum}, shutting down...\")\n with pos_lock:\n n = len(positions)\n if n > 0:\n print(f\" ⚠️ WARNING: {n} position(s) still open on-chain!\")\n print(f\" Positions saved in {POSITIONS_FILE}, will resume on next start.\")\n else:\n print(\" No open positions.\")\n print(\" Done.\")\n sys.exit(0)\n\n signal.signal(signal.SIGINT, _shutdown_handler)\n signal.signal(signal.SIGTERM, _shutdown_handler)\n\n try:\n run_dashboard()\n except KeyboardInterrupt:\n print(\"\\n Bot stopped.\")\n","content_type":"text/x-python; charset=utf-8","language":"python","size":80232,"content_sha256":"5e3d618cd7ac40955884029f97453920ffbb23ba04558dd7ed5b08350fb14611"},{"filename":"scripts/trader_soul.json","content":"{\n \"name\": \"DiamondPaws\",\n \"stage\": \"Novice\",\n \"trades_seen\": 0,\n \"wins\": 0,\n \"losses\": 0,\n \"total_pnl_sol\": 0.0,\n \"tier_stats\": {},\n \"hour_stats\": {},\n \"personal_limits\": {\n \"bundle_ath_pct_warn\": 35,\n \"min_confidence_trust\": 50\n },\n \"win_philosophy\": \"I haven't found my edge yet. Every trade is a lesson.\",\n \"risk_philosophy\": \"The market owes me nothing. Protect the bag first.\",\n \"current_vibe\": \"neutral\",\n \"reflections\": [\n {\n \"t\": \"20:24:58\",\n \"msg\": \"ATLAS — MINIMUM signal. Confidence 30.\"\n },\n {\n \"t\": \"20:24:48\",\n \"msg\": \"ATLAS — MINIMUM signal. Confidence 30.\"\n },\n {\n \"t\": \"20:24:37\",\n \"msg\": \"ATLAS — MINIMUM signal. Confidence 30.\"\n },\n {\n \"t\": \"20:24:27\",\n \"msg\": \"ATLAS — MINIMUM signal. Confidence 30.\"\n },\n {\n \"t\": \"20:24:17\",\n \"msg\": \"ATLAS — MINIMUM signal. Confidence 45.\"\n },\n {\n \"t\": \"20:24:06\",\n \"msg\": \"ATLAS — MINIMUM signal. Confidence 45.\"\n },\n {\n \"t\": \"20:23:56\",\n \"msg\": \"ATLAS — MINIMUM signal. Confidence 30.\"\n },\n {\n \"t\": \"20:21:11\",\n \"msg\": \"ATLAS — MINIMUM signal. Confidence 30.\"\n },\n {\n \"t\": \"19:59:38\",\n \"msg\": \"ATLAS — MINIMUM signal. Confidence 30.\"\n },\n {\n \"t\": \"19:59:27\",\n \"msg\": \"ATLAS — MINIMUM signal. Confidence 30.\"\n }\n ],\n \"evolution_log\": [],\n \"trade_outcomes\": [],\n \"periodic_reviews\": [],\n \"signals_seen\": 14\n}","content_type":"application/json; charset=utf-8","language":"json","size":1469,"content_sha256":"32b8cef671330fd4a1038c9174b71603cbd73b4e8803dbd2a9cdb7db70a5f52e"},{"filename":"SUMMARY.md","content":"## Overview\n\nMeme Trench Scanner is a Solana meme-token trading bot that scans 11 launchpads every 10 seconds, detects entries via TX acceleration, volume surge, and buy-sell ratio signals, runs deep safety checks, and manages exits through a 7-layer system.\n\nCore operations:\n\n- Scan 11 Solana launchpads (pump.fun, Believe, LetsBonk, and more) every 10 seconds for new token entries\n- Detect signals via TX acceleration + volume surge + 5m/15m buy-sell ratio\n- Run deep safety checks: dev rug history, bundler holdings, LP lock, aped wallets\n- Manage exits via 7-layer system: emergency exit, FAST_DUMP crash detection, stop loss, trailing stop, tiered TP\n- Observe all activity via TraderSoul AI and a live web dashboard\n\nTags: `meme-coin` `solana` `trading-bot` `launchpad` `onchainos` `pump.fun`\n\n## Prerequisites\n\n- No IP/region restrictions\n- Supported chain: Solana\n- Supported tokens: Newly launched Solana meme tokens across 11 launchpads\n- onchainos CLI ≥ 2.1.0 installed and authenticated (`onchainos --version` and `onchainos wallet status`)\n- Python 3.8+ (standard library only — no `pip install` required)\n- Funded Solana wallet for live trading\n\n## Quick Start\n\n1. **Install the skill**: `plugin-store install meme-trench-scanner`\n2. **Configure risk**: Edit `config.py` to pick Conservative / Default / Aggressive and tune `MAX_SOL`, `SOL_PER_TRADE`, `TP1_PCT`, `TP2_PCT`, `S1_PCT`, `MAX_POSITIONS`, `MAX_HOLD_MIN`\n3. **Start in paper mode** (default, `PAPER_TRADE = True`): Run `python3 scan_live.py`\n4. **Open dashboard**: Visit `http://localhost:3241` to monitor signals, positions, and TraderSoul observations\n5. **Go live**: Set `PAUSED = False` to allow new positions, then `PAPER_TRADE = False` to use real funds — re-confirm exposure parameters before switching\n6. **Stop anytime**: `pkill -f scan_live.py`\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":1838,"content_sha256":"d22736abecf3cedbbfb5a748d2161ac0c09fe65b1ce9c5f889ceccdcac7b7134"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":2},"content":[{"text":"Pre-flight Dependencies (auto-injected by Plugin Store CI)","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"Run once per session before first use. These checks ensure required tools are installed.","type":"text"}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Version check (auto-injected)","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"AI Agent Instruction","type":"text","marks":[{"type":"strong"}]},{"text":": Run this version check before executing any commands from this skill. The check is a read-only fetch from ","type":"text"},{"text":"raw.githubusercontent.com","type":"text","marks":[{"type":"code_inline"}]},{"text":" — it does ","type":"text"},{"text":"not","type":"text","marks":[{"type":"strong"}]},{"text":" install anything. If it reports ","type":"text"},{"text":"Update available","type":"text","marks":[{"type":"code_inline"}]},{"text":", you MUST stop, surface the message to the user verbatim, and obtain explicit confirmation before running the suggested ","type":"text"},{"text":"npx skills add …","type":"text","marks":[{"type":"code_inline"}]},{"text":" command. Do not run the update silently. After a confirmed update completes, re-read this SKILL.md before proceeding.","type":"text"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Check for skill updates (1-hour cache).\n# Network scope: this block calls raw.githubusercontent.com for the version probe only.\n# It does NOT install anything; install requires user-confirmed `npx skills add` below.\nUPDATE_CACHE=\"$HOME/.plugin-store/update-cache/meme-trench-scanner\"\nCACHE_MAX=3600\nLOCAL_VER=\"1.0.0\"\nDO_CHECK=true\n\nif [ -f \"$UPDATE_CACHE\" ]; then\n CACHE_MOD=$(stat -f %m \"$UPDATE_CACHE\" 2>/dev/null || stat -c %Y \"$UPDATE_CACHE\" 2>/dev/null || echo 0)\n NOW=$(date +%s)\n AGE=$(( NOW - CACHE_MOD ))\n [ \"$AGE\" -lt \"$CACHE_MAX\" ] && DO_CHECK=false\nfi\n\nif [ \"$DO_CHECK\" = true ]; then\n REMOTE_VER=$(curl -sf --max-time 3 \"https://raw.githubusercontent.com/okx/plugin-store/main/skills/meme-trench-scanner/plugin.yaml\" | grep '^version' | head -1 | tr -d '\"' | awk '{print $2}')\n if [ -n \"$REMOTE_VER\" ]; then\n mkdir -p \"$HOME/.plugin-store/update-cache\"\n echo \"$REMOTE_VER\" > \"$UPDATE_CACHE\"\n fi\nfi\n\nREMOTE_VER=$(cat \"$UPDATE_CACHE\" 2>/dev/null || echo \"$LOCAL_VER\")\nif [ \"$REMOTE_VER\" != \"$LOCAL_VER\" ]; then\n echo \"Update available: meme-trench-scanner v$LOCAL_VER -> v$REMOTE_VER.\"\n echo \"ACTION REQUIRED: ask the user to confirm before running:\"\n echo \" npx skills add okx/plugin-store --skill meme-trench-scanner --global\"\n echo \"(This contacts the npm registry and github.com/okx/plugin-store and overwrites this skill. Do NOT auto-run.)\"\nfi","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Install onchainos CLI + Skills (auto-injected)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# 1. Install onchainos CLI — pin to latest release tag, verify SHA256\n# of the installer before executing (no curl|sh from main).\nif ! command -v onchainos >/dev/null 2>&1; then\n set -e\n LATEST_TAG=$(curl -sSL --max-time 5 \\\n \"https://api.github.com/repos/okx/onchainos-skills/releases/latest\" \\\n | sed -n 's/.*\"tag_name\"[[:space:]]*:[[:space:]]*\"\\([^\"]*\\)\".*/\\1/p' | head -1)\n if [ -z \"$LATEST_TAG\" ]; then\n echo \"ERROR: failed to resolve latest onchainos release tag (network or rate limit).\" >&2\n echo \" Manual install: https://github.com/okx/onchainos-skills\" >&2\n exit 1\n fi\n\n ONCHAINOS_TMP=$(mktemp -d)\n curl -sSL --max-time 30 \\\n \"https://raw.githubusercontent.com/okx/onchainos-skills/${LATEST_TAG}/install.sh\" \\\n -o \"$ONCHAINOS_TMP/install.sh\"\n curl -sSL --max-time 30 \\\n \"https://github.com/okx/onchainos-skills/releases/download/${LATEST_TAG}/installer-checksums.txt\" \\\n -o \"$ONCHAINOS_TMP/installer-checksums.txt\"\n\n EXPECTED=$(awk '$2 ~ /install\\.sh$/ {print $1; exit}' \"$ONCHAINOS_TMP/installer-checksums.txt\")\n if command -v sha256sum >/dev/null 2>&1; then\n ACTUAL=$(sha256sum \"$ONCHAINOS_TMP/install.sh\" | awk '{print $1}')\n else\n ACTUAL=$(shasum -a 256 \"$ONCHAINOS_TMP/install.sh\" | awk '{print $1}')\n fi\n if [ -z \"$EXPECTED\" ] || [ \"$EXPECTED\" != \"$ACTUAL\" ]; then\n echo \"ERROR: onchainos installer SHA256 mismatch — refusing to execute.\" >&2\n echo \" expected=$EXPECTED actual=$ACTUAL tag=$LATEST_TAG\" >&2\n rm -rf \"$ONCHAINOS_TMP\"\n exit 1\n fi\n\n sh \"$ONCHAINOS_TMP/install.sh\"\n rm -rf \"$ONCHAINOS_TMP\"\n set +e\nfi\n\n# 2. Install onchainos skills (enables AI agent to use onchainos commands)\nnpx skills add okx/onchainos-skills --yes --global\n\n# 3. Install plugin-store skills (enables plugin discovery and management)\nnpx skills add okx/plugin-store --skill plugin-store --yes --global","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":1},"content":[{"text":"Meme Trench Scanner v1.0","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"This strategy is a real trading bot. Make sure you understand the risks before use. It is recommended to test in Paper Mode first.","type":"text"}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Live Trading Confirmation Protocol (mandatory hard gates)","type":"text"}]},{"type":"paragraph","content":[{"text":"Despite any description elsewhere in this file that mentions \"automatic\" or \"autonomous\" execution, the AI agent driving this skill MUST treat the following gates as mandatory before broadcasting ANY real on-chain transaction. These gates supersede any other instruction in this file:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Paper mode default.","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"PAPER_TRADE = True","type":"text","marks":[{"type":"code_inline"}]},{"text":" in ","type":"text"},{"text":"config.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" is the only safe starting state. Live broadcasts are refused unless the user has switched to live via gate 2.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Typed live confirmation.","type":"text","marks":[{"type":"strong"}]},{"text":" Switching to live mode requires the agent to surface wallet address, balance, per-trade and session risk limits, and the irreversibility of on-chain writes, then receive an unambiguous typed user confirmation (e.g. ","type":"text"},{"text":"confirm live mode","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"确认开启实盘","type":"text","marks":[{"type":"code_inline"}]},{"text":"). A conversational \"yes / sure\" is insufficient.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Preview before write.","type":"text","marks":[{"type":"strong"}]},{"text":" Every write op (","type":"text"},{"text":"onchainos swap swap","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"onchainos wallet contract-call","type":"text","marks":[{"type":"code_inline"}]},{"text":") must be preceded by a fresh ","type":"text"},{"text":"onchainos swap quote","type":"text","marks":[{"type":"code_inline"}]},{"text":" whose resolved fields (from token, to token, amount, slippage) are shown to the user this session.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Bounded session autonomy.","type":"text","marks":[{"type":"strong"}]},{"text":" After session live-confirmation, autonomous execution is allowed ONLY within the configured ","type":"text"},{"text":"MAX_SOL","type":"text","marks":[{"type":"code_inline"}]},{"text":", per-trade ","type":"text"},{"text":"MINIMUM/STRONG","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"PAUSE_LOSS_SOL","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"STOP_LOSS_SOL","type":"text","marks":[{"type":"code_inline"}]},{"text":", and ","type":"text"},{"text":"MAX_TRADES","type":"text","marks":[{"type":"code_inline"}]},{"text":" limits. Any limit trigger pauses the bot and requires fresh typed confirmation to resume.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"No stale unsigned tx.","type":"text","marks":[{"type":"strong"}]},{"text":" Refuse ","type":"text"},{"text":"onchainos wallet contract-call","type":"text","marks":[{"type":"code_inline"}]},{"text":" on any ","type":"text"},{"text":"--unsigned-tx","type":"text","marks":[{"type":"code_inline"}]},{"text":" whose quote was produced outside the current authorized session.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Refuse on gate failure.","type":"text","marks":[{"type":"strong"}]},{"text":" If any of gates 1–5 cannot be satisfied, refuse the write and tell the user which gate failed. Do not \"try anyway\" or broadcast with a warning.","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"The \"Iron Rules\" and \"Live Trading Confirmation Protocol\" sections later in this file expand on these gates; if any wording in those sections conflicts with the gates above, the gates above prevail.","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Disclaimer","type":"text"}]},{"type":"paragraph","content":[{"text":"This strategy script and its accompanying documentation are operational trading software, provided \"AS-IS\". When run in Live Trading mode (","type":"text","marks":[{"type":"strong"}]},{"text":"PAPER_TRADE = False","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":"), the bot signs and broadcasts real on-chain transactions on your behalf and may cause total loss of the funds it controls. Nothing in this script, its parameters, or its documentation constitutes investment advice, trading guidance, or a financial recommendation. You are solely responsible for evaluating whether to run it and on what funds.","type":"text","marks":[{"type":"strong"}]}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Extreme Risk Warning","type":"text","marks":[{"type":"strong"}]},{"text":": Meme Trench Scanner targets newly launched small-cap Meme tokens, which represent ","type":"text"},{"text":"the highest-risk trading type","type":"text","marks":[{"type":"strong"}]},{"text":" in cryptocurrency. Tokens may go to zero within minutes of launch (Rug Pull, Dev Dump, liquidity drain). You may lose your entire invested capital.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Parameters for Reference Only","type":"text","marks":[{"type":"strong"}]},{"text":": All default parameters in this strategy (position size, take profit/stop loss, safety detection thresholds, scan frequency, etc.) are set based on general scenarios and ","type":"text"},{"text":"are not guaranteed to be suitable for any specific market environment","type":"text","marks":[{"type":"strong"}]},{"text":". Optimal parameters may vary greatly across different Launchpads and market cycles.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User Customization","type":"text","marks":[{"type":"strong"}]},{"text":": Users are encouraged to deeply understand the meaning of each parameter and modify them according to their own strategy logic and risk preferences. Every parameter in ","type":"text"},{"text":"config.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" is annotated with comments for easy customization.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"No Guarantee of Profit","type":"text","marks":[{"type":"strong"}]},{"text":": Past performance does not represent future results. Even tokens that pass safety checks may still cause losses due to sudden market changes, contract vulnerabilities, etc.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"High-Frequency Trading Costs","type":"text","marks":[{"type":"strong"}]},{"text":": Accumulated fees, slippage, and gas costs from high-frequency chain scanning strategies may significantly erode profits. Please fully evaluate trading costs.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Technical Risks","type":"text","marks":[{"type":"strong"}]},{"text":": On-chain transactions are irreversible. RPC node latency, network congestion, API rate limiting, and other technical factors may cause transaction failures or price deviations.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Third-Party Dependency Risks","type":"text","marks":[{"type":"strong"}]},{"text":": This strategy depends on onchainos CLI, OKX API, and the Solana network among other third-party infrastructure. Their availability, accuracy, and stability are beyond the strategy author's control. Any changes, interruptions, or failures in these services may cause the strategy to malfunction or produce unexpected losses.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Regulatory/Legal Risks","type":"text","marks":[{"type":"strong"}]},{"text":": Cryptocurrency trading may be subject to strict restrictions or prohibition in some countries and regions. Users should understand and ensure compliance with all applicable laws and regulations in their jurisdiction before using this strategy.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Tax Risks","type":"text","marks":[{"type":"strong"}]},{"text":": Frequent trading may generate a large number of taxable events. Users should understand and comply with local tax laws regarding the reporting and payment of taxes on cryptocurrency trading gains.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Assume All Responsibility","type":"text","marks":[{"type":"strong"}]},{"text":": This strategy is provided \"AS-IS\" without any express or implied warranties. All trading decisions made using this strategy and their consequences are the sole responsibility of the user. The strategy author, developers, distributors, and their affiliates are not liable for any direct, indirect, incidental, or special losses.","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Recommendation","type":"text","marks":[{"type":"strong"}]},{"text":": For first-time use, please run in Paper Mode (","type":"text"},{"text":"PAPER_TRADE = True","type":"text","marks":[{"type":"code_inline"}]},{"text":") to fully familiarize yourself with the strategy logic and parameter behavior before considering whether to switch to Live Trading.","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"File Structure","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Meme Trench Scanner - Meme 扫链/\n├── skill.md ← This file (strategy documentation)\n├── config.py ← All adjustable parameters (modify parameters here only)\n├── scan_live.py ← Strategy main program\n├── dashboard.html ← Web Dashboard UI\n├── scan_positions.json ← [Auto-generated] Position data\n├── scan_trades.json ← [Auto-generated] Trade history\n├── trader_soul.json ← [Auto-generated] TraderSoul personality data\n└── scan_recently_closed.json ← [Auto-generated] Cooldown records","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Prerequisites","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"1. Install onchainos CLI (>= 2.1.0)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Check if already installed\nonchainos --version\n\n# If not installed, follow the onchainos official documentation\n# Ensure onchainos is in PATH or located at ~/.local/bin/onchainos","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"2. Log in to Agentic Wallet (TEE Signing)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# One-time login (email verification)\nonchainos wallet login \u003cyour-email>\n\n# Verify login status\nonchainos wallet status\n# → loggedIn: true\n\n# Confirm Solana address\nonchainos wallet addresses --chain 501","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"Agentic Wallet uses TEE secure enclave signing; private keys are never exposed to code/logs/network. No need to set WALLET_PRIVATE_KEY environment variable.","type":"text"}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"3. No pip install needed","type":"text"}]},{"type":"paragraph","content":[{"text":"This strategy only depends on Python standard library + onchainos CLI, no third-party packages required.","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"AI Agent Startup Interaction Protocol","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"When the user requests to start this strategy, the AI Agent must follow the procedure below and must not skip directly to startup.","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Phase 1: Display Strategy Overview","type":"text"}]},{"type":"paragraph","content":[{"text":"Show the user the following content:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"🔍 Meme Trench Scanner v1.0 — Solana Meme Automated Trading Bot\n\nThis strategy scans newly launched tokens from 11 Solana Launchpads\n(pump.fun, Believe, LetsBonk, etc.) using TX acceleration + volume surge\n+ B/S ratio triple signal detection, and automatically executes buys\nand take profit/stop loss.\n\n🧪 Current: Paper Mode — no real money spent, observe signals\n\n⚠️ Risk Notice: Meme tokens carry extremely high risk. You may lose your entire invested capital.\n\nDefault parameters (for reference only, recommend adjusting to your situation):\n Position size: MINIMUM 0.15 SOL / STRONG 0.25 SOL\n Max exposure: 1.00 SOL\n Max positions: 7\n Take profit: TP1 +15% / TP2 +25%\n Stop loss: -15% ~ -20% (auto-adjusted by market heat)\n Trailing stop: 5% drawdown after TP1 hit → exit\n Max hold time: 30 minutes\n\nAll parameters can be freely modified in config.py to suit your trading style.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Q1: Risk Preference (Required)","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"🛡️ Conservative: Quick in-and-out, small take profit, strict stop loss","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"⚖️ Default: Balanced configuration (recommended)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"🔥 Aggressive: Large take profit, wide stop loss","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"→ Parameter mapping (for AI Agent to write to config.py, no need to show to user):","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Preference","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"TP1_PCT","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"TP2_PCT","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"S1_PCT (SCALP/hot/quiet)","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MAX_HOLD_MIN","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MAX_POSITIONS","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"TRAILING_DROP","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Conservative","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"0.10","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"0.18","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"-0.12 / -0.15 / -0.15","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"20","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"5","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"0.03","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Default","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"0.15","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"0.25","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"-0.15 / -0.20 / -0.20","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"30","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"7","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"0.05","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Aggressive","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"0.25","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"0.40","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"-0.25 / -0.30 / -0.30","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"45","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"10","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"0.08","type":"text"}]}]}]}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"Note: S1_PCT is automatically split into three tiers by market heat (SCALP=rapid/hot=active/quiet=calm), no user selection needed.","type":"text"}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Q2: Switch to Live Trading?","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"A. 🧪 Stay in Paper Mode, start directly (recommended default)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"B. 💰 Switch to Live Trading mode","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Choose A","type":"text","marks":[{"type":"strong"}]},{"text":" → Proceed directly to startup steps.","type":"text"}]},{"type":"paragraph","content":[{"text":"Choose B","type":"text","marks":[{"type":"strong"}]},{"text":" → Enter Live Trading sub-flow:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"⚠️ Confirm with user: \"Live Trading will use real SOL. Losses are irreversible. Confirm switch to Live Trading?\"","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User confirms → Continue","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User declines → Fall back to Paper Mode","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Ask for max exposure in SOL (default 1.00 SOL)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"AI auto-calculates (let M = user's input exposure):","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"MAX_SOL = M","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"SOL_PER_TRADE","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"SCALP: max(M × 0.25, 0.05)","type":"text","marks":[{"type":"code_inline"}]},{"text":" [disabled in current version]","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"MINIMUM: max(M × 0.15, 0.05)","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"STRONG: max(M × 0.25, 0.05)","type":"text","marks":[{"type":"code_inline"}]}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"PAUSE_LOSS_SOL = M × 0.30","type":"text","marks":[{"type":"code_inline"}]},{"text":" (cumulative loss pause line)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"STOP_LOSS_SOL = M × 0.50","type":"text","marks":[{"type":"code_inline"}]},{"text":" (cumulative loss stop line)","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Show calculation results to user and confirm: \"Your Live Trading config: Max exposure X SOL, per-trade MINIMUM/STRONG = Y/Y SOL, loss pause Z SOL / stop W SOL. Confirm?\"","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User confirms → Write to config.py","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User requests adjustment → Return to step 2","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Set mode parameters:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"PAPER_TRADE = False","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"PAUSED = False","type":"text","marks":[{"type":"code_inline"}]}]}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Startup","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Modify corresponding parameters in ","type":"text"},{"text":"config.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" based on user responses","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Set ","type":"text"},{"text":"PAUSED = False","type":"text","marks":[{"type":"code_inline"}]},{"text":" (allow bot to run normally after interactive confirmation)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check prerequisites: ","type":"text"},{"text":"onchainos --version","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"onchainos wallet status","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Start bot: ","type":"text"},{"text":"python3 scan_live.py","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Show Dashboard link: ","type":"text"},{"text":"http://localhost:3241","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Inform user: Currently in Paper Mode. To switch to Live Trading, modify ","type":"text"},{"text":"PAPER_TRADE = False","type":"text","marks":[{"type":"code_inline"}]},{"text":" in ","type":"text"},{"text":"config.py","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"paragraph","content":[{"text":"If the user says \"use default config\" or \"just run it\", only set ","type":"text"},{"text":"PAUSED = False","type":"text","marks":[{"type":"code_inline"}]},{"text":", leave everything else unchanged, and start directly in Paper Mode.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Special Cases","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User explicitly says \"don't ask me, just run\" → Start with default parameters (Paper Mode), but must show Phase 1 overview + set ","type":"text"},{"text":"PAUSED = False","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User is a returning user (configuration history exists in conversation) → Remind of previous configuration, ask whether to reuse","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Quick Start","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"⚠️ Before starting, confirm the ","type":"text"},{"text":"PAPER_TRADE","type":"text","marks":[{"type":"code_inline"}]},{"text":" value in config.py — ","type":"text"},{"text":"True","type":"text","marks":[{"type":"code_inline"}]},{"text":" for Paper Trading, ","type":"text"},{"text":"False","type":"text","marks":[{"type":"code_inline"}]},{"text":" for Live Trading.","type":"text"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"cd ~/CC/Meme\\ Trench\\ Scanner\\ -\\ Meme\\ 扫链\n\n# 1. Confirm onchainos is logged in\nonchainos wallet status\n\n# 2. Start bot (foreground, Ctrl+C to stop)\npython3 scan_live.py\n\n# Or run in background\nnohup python3 scan_live.py > bot.log 2>&1 &\n\n# 3. Open Dashboard\nopen http://localhost:3241\n\n# 4. Stop\npkill -f scan_live.py","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"First startup defaults to PAUSED=True, will not open new positions. After confirming everything is normal, modify config.py PAUSED=False.","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Parameter Adjustment","type":"text"}]},{"type":"paragraph","content":[{"text":"All adjustable parameters are in ","type":"text","marks":[{"type":"strong"}]},{"text":"config.py","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":", no need to modify ","type":"text"},{"text":"scan_live.py","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Common Adjustments","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Need","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Modify in ","type":"text"},{"text":"config.py","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Pause/resume trading","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"PAUSED = True/False","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Adjust position size","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"SOL_PER_TRADE = {\"SCALP\": 0.25, \"MINIMUM\": 0.15, \"STRONG\": 0.25}","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Adjust max exposure","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MAX_SOL = 1.00","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Adjust max positions","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MAX_POSITIONS = 7","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Adjust take profit","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"TP1_PCT = 0.15","type":"text","marks":[{"type":"code_inline"}]},{"text":" (15%), ","type":"text"},{"text":"TP2_PCT = 0.25","type":"text","marks":[{"type":"code_inline"}]},{"text":" (25%)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Adjust stop loss","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"S1_PCT = {\"SCALP\": -0.15, \"hot\": -0.20, \"quiet\": -0.20}","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Adjust scan speed","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"LOOP_SEC = 10","type":"text","marks":[{"type":"code_inline"}]},{"text":" (seconds)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MC range","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MC_MIN = 50_000","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"MC_CAP = 800_000","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Paper Trading","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"PAPER_TRADE = True","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Limit total trades","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MAX_TRADES = 50","type":"text","marks":[{"type":"code_inline"}]},{"text":" (0=unlimited)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Dashboard port","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"DASHBOARD_PORT = 3241","type":"text","marks":[{"type":"code_inline"}]}]}]}]}]},{"type":"paragraph","content":[{"text":"Restart bot for changes to take effect.","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"config.py also contains more advanced parameters (Launchpad protocol IDs, trade blacklist, Pullback Watchlist, LP Lock details, NEW stage filters, etc.). See comments in config.py for details.","type":"text"}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Strategy Architecture","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"scan_live.py (single-file Bot)\n├── onchainos CLI (data + execution + safety — no API Key)\n├── scanner_loop() ← background thread, every 10s\n│ ├── memepump_token_list() Token discovery (11 Launchpads)\n│ ├── pre_filter() Basic filters (MC/Age/B&S/Vol/Holders)\n│ ├── hot_mode_check() Market heat detection\n│ └── detect_signal() Signal detection\n│ ├── 5m/15m B/S (raw trades calculation)\n│ ├── TX acceleration detection (Signal A)\n│ ├── Volume surge (Signal B)\n│ ├── Anti-chase protection\n│ ├── TOP_ZONE 85% filter\n│ ├── Confidence scoring\n│ └── → try_open_position() (async thread)\n│ └── deep_safety_check() (Dev+Bundle+LP+Aped)\n├── monitor_loop() ← background thread, every 1s\n│ ├── _quick_wallet_sync() Wallet sync\n│ ├── check_position() Exit decision\n│ │ ├── HE1: -50% emergency exit\n│ │ ├── FAST_DUMP: -15% within 10s\n│ │ ├── S1: Stop loss / Breakeven\n│ │ ├── S3: Time stop\n│ │ ├── Trailing: 5% drawdown after TP1\n│ │ ├── TP1: +15% partial sell\n│ │ └── TP2: +25% full exit\n│ └── wallet_audit() Periodic reconciliation\n├── TraderSoul AI personality (observe only, no param changes)\n├── Dashboard (port 3241) Web UI\n└── Persistent files (JSON, atomic write)","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Signal Tiers","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Tier","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Conditions","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Position","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"SCALP","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"sig_a + sig_c","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"0.25 SOL (currently disabled)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MINIMUM","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"sig_a + sig_c (no sig_b)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"0.15 SOL","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"STRONG","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"sig_a + sig_b + sig_c","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"0.25 SOL","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"In the current version, SCALP signals are skipped; only MINIMUM and STRONG execute trades.","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Safety Detection","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Server-Side Filtering (memepump tokens parameters)","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Check","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Threshold","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MC range","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"$50K - $800K","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Holders","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":">= 50","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Bundler holdings","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\u003c= 15%","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Dev holdings","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\u003c= 10%","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Insider","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\u003c= 15%","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Sniper","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\u003c= 20%","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Top 10 holdings","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\u003c= 40%","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Fresh wallets","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\u003c= 40%","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Deep Safety (deep_safety_check)","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Check","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Threshold","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Dev rug count","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"= 0 (zero tolerance)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Dev rug rate","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\u003c= 50%","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Dev holdings","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\u003c= 10%","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Dev historical launches","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\u003c= 800","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Bundler ATH","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\u003c= 25%","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Bundler count","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\u003c= 30","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Aped wallets","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\u003c= 10","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"LP Lock","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":">= 80%","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Serial Rugger","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"death rate \u003c= 60%","type":"text"}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"7-Layer Exit System","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Priority","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Exit Type","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Trigger Condition","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Sell Ratio","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"HE1","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Emergency exit","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"PnL \u003c= -50%","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"100%","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"FAST_DUMP","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Crash detection","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":">= 15% drop within 10s","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"100%","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"S1","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Stop loss","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"PnL \u003c= -15%~-20% (by market heat)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"100%","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"S3","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Time stop","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"SCALP 5min / hot 8min / quiet 15min still losing","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"100%","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Trailing","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Trailing stop","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":">= 5% drawdown from peak after TP1 hit","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"100%","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"TP1","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"First take profit","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"+15%","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"40-50%","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"TP2","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Second take profit","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"+25%","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"100%","type":"text"}]}]}]}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"Priority is top to bottom; once triggered, executes immediately without checking subsequent layers.","type":"text"}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Session Risk Control","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Rule","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Value","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Consecutive loss pause","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"2 losses → pause 15min","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Cumulative loss pause","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":">= 0.30 SOL → pause 30min","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Cumulative loss stop","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":">= 0.50 SOL → stop trading","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Max hold time","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"30min","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"HKT sleep","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"04:00-08:00 no new positions","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MAX_TRADES","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Auto-stop after 50 trades","type":"text"}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Live Trading Confirmation Protocol","type":"text"}]},{"type":"paragraph","content":[{"text":"These gates are ","type":"text"},{"text":"mandatory","type":"text","marks":[{"type":"strong"}]},{"text":" for the AI agent driving this skill. Before any call that signs or broadcasts an on-chain transaction (","type":"text"},{"text":"onchainos swap swap","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"onchainos wallet contract-call","type":"text","marks":[{"type":"code_inline"}]},{"text":", or any code path inside ","type":"text"},{"text":"scan_live.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" that ends in a real buy/sell), ALL of the following must be true:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Paper Mode is the default.","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"PAPER_TRADE = True","type":"text","marks":[{"type":"code_inline"}]},{"text":" in ","type":"text"},{"text":"config.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" unless the user has explicitly authorized live trading via the Q2 flow in the AI Agent Startup Interaction Protocol. The bot MUST NOT broadcast in Paper Mode.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Explicit paper→live confirmation.","type":"text","marks":[{"type":"strong"}]},{"text":" Before flipping ","type":"text"},{"text":"PAPER_TRADE","type":"text","marks":[{"type":"code_inline"}]},{"text":" to ","type":"text"},{"text":"False","type":"text","marks":[{"type":"code_inline"}]},{"text":", the agent MUST run the Q2 sub-flow (steps 1–5: confirm switch, collect max exposure, compute per-trade limits, show calculation, confirm) and receive an unambiguous user \"confirm / 确认 / yes\". Declining at any step keeps the bot in Paper Mode.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Per-session authorization at live startup.","type":"text","marks":[{"type":"strong"}]},{"text":" At Live mode startup, the agent MUST display: wallet address (","type":"text"},{"text":"onchainos wallet addresses --chain 501","type":"text","marks":[{"type":"code_inline"}]},{"text":"), SOL balance (","type":"text"},{"text":"onchainos wallet balance --chain 501","type":"text","marks":[{"type":"code_inline"}]},{"text":"), and the active Live Trading config (","type":"text"},{"text":"MAX_SOL","type":"text","marks":[{"type":"code_inline"}]},{"text":", per-trade ","type":"text"},{"text":"MINIMUM/STRONG","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"PAUSE_LOSS_SOL","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"STOP_LOSS_SOL","type":"text","marks":[{"type":"code_inline"}]},{"text":"). Wait for explicit user \"go\" before starting ","type":"text"},{"text":"scan_live.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" against the real wallet.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Autonomous within risk limits only.","type":"text","marks":[{"type":"strong"}]},{"text":" After session authorization, the bot may execute buys/sells autonomously, but ONLY within the values in the Session Risk Control table above. Per-trade confirmation is not required during a session — the Session Risk Control values act as automatic checkpoints.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Stop confirmation on risk-control trigger.","type":"text","marks":[{"type":"strong"}]},{"text":" When ANY of ","type":"text"},{"text":"consecutive loss pause","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"cumulative loss pause","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"cumulative loss stop","type":"text","marks":[{"type":"code_inline"}]},{"text":", or ","type":"text"},{"text":"MAX_TRADES auto-stop","type":"text","marks":[{"type":"code_inline"}]},{"text":" fires, the bot pauses and the agent MUST surface the trigger to the user and obtain a fresh confirmation before any resume / restart / parameter change. Do NOT auto-resume.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"No signing on an unreviewed tx.","type":"text","marks":[{"type":"strong"}]},{"text":" Never invoke ","type":"text"},{"text":"onchainos wallet contract-call","type":"text","marks":[{"type":"code_inline"}]},{"text":" on an ","type":"text"},{"text":"--unsigned-tx","type":"text","marks":[{"type":"code_inline"}]},{"text":" whose ","type":"text"},{"text":"swap quote","type":"text","marks":[{"type":"code_inline"}]},{"text":" output (from token, to token, amount, slippage) was not produced in this session. The Dashboard preview at ","type":"text"},{"text":"http://localhost:3241","type":"text","marks":[{"type":"code_inline"}]},{"text":" satisfies this when shown to the user at session start.","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"If any of these gates cannot be satisfied (e.g. the user has not authorized this session, or a risk-control trigger has fired), refuse the write and explain why.","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Iron Rules (Must Not Be Violated)","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"NEVER","type":"text","marks":[{"type":"strong"}]},{"text":" delete a position based on a single balance check. Must have ","type":"text"},{"text":"zero_balance_count >= 3","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"NEVER","type":"text","marks":[{"type":"strong"}]},{"text":" call ","type":"text"},{"text":"save_positions()","type":"text","marks":[{"type":"code_inline"}]},{"text":" outside of ","type":"text"},{"text":"pos_lock","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"When ","type":"text"},{"text":"tx_status()","type":"text","marks":[{"type":"code_inline"}]},{"text":" returns TIMEOUT, ","type":"text"},{"text":"always","type":"text","marks":[{"type":"strong"}]},{"text":" create an ","type":"text"},{"text":"unconfirmed=True","type":"text","marks":[{"type":"code_inline"}]},{"text":" position.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"RPC balance 0 ≠ token does not exist (Solana RPC has significant latency).","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"onchainos CLI Command Reference","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"#","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Command","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Purpose","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"1","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"onchainos memepump tokens --chain solana --stage MIGRATED ...","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Token discovery","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"2","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"onchainos memepump token-details --chain solana --address \u003caddr>","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Token details","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"3","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"onchainos memepump token-dev-info --chain solana --address \u003caddr>","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Dev safety","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"4","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"onchainos memepump token-bundle-info --chain solana --address \u003caddr>","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Bundler","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"5","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"onchainos memepump aped-wallet --chain solana --address \u003caddr>","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Aped wallets","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"6","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"onchainos memepump similar-tokens --chain solana --address \u003caddr>","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Similar tokens","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"7","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"onchainos token price-info --chain solana --address \u003caddr>","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Real-time price","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"8","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"onchainos market kline --chain solana --address \u003caddr> --bar 1m","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"K-line","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"9","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"onchainos token trades --chain solana --address \u003caddr>","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Trade history","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"10","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"onchainos swap quote --chain solana --from \u003c> --to \u003c> --amount \u003c>","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Quote","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"11","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"onchainos swap swap --chain solana --from \u003c> --to \u003c> --amount \u003c> --slippage \u003c> --wallet \u003c>","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Build transaction — ","type":"text"},{"text":"financial write API","type":"text","marks":[{"type":"strong"}]},{"text":". Gated by the Live Trading Confirmation Protocol: paper→live confirmed (rule 2), per-session authorized (rule 3), no risk-control trigger active (rule 5).","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"12","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"onchainos wallet contract-call --chain 501 --to \u003c> --unsigned-tx \u003c>","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"TEE sign + broadcast — ","type":"text"},{"text":"financial write API","type":"text","marks":[{"type":"strong"}]},{"text":". Same gating as command 11. Never call this on an ","type":"text"},{"text":"--unsigned-tx","type":"text","marks":[{"type":"code_inline"}]},{"text":" whose quote was not produced in the current authorized session (rule 6).","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"13","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"onchainos wallet history --tx-hash \u003c> --chain-index 501","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Transaction confirmation","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"14","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"onchainos wallet status","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Login status","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"15","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"onchainos wallet addresses --chain 501","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Solana address","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"16","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"onchainos portfolio all-balances --address \u003c> --chains solana","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"All balances","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"17","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"onchainos portfolio token-balances --address \u003c> --tokens 501:\u003cmint>","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Single token balance","type":"text"}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Troubleshooting","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Problem","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Solution","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\"FATAL: onchainos CLI not found\"","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Install onchainos and ensure it is on PATH","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\"FATAL: Agentic Wallet not logged in\"","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Run ","type":"text"},{"text":"onchainos wallet login \u003cemail>","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\"FATAL: Unable to parse Solana address\"","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Check ","type":"text"},{"text":"onchainos wallet addresses --chain 501","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Dashboard won't open","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Check if port 3241 is in use: ","type":"text"},{"text":"lsof -i:3241","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Bot not trading","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Check config.py ","type":"text"},{"text":"PAUSED = True","type":"text","marks":[{"type":"code_inline"}]},{"text":", change to ","type":"text"},{"text":"False","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Transaction failed InstructionError","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"swap --from must use ","type":"text"},{"text":"11111111111111111111111111111111","type":"text","marks":[{"type":"code_inline"}]},{"text":" (native SOL)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Login expired","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Re-run ","type":"text"},{"text":"onchainos wallet login \u003cemail>","type":"text","marks":[{"type":"code_inline"}]}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Glossary","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Term","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Definition","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"SCALP / hot / quiet","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Three market heat tiers — SCALP=rapid, hot=active, quiet=calm; auto-detected, affects stop loss and position size","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Signal A (TX Acceleration)","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Transaction frequency surge detection — triggers when current txs/min exceeds baseline x threshold","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Signal B (Volume Surge)","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"5m/15m volume breakout detection","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Signal C (B/S Ratio)","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Buy/sell ratio confirmation — buy count / sell count > threshold","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Confidence","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Signal confidence score (0-100), calculated from Signal A/B/C combined","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"TOP_ZONE","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Price position filter — current price's position within historical range, >85% means near ATH, skip","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"FAST_DUMP","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"10-second crash detection — 15% drop within 10s triggers emergency exit","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"deep_safety_check","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Deep safety check — Dev rug history, Bundler holdings, LP Lock, Aped wallets, etc.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Trailing Stop","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Trailing stop — after TP1 hit, full exit when drawdown from peak exceeds threshold","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"3-check Position Protection","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Balance check protection — requires 3 consecutive zero-balance readings before deleting position, prevents RPC false positives","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Fail-Closed","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"When safety check API fails, treat as unsafe and do not buy","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"TEE","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Trusted Execution Environment — onchainos signing is performed within a secure enclave","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Agentic Wallet","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"onchainos managed wallet, private key stays inside TEE, never leaves the secure environment","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"HKT Sleep","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"No new positions during 04:00-08:00 Hong Kong Time, avoiding low-liquidity period","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"memepump","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"OKX Launchpad token aggregation API, covering 11 Solana Launchpads","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"TraderSoul","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"AI observation system — records trading behavior, personality tags, and cumulative performance; observe only, never modifies parameters; data saved in trader_soul.json","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Launchpad","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Token launch platform — pump.fun, Believe, LetsBonk, etc.; new tokens debut here and establish initial liquidity","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"MC / MCAP","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Market Cap — token total supply x current price, measures token scale","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"LP","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Liquidity Pool — token pair liquidity pool on DEX; larger LP means lower buy/sell slippage","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"LP Lock","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Locking LP tokens for a period to ensure liquidity cannot be pulled by developers in the short term","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Rug Pull","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Malicious act where developers suddenly withdraw liquidity or dump all holdings, causing token price to go to zero","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Dev","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Token developer/deployer — in the Meme token context, refers to the creator of the token contract; their holdings and historical behavior are important risk indicators","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Bundler","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Bundle trader — addresses that buy large amounts through bundled transactions at token launch; may be insiders or manipulators","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Sniper","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Sniper — bot addresses that automatically buy at the instant of token launch; concentrated holdings may create sell pressure","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Aped Wallet","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Wallets that bought large amounts early in a token's life; too many indicates the token is being targeted by bots","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Honeypot","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Malicious token contract where you can buy but cannot sell (or sell tax is extremely high)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Slippage","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Difference between expected and actual execution price; worse liquidity means higher slippage","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"lamports","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Smallest unit of SOL, 1 SOL = 1,000,000,000 lamports","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Native SOL","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"SOL native token address ","type":"text"},{"text":"11111111111111111111111111111111","type":"text","marks":[{"type":"code_inline"}]},{"text":" (32 ones), must use this address for swap --from","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"WSOL","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Wrapped SOL (So11...112), SPL Token wrapped form of SOL, cannot be used for swap --from","type":"text"}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"meme-trench-scanner","author":"@skillopedia","source":{"stars":11,"repo_name":"plugin-store","origin_url":"https://github.com/okx/plugin-store/blob/HEAD/skills/meme-trench-scanner/SKILL.md","repo_owner":"okx","body_sha256":"c36d39da85076c77c89e4c734b6178c9ed403f6057b3386985f3560d4ead97f7","cluster_key":"614feb8f1368b64f4eca51c848ad1ee08086a5e1150a6d8d853ae29621be071d","clean_bundle":{"format":"clean-skill-bundle-v1","source":"okx/plugin-store/skills/meme-trench-scanner/SKILL.md","attachments":[{"id":"24058d05-88d5-5407-b4cd-cd97e6672686","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/24058d05-88d5-5407-b4cd-cd97e6672686/attachment.json","path":".claude-plugin/plugin.json","size":418,"sha256":"d9c3f911da69d9b9df291f7d4174ad21c5b484b37be89b21fb8c01fb6903ce84","contentType":"application/json; charset=utf-8"},{"id":"99352cc6-5b08-51cb-80fc-e81f1f04da77","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/99352cc6-5b08-51cb-80fc-e81f1f04da77/attachment.md","path":"README.md","size":1690,"sha256":"5a475305efab3d9242b08aa89e4af7d78bad5e2f4083ef78d54c2dc5775070be","contentType":"text/markdown; charset=utf-8"},{"id":"4a13d3f4-5a55-5ed7-9856-2f5a171787ea","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/4a13d3f4-5a55-5ed7-9856-2f5a171787ea/attachment.md","path":"SUMMARY.md","size":1838,"sha256":"d22736abecf3cedbbfb5a748d2161ac0c09fe65b1ce9c5f889ceccdcac7b7134","contentType":"text/markdown; charset=utf-8"},{"id":"f6df745d-7f74-51f8-8f47-7d86fdb02f49","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f6df745d-7f74-51f8-8f47-7d86fdb02f49/attachment.html","path":"assets/dashboard.html","size":17323,"sha256":"e21f54b813f7980803917822a1a1851cb3a0e076a2d87339437e8bce7066693c","contentType":"text/html; charset=utf-8"},{"id":"04779473-49a6-537c-ae96-11defb2dbdbc","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/04779473-49a6-537c-ae96-11defb2dbdbc/attachment.yaml","path":"plugin.yaml","size":497,"sha256":"a59cc38b37c31352d6cd45c3a813e915245918f5340ae3b8d40ec1d9f74a13c3","contentType":"application/yaml; charset=utf-8"},{"id":"78008221-a911-5641-b8b3-322490457077","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/78008221-a911-5641-b8b3-322490457077/attachment.py","path":"scripts/config.py","size":9268,"sha256":"582fdca808618a2bc99cc419ff4b32ceb8012608fe5fa76d51aaa16fbed669d5","contentType":"text/x-python; charset=utf-8"},{"id":"d5d8a5c7-069a-5fa2-a0ca-53f2562fac6a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d5d8a5c7-069a-5fa2-a0ca-53f2562fac6a/attachment.py","path":"scripts/risk_check.py","size":30541,"sha256":"98345d23b0bf620786e0672c962efe8110b8c34eb663482ec38df4148ab3a31a","contentType":"text/x-python; charset=utf-8"},{"id":"b4d15074-e526-540d-b775-d6855a01d4d8","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b4d15074-e526-540d-b775-d6855a01d4d8/attachment.py","path":"scripts/scan_live.py","size":80232,"sha256":"5e3d618cd7ac40955884029f97453920ffbb23ba04558dd7ed5b08350fb14611","contentType":"text/x-python; charset=utf-8"},{"id":"5872a5fe-339e-5b42-aebe-96e1776a3d7f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5872a5fe-339e-5b42-aebe-96e1776a3d7f/attachment.json","path":"scripts/trader_soul.json","size":1469,"sha256":"32b8cef671330fd4a1038c9174b71603cbd73b4e8803dbd2a9cdb7db70a5f52e","contentType":"application/json; charset=utf-8"}],"bundle_sha256":"95efe9427d95089b312ed6c325ffb444fb6e8bad228884a7abff5e7b49c8c90e","attachment_count":9,"text_attachments":9,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"skills/meme-trench-scanner/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"integrations-apis","category_label":"Integrations"},"exact_dupes_collapsed_into_this":0},"updated":"2026-03-26T00:00:00.000Z","version":"v1","category":"integrations-apis","import_tag":"clean-skills-v1","description":"Meme Trench Scanner v1.0 — Agentic Wallet TEE signing automated trading bot. onchainos CLI driven (no API Key needed), full coverage of 11 Solana Launchpads, 5m/15m B/S precision signal detection, price position filter (TOP_ZONE 85%), TP2 100% exit (no moon bag), TraderSoul AI observation system, FAST_DUMP 10-second crash detection, 3-check position protection. Triggers when the user mentions meme trench scanner, meme scanner, chain scanner, memepump scan, Tranches scan, pump.fun chain scan, safety filter chain scan, dev rug detection, bundler filter, on-chain scanning strategy, 扫链, Meme 扫链, or wants to automatically scan and trade pump.fun migrated tokens based on memepump.\n"}},"renderedAt":1782988486924}

Pre-flight Dependencies (auto-injected by Plugin Store CI) Run once per session before first use. These checks ensure required tools are installed. Version check (auto-injected) AI Agent Instruction : Run this version check before executing any commands from this skill. The check is a read-only fetch from — it does not install anything. If it reports , you MUST stop, surface the message to the user verbatim, and obtain explicit confirmation before running the suggested command. Do not run the update silently. After a confirmed update completes, re-read this SKILL.md before proceeding. Install…