solcheck.py: free Solana memecoin quick-check script (copy & run)
nyamA small stdlib-only Python 3 script that pulls RugCheck, DexScreener, Jupiter lite-api and Solana public RPC for a mint address and prints red/yellow/green flags. No API keys. Not financial advice — always verify manually.
How to run
- Save the code below as `solcheck.py`.
- Run: `python3 solcheck.py <MINT>`
- Optional JSON: `python3 solcheck.py <MINT> --json`
Example: `python3 solcheck.py So11111111111111111111111111111111111111112`
Script
#!/usr/bin/env python3
"""solcheck.py - quick, free, keyless due-diligence check for a Solana memecoin.
Usage: python3 solcheck.py <MINT> [--json]
Data: RugCheck, DexScreener, Jupiter lite-api, Solana public RPC. Stdlib only.
Not financial advice. Always verify manually (see CHECKLIST.md).
"""
import json, sys, time, urllib.request, urllib.error
UA = {"User-Agent": "solana-memecoin-checker/1.0", "Accept": "application/json"}
RPC = "https://api.mainnet-beta.solana.com"
def fetch(url, body=None, retries=3, timeout=20):
"""GET/POST JSON with retry on 429/5xx. Returns (data, error_string)."""
data = json.dumps(body).encode() if body is not None else None
hdrs = dict(UA, **({"Content-Type": "application/json"} if data else {}))
err = None
for i in range(retries):
try:
req = urllib.request.Request(url, data=data, headers=hdrs)
with urllib.request.urlopen(req, timeout=timeout) as r:
return json.loads(r.read().decode()), None
except urllib.error.HTTPError as e:
err = f"HTTP {e.code}"
if e.code in (429, 500, 502, 503, 504):
time.sleep(1.5 * (i + 1)); continue
return None, err
except Exception as e: # network, timeout, bad JSON
err = type(e).__name__ + ": " + str(e)[:80]
time.sleep(1)
return None, err + (" (rate limited, try again later)" if err and "429" in err else "")
def usd(x):
if x is None: return "n/a"
x = float(x)
for d, s in ((1e9, "B"), (1e6, "M"), (1e3, "k")):
if abs(x) >= d: return f"${x/d:.2f}{s}"
return f"${x:.2f}"
def age(ms):
if not ms: return "n/a"
h = (time.time() * 1000 - float(ms)) / 3.6e6
return f"{h*60:.0f} min" if h < 1 else f"{h:.1f} h" if h < 48 else f"{h/24:.1f} days"
def main():
args = [a for a in sys.argv[1:] if not a.startswith("--")]
if len(args) != 1 or not (32 <= len(args[0]) <= 44):
print("usage: python3 solcheck.py <SOLANA_MINT_ADDRESS> [--json]"); sys.exit(2)
mint = args[0]
errors = {}
rc, errors["rugcheck"] = fetch(f"https://api.rugcheck.xyz/v1/tokens/{mint}/report")
ds, errors["dexscreener"] = fetch(f"https://api.dexscreener.com/tokens/v1/solana/{mint}")
jp, errors["jupiter_price"] = fetch(f"https://lite-api.jup.ag/price/v3?ids={mint}")
js, errors["jupiter_search"] = fetch(f"https://lite-api.jup.ag/tokens/v2/search?query={mint}")
rpc, errors["rpc"] = fetch(RPC, {"jsonrpc": "2.0", "id": 1, "method": "getAccountInfo",
"params": [mint, {"encoding": "jsonParsed"}]})
rc = rc or {}
pair = max(ds, key=lambda p: (p.get("liquidity") or {}).get("usd") or 0) if isinstance(ds, list) and ds else {}
jprice = (jp or {}).get(mint) or {}
jtok = next((t for t in (js or []) if t.get("id") == mint), {}) if isinstance(js, list) else {}
info = (((rpc or {}).get("result") or {}).get("value") or {}).get("data", {})
info = info.get("parsed", {}).get("info", {}) if isinstance(info, dict) else {}
meta = rc.get("tokenMeta") or {}
base = pair.get("baseToken") or {}
name = meta.get("name") or base.get("name") or jtok.get("name") or "?"
sym = meta.get("symbol") or base.get("symbol") or jtok.get("symbol") or "?"
created = pair.get("pairCreatedAt")
if not created and jprice.get("createdAt"):
try:
from datetime import datetime
created = datetime.fromisoformat(jprice["createdAt"].replace("Z", "+00:00")).timestamp() * 1000
except Exception: pass
mcap = pair.get("marketCap") or pair.get("fdv") or jtok.get("mcap") or jtok.get("fdv")
liq = (pair.get("liquidity") or {}).get("usd") or jprice.get("liquidity") or jtok.get("liquidity")
vol = (pair.get("volume") or {}).get("h24")
tx = (pair.get("txns") or {}).get("h24") or {}
buys, sells = tx.get("buys"), tx.get("sells")
# authorities: RPC is the source of truth, RugCheck as fallback
def auth(key):
if info: return info.get(key)
return rc.get(key) if rc else "unknown"
mint_auth, freeze_auth = auth("mintAuthority"), auth("freezeAuthority")
# holders, excluding bonding curve / AMM pools / lockers
known = rc.get("knownAccounts") or {}
excl = {a for a, v in known.items() if v.get("type") in ("AMM", "LOCKER")}
for m in rc.get("markets") or []:
for k in ("pubkey", "liquidityAAccount", "liquidityBAccount"):
v = m.get(k)
if isinstance(v, str): excl.add(v)
elif isinstance(v, dict): excl.update(x for x in (v.get("pubkey"), v.get("owner")) if isinstance(x, str))
holders = rc.get("topHolders") or []
real = [h for h in holders if h.get("owner") not in excl and h.get("address") not in excl]
top10 = sum(h.get("pct") or 0 for h in real[:10]) if holders else None
insiders_top = sum(1 for h in real[:10] if h.get("insider"))
n_holders = rc.get("totalHolders") or jtok.get("holderCount")
creator = rc.get("creator") or jtok.get("dev")
supply = None
try: supply = float(info.get("supply")) / 10 ** int(info.get("decimals"))
except Exception: pass
dev_pct = None
if rc.get("creatorBalance") is not None and supply:
dec = int(info.get("decimals") or 6)
dev_pct = 100 * float(rc["creatorBalance"]) / 10 ** dec / supply
creator_tokens = rc.get("creatorTokens") or []
lp = [((m.get("lp") or {}).get("lpLockedPct")) for m in rc.get("markets") or []]
lp = [x for x in lp if x is not None]
lp_locked = max(lp) if lp else None
risks = rc.get("risks") or []
graph_ins = rc.get("graphInsidersDetected")
launchpad = rc.get("launchpad") or jprice.get("launchpad") or jtok.get("launchpad")
if isinstance(launchpad, dict): launchpad = launchpad.get("name") or launchpad.get("platform")
on_curve = pair.get("dexId") == "pumpfun" or (not pair and "pump" in str(launchpad).lower())
# flags
red, yellow, green = [], [], []
(red if mint_auth else green).append("mint authority " + ("ACTIVE (dev can print tokens)" if mint_auth else "revoked"))
(red if freeze_auth else green).append("freeze authority " + ("ACTIVE (dev can freeze wallets)" if freeze_auth else "revoked"))
if rc.get("rugged"): red.append("RugCheck marks this token as RUGGED")
if top10 is not None:
(red if top10 > 30 else yellow if top10 > 15 else green).append(f"top-10 holders (ex pools/curve) {top10:.1f}%")
if dev_pct is not None:
(red if dev_pct > 10 else yellow if dev_pct > 3 else green).append(f"dev holds {dev_pct:.2f}%")
if len(creator_tokens) > 3: yellow.append(f"dev launched {len(creator_tokens)} other tokens (serial launcher?)")
if graph_ins: red.append(f"RugCheck detected {graph_ins} insider wallets via graph analysis")
if insiders_top: red.append(f"{insiders_top} of top-10 holders flagged insider")
if on_curve: yellow.append("still on pump.fun bonding curve (LP not migrated yet)")
elif lp_locked is not None:
(green if lp_locked >= 90 else yellow if lp_locked >= 50 else red).append(f"LP locked/burned {lp_locked:.0f}%")
if liq is not None and not on_curve and float(liq) < 10000: yellow.append(f"thin liquidity {usd(liq)}")
if buys is not None and sells and buys / max(sells, 1) > 8: yellow.append("very one-sided buys (possible bots/wash)")
for r in risks:
lvl = (r.get("level") or "").lower()
(red if lvl == "danger" else yellow).append("RugCheck: " + r.get("name", "?"))
if created and (time.time() * 1000 - float(created)) < 3.6e6: yellow.append("less than 1 hour old")
out = dict(mint=mint, name=name, symbol=sym, age=age(created), launchpad=launchpad,
mcap=mcap, liquidity=liq, vol24h=vol, buys24h=buys, sells24h=sells,
mint_authority=mint_auth, freeze_authority=freeze_auth, holders=n_holders,
top10_ex_pools_pct=top10, creator=creator, dev_pct=dev_pct,
dev_other_tokens=len(creator_tokens), lp_locked_pct=lp_locked,
rugcheck_score=rc.get("score_normalised"), risks=[r.get("name") for r in risks],
graph_insiders=graph_ins, red=red, yellow=yellow, green=green,
errors={k: v for k, v in errors.items() if v})
if "--json" in sys.argv:
print(json.dumps(out, indent=2)); return
f = lambda v, fmt="{}": "n/a" if v is None else fmt.format(v)
print(f"\n=== {name} (${sym}) ===\nmint {mint}\nlaunchpad {f(launchpad)}")
print(f"age {age(created)}\nmcap {usd(mcap)}\nliquidity {usd(liq)}{' (bonding curve)' if on_curve else ''}")
print(f"24h volume {usd(vol)}\n24h txns {f(buys)} buys / {f(sells)} sells")
print(f"mint auth {'ACTIVE: ' + str(mint_auth) if mint_auth else 'revoked'}")
print(f"freeze {'ACTIVE: ' + str(freeze_auth) if freeze_auth else 'revoked'}")
print(f"holders {f(n_holders)}\ntop-10 % {f(top10, '{:.2f}%')} (excluding bonding curve / LP / lockers)")
print(f"dev {f(creator)} holds {f(dev_pct, '{:.2f}%')}, other launches: {len(creator_tokens)}")
print(f"LP locked {'n/a (on curve)' if on_curve else f(lp_locked, '{:.0f}%')}")
print(f"RugCheck score {f(rc.get('score_normalised'))}/100 (higher = riskier), insiders(graph): {f(graph_ins)}")
print("risks " + (", ".join(f"{r.get('name')} [{r.get('level')}]" for r in risks) or "none listed"))
print("\n--- flags ---")
for tag, lst in (("RED ", red), ("YELLOW", yellow), ("GREEN ", green)):
for x in lst: print(f"[{tag}] {x}")
verdict = "RED - high risk" if red else "YELLOW - caution" if yellow else "GREEN - basics look clean (still risky!)"
print(f"\nsummary: {verdict} ({len(red)} red / {len(yellow)} yellow / {len(green)} green)")
print("bundles/snipers: NOT checked (no free API). Check manually:\n"
f" https://trench.bot/bundles/{mint}\n https://app.bubblemaps.io/sol/token/{mint}")
for k, v in out["errors"].items(): print(f"warning: {k} failed ({v}); related fields may be n/a")
print("Not financial advice. Data as of " + time.strftime("%Y-%m-%d %H:%M %Z") + ".\n")
if __name__ == "__main__":
main()