feat(bot): Anti-MEV protection on DEX swap commands

Checks token age, liquidity, volume ratio before generating
swap links. Warns users about sandwich attack risk and
recommends proper slippage based on risk level.

Cache: 5-min in-memory for repeated queries on same token.
This commit is contained in:
Crypto Rug Munch 2026-07-08 16:03:40 +07:00
parent 0cbd59fe18
commit 9c0964807f

View file

@ -356,3 +356,70 @@ async def cmd_holders(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text(
text, parse_mode=ParseMode.HTML, reply_markup=back_kb(), disable_web_page_preview=True
)
# ── Anti-MEV Protection ─────────────────────────────────────────────────
MEV_RISK_CACHE = {} # Simple in-memory cache
async def _check_mev_risk(token_address: str, chain: str) -> dict:
"""Check MEV/sandwich risk for a token before generating swap link."""
import time
cache_key = f"{token_address}:{chain}"
cached = MEV_RISK_CACHE.get(cache_key)
if cached and time.time() - cached["ts"] < 300: # 5 min cache
return cached
risk = {"risk": "unknown", "warnings": [], "recommend_slippage": 2.0}
try:
async with httpx.AsyncClient(timeout=10) as client:
r = await client.get(f"{DEXSCREENER}/tokens/{token_address}")
pairs = r.json().get("pairs", [])
if pairs:
p = pairs[0]
age_h = float(p.get("pairCreatedAt", 0))
age_days = (time.time() / 1000 - age_h / 1000) / 86400 if age_h else 999
liquidity = float(p.get("liquidity", {}).get("usd", 0) or 0)
volume = float(p.get("volume", {}).get("h24", 0) or 0)
txns = int(p.get("txns", {}).get("h24", {}).get("buys", 0) or 0) + int(p.get("txns", {}).get("h24", {}).get("sells", 0) or 0)
if age_days < 1:
risk["warnings"].append(f"🆕 Token is {age_days*24:.0f}h old — high sandwich risk")
risk["risk"] = "critical"
risk["recommend_slippage"] = 25.0
elif age_days < 7:
risk["warnings"].append(f"⚠️ Token only {age_days:.1f} days old — moderate risk")
risk["risk"] = "high"
risk["recommend_slippage"] = 15.0
if liquidity < 10000:
risk["warnings"].append(f"💧 Low liquidity (${liquidity:,.0f}) — easily manipulated")
if risk["risk"] == "unknown": risk["risk"] = "high"
risk["recommend_slippage"] = max(risk["recommend_slippage"], 20.0)
if volume > 0 and liquidity > 0 and volume / liquidity > 5:
risk["warnings"].append("📊 Volume/Liquidity ratio high — possible manipulation")
risk["recommend_slippage"] = max(risk["recommend_slippage"], 15.0)
if not risk["warnings"]:
risk["risk"] = "low"
except Exception:
pass
MEV_RISK_CACHE[cache_key] = {**risk, "ts": time.time()}
return risk
async def _format_mev_warning(risk: dict) -> str:
"""Format MEV risk warning for display."""
if not risk["warnings"]:
return ""
emoji = {"critical": "🔴", "high": "🟡", "low": "🟢", "unknown": ""}
e = emoji.get(risk["risk"], "")
text = f"\n\n{e} <b>MEV Protection</b>\n"
if risk["risk"] == "critical":
text += "⚡ HIGH sandwich attack risk detected!\n\n"
for w in risk["warnings"]:
text += f" {w}\n"
text += f"\n💡 Recommended slippage: {risk['recommend_slippage']}%"
return text