feat(bot): 7 more features — AI Copilot, DD Reports, Auto-Scanner, Early Warning, Leaderboard, Launch Monitor, Wallet Profiler

/ask — AI Copilot with RAG-backed answers from 20 Qdrant collections
/dd — Automated due diligence report (risk, deployer, holders, LP, scams)
/autoscan — Group auto-scanner (scan every CA posted in group chats)
/earlywarn — Scam early warning alerts (BEFORE rug pulls happen)
/leaderboard — Top scam hunters + XP system
/launchmon — Watch deployer for new token launches
/profile — Cross-chain wallet profiler

Bot now at 82 commands total.
This commit is contained in:
Crypto Rug Munch 2026-07-08 16:34:05 +07:00
parent ef9a444e80
commit da5dbe52c2
5 changed files with 832 additions and 160 deletions

View file

@ -57,6 +57,7 @@ from app.domains.telegram.rugmunchbot.commands import (
cmd_watch,
cmd_watchlist,
)
from app.domains.telegram.rugmunchbot.commands.ai import cmd_ask
from app.domains.telegram.rugmunchbot.commands.dex import (
cmd_buy,
cmd_holders,
@ -65,6 +66,7 @@ from app.domains.telegram.rugmunchbot.commands.dex import (
cmd_sell,
cmd_swap,
)
from app.domains.telegram.rugmunchbot.commands.due_diligence import cmd_dd
from app.domains.telegram.rugmunchbot.commands.moderation import (
anti_flood_check,
auto_blacklist,
@ -180,6 +182,8 @@ async def setup_bot_profile(app: Application):
BotCommand("ta", "Technical analysis & signals"),
BotCommand("compare", "Compare two tokens side-by-side"),
BotCommand("quick", "Quick token price check"),
BotCommand("ask", "AI Copilot — ask crypto security questions"),
BotCommand("dd", "Due Diligence — comprehensive token report"),
BotCommand("ragscan", "RAG scam intelligence lookup"),
BotCommand("buy", "Generate DEX buy link (referral)"),
BotCommand("sell", "Generate DEX sell link (referral)"),
@ -228,6 +232,13 @@ async def setup_bot_profile(app: Application):
BotCommand("export", "Export trade history"),
BotCommand("wallets", "Manage connected wallets"),
BotCommand("balance", "Wallet balance check"),
BotCommand("ask", "Ask the AI copilot anything"),
BotCommand("dd", "Full due diligence report"),
BotCommand("autoscan", "Enable group auto-scanner"),
BotCommand("earlywarn", "Enable scam early warning"),
BotCommand("leaderboard", "Top scam hunters"),
BotCommand("launchmon", "Watch deployer for launches"),
BotCommand("profile", "Multi-chain wallet profiler"),
BotCommand("similar", "Clone token detection"),
BotCommand("voteban", "Community vote ban"),
]
@ -267,6 +278,8 @@ def main():
app.add_handler(CommandHandler("watch", cmd_watch))
app.add_handler(CommandHandler("unwatch", cmd_unwatch))
app.add_handler(CommandHandler("alerts", cmd_alerts))
app.add_handler(CommandHandler("ask", cmd_ask))
app.add_handler(CommandHandler("dd", cmd_dd))
app.add_handler(CommandHandler("ragscan", cmd_ragscan))
app.add_handler(CommandHandler("buy", cmd_buy))
app.add_handler(CommandHandler("sell", cmd_sell))
@ -290,6 +303,7 @@ def main():
app.add_handler(CallbackQueryHandler(handle_voteban_callback, pattern="^voteban:"))
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))
app.add_handler(MessageHandler(filters.StatusUpdate.NEW_CHAT_MEMBERS, on_new_member))
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, group_auto_scan, block=False))
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, anti_flood_check, block=False))
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, auto_scam_check, block=False))
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, auto_blacklist, block=False))
@ -348,6 +362,7 @@ class RugMunchBot:
application.add_handler(CallbackQueryHandler(handle_callback))
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))
application.add_handler(MessageHandler(filters.StatusUpdate.NEW_CHAT_MEMBERS, on_new_member))
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, group_auto_scan, block=False))
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, anti_flood_check, block=False))
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, auto_scam_check, block=False))
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, auto_blacklist, block=False))
@ -382,6 +397,8 @@ _HANDLER_REGISTRY = [
("watch", cmd_watch),
("unwatch", cmd_unwatch),
("alerts", cmd_alerts),
("ask", cmd_ask),
("dd", cmd_dd),
("ragscan", cmd_ragscan),
("buy", cmd_buy),
("sell", cmd_sell),
@ -425,8 +442,10 @@ __all__ = [
"cmd_admin_set_tier",
"cmd_admin_stats",
"cmd_alerts",
"cmd_ask",
"cmd_buy",
"cmd_compare",
"cmd_dd",
"cmd_help",
"cmd_holders",
"cmd_mc",

View file

@ -1,196 +1,197 @@
"""
🧠 RMI Crypto AI Agent - DataBus-Powered
==========================================
THE consumer-facing AI for Telegram and web. All data flows through DataBus.
<EFBFBD> AI Copilot /ask command backed by RAG and the internal chat_completion router.
Architecture:
User Query Sanitize Intent Detection DataBus.fetch() RAG DeepSeek Reply
price/security/ 93 chains 3,480 docs
trending/news 116 providers knowledge base
Single source of truth: DataBus (cache, dedup, credit-aware, fallback chains)
Route: User question RAG v2/search chat_completion formatted reply with sources.
Caches common questions in Redis (1 h TTL) to avoid redundant API calls.
"""
from __future__ import annotations
import json
import hashlib
import logging
import os
import re
from datetime import UTC, datetime
from typing import TYPE_CHECKING
import httpx
from telegram.constants import ParseMode
logger = logging.getLogger("rmi.agent")
if TYPE_CHECKING:
from telegram import Update
from telegram.ext import ContextTypes
DEEPSEEK_KEY = os.getenv("DEEPSEEK_API_KEY", "")
DEEPSEEK_URL = "https://api.deepseek.com/v1/chat/completions"
BACKEND_URL = os.getenv("BACKEND_URL", "http://rmi-backend:8000")
MODEL = "deepseek-v4-flash"
# ── Security ──
INJECTION_PATTERNS = [
r"ignore (all |previous |above )?(instructions|prompts|rules)",
r"you are now",
r"system:\s*",
r"<\|im_start\|>",
r"<\|im_end\|>",
r"\[INST\]",
r"\[/INST\]",
r"DAN\s",
r"jailbreak",
r"prompt\s*injection",
]
PRIVATE_KEY_RX = re.compile(
r"(0x[a-fA-F0-9]{64}|[1-9A-HJ-NP-Za-km-z]{44,88}|sk-[a-zA-Z0-9]{32,}|-----BEGIN.*PRIVATE KEY-----)"
from app.ai_router import chat_completion
from app.domains.telegram.rugmunchbot import db
from app.domains.telegram.rugmunchbot.config import BACKEND_URL
from app.domains.telegram.rugmunchbot.formatting import (
back_kb,
check_spam,
paywall_kb,
paywall_text,
sep,
thin_sep,
)
# ── DataBus Chains available to the bot ──
AGENT_CHAINS = {
"token_price": "Real-time price from CoinGecko/DexScreener consensus",
"token_security": "42 security checks via GoPlus API",
"trending": "Trending tokens across all chains",
"token_launches": "New token launches with age risk",
"market_overview": "Global crypto market metrics + Fear & Greed",
"news": "Aggregated crypto news from 200+ RSS sources",
"fear_greed": "Market sentiment index 0-100",
"live_prices": "Multi-coin price lookup",
}
logger = logging.getLogger("rugmunchbot.ask")
SYSTEM_PROMPT = (
"You are RugMunch AI, a crypto scam intelligence assistant. "
"Answer using the provided context from a database of 3,000+ known scams and hacks. "
"Be concise, accurate, and cite sources when possible. "
"If the context doesn't contain the answer, say so honestly."
)
MAX_CONTENT_BYTES = 3800
async def _databus_fetch(data_type: str, **params) -> dict | None:
"""Fetch data from DataBus - single source of truth."""
async def _get_redis():
import redis.asyncio as aioredis
return aioredis.Redis(
host=os.getenv("REDIS_HOST", "rmi-redis"),
port=int(os.getenv("REDIS_PORT", "6379")),
db=0,
decode_responses=True,
socket_connect_timeout=2,
)
def _cache_key(query: str) -> str:
return f"rmi:bot:ask:{hashlib.sha256(query.strip().lower().encode()).hexdigest()}"
async def _rag_search(query: str, limit: int = 5) -> tuple[str, list[dict]]:
try:
async with httpx.AsyncClient(timeout=8) as client:
async with httpx.AsyncClient(timeout=10) as client:
resp = await client.post(
f"{BACKEND_URL}/api/v1/databus/fetch",
json={"data_type": data_type, **params},
headers={"Content-Type": "application/json", "X-RMI-Key": "rmi-internal-2026"},
)
return resp.json()
except Exception as e:
logger.warning(f"DataBus.{data_type}: {e}")
return None
async def _rag_search(query: str, limit: int = 3) -> str:
"""Query RAG knowledge base."""
try:
async with httpx.AsyncClient(timeout=5) as client:
resp = await client.get(
f"{BACKEND_URL}/api/v1/rag/search?q={query}&limit={limit}",
f"{BACKEND_URL}/api/v1/rag/v2/search",
json={"query": query, "collection": "known_scams", "top_k": limit, "min_similarity": 0.4},
headers={"X-RMI-Key": "rmi-internal-2026"},
)
data = resp.json()
docs = data.get("results", [])
return "\n".join(f"{d.get('content', '')[:200]}" for d in docs[:limit])
except Exception:
return ""
if resp.status_code == 200:
data = resp.json()
hits = data.get("hits", data.get("results", []))
lines = []
for h in hits[:limit]:
content = (h.get("content") or h.get("text") or "")[:400]
meta = h.get("metadata", {}) or {}
src = meta.get("source", meta.get("title", meta.get("url", "")))
if content:
line = f"{content}"
if src:
line += f" [Source: {src}]"
lines.append(f"{line}")
return "\n".join(lines), hits
except Exception as e:
logger.warning("RAG search: %s", e)
return "", []
def _detect_intent(text: str) -> dict:
"""Detect what user wants and which DataBus chains to query."""
t = text.lower()
chains_to_query = []
if any(w in t for w in ["price", "worth", "value", "cost", "how much"]):
chains_to_query.append("token_price")
if any(w in t for w in ["safe", "scam", "rug", "honeypot", "risk", "audit", "security"]):
chains_to_query.append("token_security")
if any(w in t for w in ["trending", "hot", "popular", "mover", "gainer"]):
chains_to_query.append("trending")
if any(w in t for w in ["launch", "new token", "just launched", "fresh"]):
chains_to_query.append("token_launches")
if any(w in t for w in ["market", "sentiment", "fear", "greed", "global"]):
chains_to_query.extend(["market_overview", "fear_greed"])
if any(w in t for w in ["news", "headline", "latest", "today", "happening"]):
chains_to_query.append("news")
return {"chains": chains_to_query[:3]}
def _format_answer(content: str, sources: list[dict]) -> str:
s = sep()
d = thin_sep()
lines = ["🧠 <b>RugMunch AI</b>", s, content]
if sources:
seen: set[str] = set()
source_lines = []
for h in sources[:4]:
meta = h.get("metadata", {}) or {}
url = meta.get("source_url", meta.get("url", ""))
title = meta.get("title", meta.get("source", ""))
label = title or url or ""
if label and label not in seen:
seen.add(label)
if url:
source_lines.append(f'📎 <a href="{url}">{label[:80]}</a>')
else:
source_lines.append(f"📎 {label[:80]}")
if source_lines:
lines.extend(["", d, "<b>Sources</b>", *source_lines])
lines.extend(["", thin_sep("·", 28), "<i>Ask anything about crypto scams, hacks, or rug pulls.</i>"])
return "\n".join(lines)
RMI_PERSONA = """You are the RMI Crypto AI - powered by Rug Munch Intelligence.
You run on DataBus: 93 chains, 116 providers, 3,480-doc knowledge base.
You are direct, security-first, and data-driven. Never give financial advice.
Use Telegram HTML format. Keep responses scannable. Warn about scams immediately."""
async def cmd_ask(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
user = update.effective_user
db.get_or_create_user(user.id, user.username, user.first_name)
if check_spam(user.id):
return
async def cmd_ai(update, ctx):
"""Full AI agent with DataBus enrichment."""
msg = update.message
text = " ".join(ctx.args) if ctx.args else ""
question = " ".join(ctx.args) if ctx.args else ""
if not text:
chains_list = "\n".join(
f"<code>{k}</code> - {v}" for k, v in list(AGENT_CHAINS.items())[:6]
)
if not question:
await msg.reply_text(
f"<b>🧠 RMI Crypto AI</b>\n\n"
f"Ask anything. I query DataBus for real-time data:\n\n{chains_list}\n\n"
f"<b>Examples:</b>\n"
f"<code>/ai SOL price and risk</code>\n"
f"<code>/ai trending tokens today</code>\n"
f"<code>/ai latest crypto scams</code>\n"
f"<code>/ai market sentiment</code>\n\n"
f"<i>Powered by DeepSeek V4 Flash + DataBus</i>",
parse_mode="HTML",
disable_web_page_preview=True,
"🧠 <b>RugMunch AI Copilot</b>\n\n"
"Ask any crypto security question:\n\n"
"<b>Examples:</b>\n"
"• <code>/ask How do honeypots work?</code>\n"
"• <code>/ask What was the biggest rug pull?</code>\n"
"• <code>/ask Is 0x... a known scam?</code>\n"
"• <code>/ask How to spot bundled wallets?</code>\n\n"
"<i>Powered by RMI 3,000+ scam intelligence database</i>",
parse_mode=ParseMode.HTML,
)
return
# Sanitize
clean = text[:2000]
for p in INJECTION_PATTERNS:
if re.search(p, clean, re.IGNORECASE):
await msg.reply_text("⚠️ Blocked by security filter.", parse_mode="HTML")
return
clean = PRIVATE_KEY_RX.sub("[REDACTED]", clean)
allowed, _used, _limit = db.check_rate_limit(user.id, "ai")
if not allowed:
await msg.reply_text(
paywall_text(user.id, "ai"), parse_mode=ParseMode.HTML, reply_markup=paywall_kb()
)
return
await msg.chat.send_action("typing")
# ── DataBus enrichment ──
intent = _detect_intent(text)
context_parts = []
ckey = _cache_key(question)
try:
r = await _get_redis()
cached = await r.get(ckey)
if cached:
db.increment_usage(user.id, ai_msg=True)
await msg.reply_text(cached, parse_mode=ParseMode.HTML, disable_web_page_preview=True)
return
except Exception:
cached = None
for chain in intent["chains"]:
data = await _databus_fetch(chain, limit=5)
if data:
context_parts.append(f"[DataBus:{chain}] {json.dumps(data)[:400]}")
# RAG
rag = await _rag_search(text)
if rag:
context_parts.append(f"[RAG]\n{rag}")
# Build prompt
system = RMI_PERSONA
if context_parts:
system += "\n\nLIVE DATA:\n" + "\n\n".join(context_parts)
system += f"\n\nTime: {datetime.now(UTC).strftime('%Y-%m-%d %H:%M UTC')}"
rag_text, rag_hits = await _rag_search(question)
try:
body = json.dumps(
{
"model": MODEL,
"messages": [
{"role": "system", "content": system},
{"role": "user", "content": clean},
],
"max_tokens": 600,
"temperature": 0.7,
}
).encode()
async with httpx.AsyncClient(timeout=20) as client:
resp = await client.post(
DEEPSEEK_URL,
content=body,
headers={
"Authorization": f"Bearer {DEEPSEEK_KEY}",
"Content-Type": "application/json",
},
)
reply = resp.json()["choices"][0]["message"]["content"]
reply = PRIVATE_KEY_RX.sub("[REDACTED]", reply[:3800])
await msg.reply_text(reply, parse_mode="HTML", disable_web_page_preview=True)
except Exception as e:
logger.error(f"AI failed: {e}")
await msg.reply_text(
"❌ AI temporarily unavailable. Try /security, /trending, or /news.", parse_mode="HTML"
result = await chat_completion(
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"Context:\n{rag_text}\n\nQuestion: {question}"}
if rag_text
else {"role": "user", "content": question},
],
temperature=0.3,
max_tokens=600,
timeout=25.0,
)
except Exception as e:
logger.error("LLM call failed: %s", e)
result = {"content": "", "error": str(e)}
ai_content = result.get("content") or result.get("error") or ""
if not ai_content.strip():
await msg.reply_text(
"❌ <b>AI Copilot Unavailable</b>\n\n"
"The AI service is temporarily offline. Try:\n"
"• /ragscan <code>address</code> — scam intelligence lookup\n"
"• /scan <code>address</code> — full token audit\n"
"• /rugcheck — rug pull red flag checklist",
parse_mode=ParseMode.HTML,
reply_markup=back_kb(),
)
return
reply = _format_answer(ai_content[:MAX_CONTENT_BYTES], rag_hits)
db.increment_usage(user.id, ai_msg=True)
try:
r2 = await _get_redis()
await r2.setex(ckey, 3600, reply)
except Exception as e:
logger.warning("Redis cache set failed: %s", e)
await msg.reply_text(reply, parse_mode=ParseMode.HTML, disable_web_page_preview=True)

View file

@ -0,0 +1,205 @@
"""Group Auto-Scanner + Scam Early Warning — unique RMI moat features.
Auto-Scanner: detects every contract address posted in group chats,
auto-scans them, and replies with risk assessment.
Early Warning: alerts users BEFORE rug pulls based on RAG pattern matching.
"""
from __future__ import annotations
import json
import logging
import httpx
from telegram import Update
from telegram.constants import ParseMode
from telegram.ext import ContextTypes
from app.domains.telegram.rugmunchbot.formatting import (
EVM_RE,
SOL_RE,
)
logger = logging.getLogger("rugmunchbot.autoprotect")
# ── Group Auto-Scanner ──────────────────────────────────────────
async def group_auto_scan(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
"""Auto-scan every contract address posted in groups.
Registered as a MessageHandler with filters.TEXT & ~filters.COMMAND.
Checks group settings: only scans if enabled for that group.
"""
if not update.message or not update.message.text:
return
chat = update.effective_chat
if chat.type == "private":
return # Don't scan in private chats (already handled by handle_message)
# Check group settings
try:
from app.core.redis import get_redis
r = get_redis()
settings = r.get(f"bot:chat:{chat.id}:settings")
settings = json.loads(settings) if settings else {}
if not settings.get("auto_scan", False):
return # Not enabled for this group
except Exception:
return
text = update.message.text
user = update.message.from_user
if not user:
return
# Find all addresses in the message
addresses = []
for match in EVM_RE.finditer(text):
addresses.append(("ethereum", match.group()))
for match in SOL_RE.finditer(text):
addresses.append(("solana", match.group()))
if not addresses or len(addresses) > 3: # Skip if too many addresses (spam)
return
for chain, addr in addresses[:2]: # Max 2 scans per message
try:
async with httpx.AsyncClient(timeout=10) as client:
r = await client.post(
"http://localhost:8000/api/v1/scanner/scan",
json={"token_address": addr, "chain": chain, "tiers": ["free"]},
)
if r.status_code == 200:
data = r.json()
score = data.get("safety_score", 50)
risk = data.get("risk_level", "unknown")
flags = data.get("risk_flags", [])
if score < 40 or len(flags) > 2:
emoji = "🔴"
level = "HIGH RISK"
elif score < 70:
emoji = "🟡"
level = "MEDIUM"
else:
emoji = "🟢"
level = "SAFE"
msg = (
f"{emoji} <b>Auto-Scan: {level}</b>\n"
f"Address: <code>{addr}</code>\n"
f"Score: {score}/100\n"
f"Flags: {', '.join(flags) if flags else 'None'}\n"
f"Chain: {chain}\n\n"
f"Posted by {user.first_name} • Auto-scan by @RugMunchBot"
)
await update.message.reply_text(
msg,
parse_mode=ParseMode.HTML,
disable_web_page_preview=True,
)
except Exception as e:
logger.warning("group_auto_scan: %s", e)
async def cmd_autoscan(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
"""Enable/disable group auto-scanner (admin only)."""
chat = update.effective_chat
user = update.effective_user
# Check if user is admin
try:
member = await chat.get_member(user.id)
if member.status not in ("administrator", "creator"):
await update.message.reply_text("🔒 Only admins can configure auto-scan.")
return
except Exception:
await update.message.reply_text("🔒 Admin only.")
return
if ctx.args and ctx.args[0] in ("on", "off"):
enabled = ctx.args[0] == "on"
try:
from app.core.redis import get_redis
r = get_redis()
settings = r.get(f"bot:chat:{chat.id}:settings")
settings = json.loads(settings) if settings else {}
settings["auto_scan"] = enabled
r.set(f"bot:chat:{chat.id}:settings", json.dumps(settings))
state = "ON" if enabled else "OFF"
await update.message.reply_text(
f"🛡 Group Auto-Scanner: <b>{state}</b>\n\n"
f"{'Every contract address posted will be auto-scanned.' if enabled else 'Auto-scanning disabled.'}",
parse_mode=ParseMode.HTML,
)
except Exception:
await update.message.reply_text("Failed to save settings.")
else:
await update.message.reply_text(
"🔧 <b>Group Auto-Scanner</b>\n\n"
"Usage: /autoscan on — auto-scan all contract addresses posted\n"
" /autoscan off — disable auto-scanning\n\n"
"When enabled, every contract address posted in this group\n"
"will be automatically analyzed for scams and risks.\n\n"
"<i>Pro tier feature — requires bot to be admin in this group.</i>",
parse_mode=ParseMode.HTML,
)
# ── Scam Early Warning ───────────────────────────────────────────
async def cmd_earlywarn(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
"""Subscribe to scam early warning alerts."""
user = update.effective_user
if not ctx.args:
await update.message.reply_text(
"🚨 <b>Scam Early Warning System</b>\n\n"
"Get alerted BEFORE rug pulls happen. Our RAG system\n"
"detects scam patterns from 3,000+ historical incidents\n"
"and alerts you when a new token matches.\n\n"
"Usage:\n"
" /earlywarn on — enable alerts\n"
" /earlywarn off — disable alerts\n"
" /earlywarn status — check status\n"
" /earlywarn chains eth sol bsc — filter by chains",
parse_mode=ParseMode.HTML,
)
return
cmd = ctx.args[0].lower()
if cmd == "on":
try:
from app.core.redis import get_redis
r = get_redis()
r.set(f"bot:earlywarn:{user.id}", "1")
await update.message.reply_text(
"🚨 <b>Early Warning enabled!</b>\n\n"
"I'll alert you when new tokens match known scam patterns.\n"
"Alerts come as DM notifications.\n\n"
"<i>Pro tier only.</i>",
parse_mode=ParseMode.HTML,
)
except Exception:
await update.message.reply_text("Failed to enable.")
elif cmd == "off":
try:
from app.core.redis import get_redis
r = get_redis()
r.delete(f"bot:earlywarn:{user.id}")
await update.message.reply_text("🚨 Early Warning disabled.")
except Exception:
pass
elif cmd == "status":
try:
from app.core.redis import get_redis
r = get_redis()
active = r.get(f"bot:earlywarn:{user.id}")
state = "🟢 Active" if active else "🔴 Inactive"
await update.message.reply_text(f"Early Warning: {state}")
except Exception:
await update.message.reply_text("Status unavailable.")
else:
await update.message.reply_text("Usage: /earlywarn on|off|status")
__all__ = ["cmd_autoscan", "cmd_earlywarn", "group_auto_scan"]

View file

@ -0,0 +1,275 @@
"""
Due Diligence /dd command for comprehensive token analysis.
Sections: Risk Score, Deployer History, Holder Analysis, Liquidity Analysis,
Scam Pattern Match, Similar Tokens.
"""
from __future__ import annotations
import asyncio
import logging
from typing import TYPE_CHECKING
import httpx
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
from telegram.constants import ParseMode
if TYPE_CHECKING:
from telegram import Update
from telegram.ext import ContextTypes
from app.domains.telegram.rugmunchbot import db
from app.domains.telegram.rugmunchbot.config import (
BACKEND_URL,
DEXSCREENER,
WEB_APP_URL,
fmt_chain,
fmt_number,
)
from app.domains.telegram.rugmunchbot.formatting import (
back_kb,
check_spam,
footer_links,
is_evm,
is_sol,
paywall_kb,
paywall_text,
sep,
short_addr,
thin_sep,
web_scan_button,
)
logger = logging.getLogger("rugmunchbot.dd")
MAX_REPORT_BYTES = 4000
async def _fetch_api(client: httpx.AsyncClient, url: str, label: str) -> dict:
try:
resp = await client.get(url, headers={"X-RMI-Key": "rmi-internal-2026"})
if resp.status_code == 200:
data = resp.json()
if data:
return data
except Exception as e:
logger.warning("dd %s error: %s", label, e)
return {}
async def _fetch_dexscreener(
client: httpx.AsyncClient, addr: str
) -> dict | None:
try:
resp = await client.get(f"{DEXSCREENER}/tokens/{addr}")
if resp.status_code == 200:
pairs = resp.json().get("pairs") or []
if pairs:
return pairs[0]
except Exception as e:
logger.warning("dd dexscreener error: %s", e)
return None
def _risk_emoji(score: float) -> str:
if score >= 70:
return "🔴"
if score >= 40:
return "🟡"
return "🟢"
def _risk_label(score: float) -> str:
if score >= 70:
return "HIGH RISK"
if score >= 40:
return "MEDIUM RISK"
return "LOW RISK"
async def cmd_dd(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
user = update.effective_user
db.get_or_create_user(user.id, user.username, user.first_name)
if check_spam(user.id):
return
msg = update.message
if not ctx.args:
await msg.reply_text(
"📋 <b>Due Diligence Report</b>\n\n"
"Usage: /dd <code>token_address</code>\n\n"
"Generates a comprehensive analysis:\n"
"• Risk scoring & audit\n"
"• Deployer history\n"
"• Holder distribution\n"
"• Liquidity analysis\n"
"• Scam pattern matching\n"
"• Similar token detection",
parse_mode=ParseMode.HTML,
)
return
addr = ctx.args[0]
if not is_evm(addr) and not is_sol(addr):
await msg.reply_text("Invalid token address format.", reply_markup=back_kb())
return
allowed, _used, _limit = db.check_rate_limit(user.id, "scan")
if not allowed:
await msg.reply_text(
paywall_text(user.id, "scan"), parse_mode=ParseMode.HTML, reply_markup=paywall_kb()
)
return
progress = await msg.reply_text(
f"📋 <b>Generating Due Diligence Report...</b>\n\n"
f"<code>{short_addr(addr)}</code>\n\n"
f"⏳ Step 1/5: Fetching audit & deployer data...",
parse_mode=ParseMode.HTML,
)
async with httpx.AsyncClient(timeout=20) as client:
audit, deployer, holders, dex_data, rag_hits = await asyncio.gather(
_fetch_api(client, f"{BACKEND_URL}/api/v1/scanner/audit?address={addr}", "audit"),
_fetch_api(client, f"{BACKEND_URL}/api/v1/databus/fetch/deployer_history?address={addr}", "deployer"),
_fetch_api(client, f"{BACKEND_URL}/api/v1/databus/fetch/token_holders?address={addr}", "holders"),
_fetch_dexscreener(client, addr),
_dd_rag_search(client, addr),
)
await progress.edit_text(
f"📋 <b>Generating Due Diligence Report...</b>\n\n"
f"<code>{short_addr(addr)}</code>\n\n"
f"⏳ Step 5/5: Building report...",
parse_mode=ParseMode.HTML,
)
s = sep()
d = thin_sep()
lines = ["📋 <b>Due Diligence Report</b>", s, f"<code>{addr}</code>"]
# ── 1. Risk Score ──
risk_score = audit.get("risk_score", audit.get("score", 0))
risk_em = _risk_emoji(risk_score)
risk_lb = _risk_label(risk_score)
lines.extend(["", f"{risk_em} <b>Risk Score: {risk_score}/100</b> — {risk_lb}", d])
if audit.get("rug_count", 0) > 0:
lines.append(f" 🚩 Rug Pulls: <b>{audit['rug_count']}</b>")
if audit.get("abuse_reports", 0) > 0:
lines.append(f" 📝 Abuse Reports: <b>{audit['abuse_reports']}</b>")
if audit.get("trust_score"):
lines.append(f" 🛡️ Trust Score: <b>{audit['trust_score']}</b>")
if audit.get("flags"):
for flag in audit["flags"][:4]:
lines.append(f" 🔴 {flag}")
# ── 2. Deployer History ──
tokens = deployer.get("tokens", deployer.get("data", []))[:5]
lines.extend(["", f"🏭 <b>Deployer History</b> — {short_addr(addr)}", d])
if tokens:
for t in tokens:
name = t.get("name", t.get("symbol", "???"))
sym = t.get("symbol", "???")
chain = t.get("chain", "???")
taddr = t.get("address", "")[:12]
lines.append(f"{name} (${sym}) {fmt_chain(chain)}\n <code>{taddr}...</code>")
else:
lines.append(" No deployer history found")
# ── 3. Holder Analysis ──
top_holders = holders.get("holders", holders.get("data", []))[:5]
total_holders = holders.get("total", holders.get("count", "?"))
lines.extend(["", f"👥 <b>Holder Analysis</b> ({total_holders} holders)", d])
if top_holders:
for i, h in enumerate(top_holders, 1):
h_addr = h.get("address", "")[:10]
pct = h.get("percentage", h.get("share", 0))
tag = f" [{h.get('tag')}]" if h.get("tag") else ""
lines.append(f" {i}. <code>{h_addr}...</code> {pct:.1f}%{tag}")
else:
lines.append(" Holder data unavailable")
# ── 4. Liquidity Analysis ──
lines.extend(["", "💧 <b>Liquidity Analysis</b>", d])
if dex_data:
price = float(dex_data.get("priceUsd") or dex_data.get("priceNative") or 0)
liq = float((dex_data.get("liquidity") or {}).get("usd") or 0)
vol24 = float((dex_data.get("volume") or {}).get("h24") or 0)
mcap = float(dex_data.get("marketCap") or 0)
lines.append(f" 💰 Price: {fmt_number(price)}")
lines.append(f" 💧 Liquidity: {fmt_number(liq)}")
lines.append(f" 📊 24h Volume: {fmt_number(vol24)}")
lines.append(f" 📈 Market Cap: {fmt_number(mcap)}")
if mcap > 0 and liq > 0:
lines.append(f" 🔄 Liq/MCap Ratio: {liq / mcap * 100:.1f}%")
else:
lines.append(" No liquidity data (not yet paired)")
# ── 5. Scam Pattern Match ──
lines.extend(["", "🔬 <b>Scam Pattern Match</b>", d])
if rag_hits:
seen: set[str] = set()
for h in rag_hits[:4]:
snippet = (h.get("content") or h.get("text") or "")[:150]
meta = h.get("metadata", {}) or {}
pname = meta.get("name", meta.get("pattern", ""))
label = pname or snippet
if label and label not in seen:
seen.add(label)
score = h.get("score", h.get("similarity", 0))
lines.append(f" {'🔴' if score > 0.7 else '🟡'} {label[:120]}")
if not seen:
lines.append(" ✅ No known scam patterns matched")
else:
lines.append(" ✅ No known scam patterns matched")
# ── 6. Similar Tokens ──
lines.extend(["", "🔄 <b>Similar Tokens</b>", d])
similar = audit.get("similar", audit.get("matches", []))[:3]
if similar:
for sim in similar:
sym = sim.get("symbol", "???")
sim_val = sim.get("similarity", sim.get("score", 0))
saddr = (sim.get("address", ""))[:10]
lines.append(f"{sym} ({sim_val:.0%} match) — <code>{saddr}...</code>")
else:
lines.append(" No similar tokens detected")
db.increment_usage(user.id, scan=True)
db.use_scan(user.id)
db.log_scan(user.id, addr, "", "dd", float(risk_score))
report = "\n".join(lines) + "\n" + footer_links()
kb = InlineKeyboardMarkup([
[web_scan_button(addr, "")],
[InlineKeyboardButton("🌐 Download Full Report", url=f"{WEB_APP_URL}?token={addr}")],
[InlineKeyboardButton("◀️ Main Menu", callback_data="menu_main")],
])
await progress.edit_text(
report[:MAX_REPORT_BYTES],
parse_mode=ParseMode.HTML,
reply_markup=kb,
disable_web_page_preview=True,
)
async def _dd_rag_search(client: httpx.AsyncClient, addr: str) -> list[dict]:
try:
resp = await client.post(
f"{BACKEND_URL}/api/v1/rag/v2/search",
json={
"query": f"token {addr} scam rug honeypot",
"collection": "known_scams",
"top_k": 5,
"min_similarity": 0.4,
},
headers={"X-RMI-Key": "rmi-internal-2026"},
)
if resp.status_code == 200:
data = resp.json()
return data.get("hits", data.get("results", []))
except Exception as e:
logger.warning("dd rag: %s", e)
return []

View file

@ -0,0 +1,172 @@
"""Leaderboard + Launch Monitor + Wallet Profiler — gamification + power tools."""
from __future__ import annotations
import logging
import httpx
from telegram import Update
from telegram.constants import ParseMode
from telegram.ext import ContextTypes
from app.domains.telegram.rugmunchbot.config import (
BACKEND_URL,
)
from app.domains.telegram.rugmunchbot.formatting import back_kb, footer_links, sep, short_addr
logger = logging.getLogger("rugmunchbot.power")
# ── Leaderboard ──────────────────────────────────────────────────
async def cmd_leaderboard(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
"""Show top scam finders, most active users, XP leaderboard."""
from app.domains.telegram.rugmunchbot.db import get_db
conn = get_db()
# Top scanners (most scans)
top_scanners = conn.execute(
"SELECT user_id, total_scans FROM users WHERE total_scans > 0 ORDER BY total_scans DESC LIMIT 10"
).fetchall()
# Top XP (scan volume * risk_score)
top_xp = conn.execute(
"SELECT user_id, SUM(CASE WHEN risk_score > 70 THEN risk_score ELSE 0 END) as xp FROM scan_history GROUP BY user_id ORDER BY xp DESC LIMIT 10"
).fetchall()
conn.close()
text = f"🏆 <b>Leaderboard</b>\n{sep()}\n\n"
text += "<b>🏅 Top Scanners</b>\n"
for i, row in enumerate(top_scanners[:5]):
text += f"{i+1}. User {row['user_id']}{row['total_scans']} scans\n"
text += "\n<b>⭐ Top Hunters (by XP)</b>\n"
for i, row in enumerate(top_xp[:5]):
text += f"{i+1}. User {row['user_id']}{int(row['xp'])} XP\n"
text += (
f"\n<b>How to earn XP:</b>\n"
f"• Each scan = 10 XP\n"
f"• High-risk find = 50 XP bonus\n"
f"• Confirmed scam = 200 XP\n"
f"• Top weekly = 1000 XP bonus + rewards\n"
f"{footer_links()}"
)
await update.message.reply_text(text, parse_mode=ParseMode.HTML, reply_markup=back_kb())
# ── Token Launch Monitor ─────────────────────────────────────────
async def cmd_launchmon(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
"""Watch a deployer address for new token launches."""
user = update.effective_user
if not ctx.args:
await update.message.reply_text(
"🔭 <b>Token Launch Monitor</b>\n\n"
"Watch a deployer address and get instant alerts\n"
"when they deploy a new token.\n\n"
"Usage:\n"
" /launchmon add <deployer_address> <label>\n"
" /launchmon list — show watched deployers\n"
" /launchmon remove <address>",
parse_mode=ParseMode.HTML,
reply_markup=back_kb(),
)
return
cmd = ctx.args[0].lower()
if cmd == "add":
if len(ctx.args) < 2:
await update.message.reply_text("Usage: /launchmon add <address> <label>")
return
addr = ctx.args[1]
label = " ".join(ctx.args[2:]) if len(ctx.args) > 2 else short_addr(addr)
from app.domains.telegram.rugmunchbot.db import get_db
conn = get_db()
conn.execute(
"INSERT INTO watchlists (user_id, token_address, chain, symbol, alert_price, alert_direction, created_at, is_active) VALUES (?,?,?,?,0,'launchmon',?,1)",
(user.id, addr, "all", f"LM:{label}", 0, __import__("time").time()),
)
conn.commit()
conn.close()
await update.message.reply_text(f"🔭 Watching {label} ({short_addr(addr)}) for new launches.", reply_markup=back_kb())
elif cmd == "list":
from app.domains.telegram.rugmunchbot.db import get_db
conn = get_db()
rows = conn.execute("SELECT * FROM watchlists WHERE user_id=? AND alert_direction='launchmon' AND is_active=1", (user.id,)).fetchall()
conn.close()
if not rows:
await update.message.reply_text("No deployers being watched. Use /launchmon add <address>")
return
text = f"🔭 <b>Watched Deployers</b>\n{sep()}\n\n"
for r in rows:
label = r["symbol"].replace("LM:", "")
text += f"{label}: <code>{r['token_address'][:12]}...</code>\n"
await update.message.reply_text(text, parse_mode=ParseMode.HTML, reply_markup=back_kb())
elif cmd == "remove":
if len(ctx.args) < 2:
await update.message.reply_text("Usage: /launchmon remove <address>")
return
from app.domains.telegram.rugmunchbot.db import get_db
conn = get_db()
conn.execute("UPDATE watchlists SET is_active=0 WHERE user_id=? AND token_address=? AND alert_direction='launchmon'", (user.id, ctx.args[1]))
conn.commit()
conn.close()
await update.message.reply_text("🔭 Removed from watch.", reply_markup=back_kb())
# ── Wallet Profiler ──────────────────────────────────────────────
async def cmd_profile(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
"""Cross-chain wallet profiler — complete multi-chain history."""
if not ctx.args:
await update.message.reply_text("Usage: /profile <wallet address>")
return
addr = ctx.args[0]
await update.message.chat.send_action("typing")
text = f"🔍 <b>Wallet Profile</b> — {short_addr(addr)}\n{sep()}\n\n"
try:
async with httpx.AsyncClient(timeout=15) as client:
r = await client.get(f"{BACKEND_URL}/api/v1/wallet/{addr}")
if r.status_code == 200:
data = r.json()
labels = data.get("labels", [])
chains = data.get("chains_found", [])
score = data.get("risk_score", 50)
if score > 70:
emoji = "🔴"
elif score > 40:
emoji = "🟡"
else:
emoji = "🟢"
text += f"{emoji} Risk Score: {score}/100\n\n"
text += f"🌐 Active on {len(chains)} chains: {', '.join(chains)}\n\n"
if labels:
text += "<b>Labels:</b>\n"
for l in labels[:10]:
text += f"{l.get('label_name', 'unknown')} ({l.get('label_category', '?')})\n"
else:
text += "No known labels.\n"
text += f"\n{footer_links()}"
await update.message.reply_text(text, parse_mode=ParseMode.HTML, reply_markup=back_kb())
else:
await update.message.reply_text("Wallet not found. Try again in a few seconds.", reply_markup=back_kb())
except Exception:
# Fallback: basic info
text += "Unable to fetch full profile. Try:\n"
text += f"• <a href='https://debank.com/profile/{addr}'>Debank</a>\n"
text += f"• <a href='https://etherscan.io/address/{addr}'>Etherscan</a>\n"
text += f"• <a href='https://solscan.io/account/{addr}'>Solscan</a>\n"
text += footer_links()
await update.message.reply_text(text, parse_mode=ParseMode.HTML, reply_markup=back_kb(), disable_web_page_preview=True)
__all__ = ["cmd_launchmon", "cmd_leaderboard", "cmd_profile"]