#!/usr/bin/env python3
# Generic nonce-transition walk for any EOA on eip155:4663 (archive node needed).
# Enumerates every outgoing tx by binary-searching eth_getTransactionCount.
# usage: nonce_walk_generic.py <address> <out.json> [token_addr] [pair_addr]
import json, sys, time, urllib.request

RPCS = ["https://rpc.ordofi.network", "https://rpc.mainnet.chain.robinhood.com",
        "https://robinhood-rpc.publicnode.com", "https://robinhood.drpc.org"]
ADDR = sys.argv[1].lower()
OUT = sys.argv[2]
TOKEN = (sys.argv[3] or "").lower() if len(sys.argv) > 3 else ""
PAIR = (sys.argv[4] or "").lower() if len(sys.argv) > 4 else ""
_id = [0]

def rpc(method, params, timeout=25, tries=6):
    _id[0] += 1
    body = json.dumps({"jsonrpc": "2.0", "id": _id[0], "method": method,
                       "params": params}).encode()
    last = None
    for attempt in range(tries):
        for url in RPCS:
            try:
                req = urllib.request.Request(url, data=body,
                                             headers={"Content-Type": "application/json"})
                with urllib.request.urlopen(req, timeout=timeout) as r:
                    d = json.loads(r.read())
                if "error" in d:
                    last = f"{url}: {d['error']}"
                    continue
                return d["result"]
            except Exception as e:
                last = f"{url}: {e}"
        time.sleep(0.5 + attempt * 0.7)
    raise RuntimeError(f"{method} failed on all doors: {last}")

def nonce_at(addr, block):
    return int(rpc("eth_getTransactionCount", [addr, hex(block)]), 16)

def block_txs(block):
    b = rpc("eth_getBlockByNumber", [hex(block), True])
    return b["transactions"] if b else []

def receipt(txh):
    return rpc("eth_getTransactionReceipt", [txh])

SIGS = {"a9059cbb": "transfer", "095ea7b3": "approve", "23b872dd": "transferFrom",
        "e8e33700": "addLiquidity", "f305d719": "addLiquidityETH",
        "40c10f19": "mint", "a0712d68": "mint/seed", "06fdde03": "name",
        "c9567bf9": "openTrading?", "02a75131": "lockVesting?"}

def classify(tx):
    to = (tx.get("to") or "").lower()
    inp = tx.get("input") or "0x"
    sel = inp[2:10]
    value = int(tx.get("value", "0x0"), 16)
    kind = []
    if to == "": kind.append("CONTRACT_CREATION")
    if to == TOKEN: kind.append("->token")
    if to == PAIR: kind.append("->pair")
    if sel in SIGS: kind.append(SIGS[sel])
    elif sel: kind.append(f"sel:{sel}")
    if value: kind.append(f"value:{value/1e18:g}")
    return ",".join(kind) or "plain-send"

def main():
    head = int(rpc("eth_blockNumber", []), 16)
    n_head = nonce_at(ADDR, head)
    low = int(sys.argv[5]) if len(sys.argv) > 5 else 1
    print(f"head={head} nonce(head)={n_head} low={low}", flush=True)

    trans_blocks = {}
    for n in range(1, n_head + 1):
        lo, hi = low, head
        while lo < hi:
            mid = (lo + hi) // 2
            if nonce_at(ADDR, mid) >= n:
                hi = mid
            else:
                lo = mid + 1
            time.sleep(0.25)
        trans_blocks.setdefault(lo, set()).add(n)
        low = lo  # subsequent nonces can't precede this transition
        print(f"nonce {n}: first seen at block {lo}", flush=True)
        time.sleep(0.25)

    rows = []
    for blk in sorted(trans_blocks):
        for tx in block_txs(blk):
            if (tx.get("from") or "").lower() != ADDR:
                continue
            r = receipt(tx["hash"])
            status = r.get("status") if r else None
            nlogs = len(r.get("logs", [])) if r else 0
            rows.append({
                "block": blk, "nonce": int(tx["nonce"], 16),
                "hash": tx["hash"], "to": (tx.get("to") or "CREATE"),
                "kind": classify(tx), "status": status, "logs": nlogs,
                "gasUsed": int(r["gasUsed"], 16) if r else None,
            })
            time.sleep(0.05)
    print(json.dumps(rows, indent=1))
    with open(OUT, "w") as f:
        json.dump({"addr": ADDR, "head": head, "nonce_head": n_head,
                   "rows": rows}, f, indent=1)

if __name__ == "__main__":
    main()
