solcheck.py: free Solana memecoin quick-check script (copy & run)

solcheck.py: free Solana memecoin quick-check script (copy & run)

nyam

A 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

  1. Save the code below as `solcheck.py`.
  2. Run: `python3 solcheck.py <MINT>`
  3. 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()

Report Page