#!/usr/bin/env python3
"""scan_monty.py — MONTY copycat scan on Robinhood Chain (eip155:4663).

Node constraints (probed 2026-09-25): state horizon <100k blocks (no deep
historical eth_getCode/StorageAt) but full log index + heavy 429 rate limit.
So: all reads at "latest"; logs fetched in wide chunks with pacing/backoff.

Per contract: name/symbol/decimals/supply, storage scan slots 0..47 for the
Monty fee recipient, first-mint (deployer heuristic), all Transfer logs ->
unique recipient count + fee-recipient flow count. JSON to stdout, progress
to stderr. Stdlib only.
"""
import json, sys, time, urllib.request

RPCS = ["https://rpc.mainnet.chain.robinhood.com", "https://rpc.ordofi.network"]
H = {"Content-Type": "application/json", "User-Agent": "ARION-scan/1.0"}
FEE = "0x227dfa9ae768e9dedf13653b9d25ce216296ea8f".lower()
TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
ZERO = "0x" + "0" * 64
CHUNK = 8_000_000

CONTRACTS = [
    "0x309e847c2528C4E37de2074b71F39C9f2C907ba3",
    "0xD5656D51CF28e3154bDf18aEe3E0C042BCf39252",
    "0x7D7aC622138BF9fd2A8DbCcA0f4Ae100761D1e35",
    "0x16b0f0B9e6eFF2D38AdC7E05f988f777D243C4e8",
    "0x96e6E80Fd156E73c92c697d232Da2F2172bA702b",
    "0xe01B4BE33F61e75b75Ba3748c6A51552210E8Da4",
]

_i = 0
def rpc(method, params, budget=60):
    global _i
    body = json.dumps({"jsonrpc": "2.0", "id": 1, "method": method, "params": params}).encode()
    t0 = time.time()
    delay = 0.5
    while True:
        url = RPCS[_i % len(RPCS)]
        _i += 1
        try:
            req = urllib.request.Request(url, data=body, headers=H)
            d = json.load(urllib.request.urlopen(req, timeout=30))
            if "result" in d:
                time.sleep(0.12)
                return d["result"]
            raise RuntimeError(str(d.get("error"))[:140])
        except Exception as e:
            if time.time() - t0 > budget:
                raise
            time.sleep(delay)
            delay = min(delay * 2, 8)

def call(to, data):
    return rpc("eth_call", [{"to": to, "data": data}, "latest"])

def dec_str(r):
    if not r or r == "0x":
        return ""
    raw = bytes.fromhex(r[2:])
    if len(raw) >= 96:
        try:
            ln = int.from_bytes(raw[64:96], "big")
            if 0 < ln <= 200:
                return raw[96:96 + ln].decode(errors="replace")
        except Exception:
            pass
    return raw.rstrip(b"\x00").decode(errors="replace")

def all_logs(addr):
    head = int(rpc("eth_blockNumber", []), 16)
    logs = []
    for lo in range(0, head + 1, CHUNK):
        hi = min(lo + CHUNK - 1, head)
        r = rpc("eth_getLogs", [{"address": addr, "fromBlock": hex(lo),
                                 "toBlock": hex(hi), "topics": [TRANSFER_TOPIC]}], budget=90)
        logs.extend(r or [])
    return logs, head

def scan(c):
    out = {"contract": c}
    out["name"] = dec_str(call(c, "0x06fdde03"))
    out["symbol"] = dec_str(call(c, "0x95d89b41"))
    d = call(c, "0x313ce567")
    out["decimals"] = int(d, 16) if d else None
    ts = call(c, "0x18160ddd")
    out["total_supply_raw"] = int(ts, 16) if ts else None

    hits, addrs = [], set()
    for slot in range(48):
        v = rpc("eth_getStorageAt", [c, hex(slot), "latest"])
        if not v or v == ZERO:
            continue
        vv = v.lower()
        if FEE[2:] in vv:
            hits.append(slot)
        if int(vv[:-40] or "0", 16) == 0 and int(vv[-40:], 16) != 0:
            addrs.add("0x" + vv[-40:])
    out["fee_storage_slots"] = hits
    out["storage_addrs"] = sorted(addrs)
    print("storage done", c[:10], file=sys.stderr)

    logs, head = all_logs(c)
    mints, holders = [], set()
    fee_hits = 0
    for lg in logs:
        t = lg["topics"]
        to = "0x" + t[2][-40:]
        holders.add(to)
        if int(t[1], 16) == 0:
            mints.append({"to": to, "tx": lg["transactionHash"],
                          "block": int(lg["blockNumber"], 16)})
        if to.lower() == FEE:
            fee_hits += 1
    out["transfer_events"] = len(logs)
    out["approx_holder_addrs"] = len(holders)
    out["first_mint"] = mints[0] if mints else None
    out["transfers_to_fee_recipient"] = fee_hits
    out["fee_match"] = bool(hits) or fee_hits > 0
    out["scan_head"] = head
    print("logs done", c[:10], len(logs), file=sys.stderr)
    return out

def main():
    res = []
    for c in CONTRACTS:
        try:
            res.append(scan(c))
        except Exception as e:
            res.append({"contract": c, "error": str(e)})
    print(json.dumps(res, indent=1))

if __name__ == "__main__":
    main()
