#!/usr/bin/env python3
"""PROPOSED, read-only on existing files. Not deployed. Ben 5 Oct 2026.

Gap found: in /opt/hist/rh/<day>.db, 84,067 of 118,904 `launches` rows (15 Sep to 4 Oct) have supply = NULL,
24,116 of them with real swaps. Anything that needs market cap (mcap.py does `int(supply)` then `continue`)
silently drops them. The bigger the runner, the more often it is in this gap:
  sellable $1M in 6 h: 18% had no supply, $2M: 24%, $5M: 42%.

This script never edits a day DB. It writes a NEW side file /opt/hist/analysis/supply_extra.db
(token, total_supply_raw, decimals, fetched_at) that readers can LEFT JOIN.
One Multicall3 eth_call returns 300 tokens (totalSupply + decimals); 80k tokens took ~4 min from the public RPC.
"""
import glob, json, sqlite3, sys, time, urllib.request
from eth_abi import encode, decode      # pip install eth-abi

RPC = 'https://rpc.mainnet.chain.robinhood.com'; UA = 'Mozilla/5.0 (Macintosh) Chrome/128'
MC3 = '0xcA11bde05977b3631167028862bE2a173976CA11'      # Multicall3, verified deployed on Robinhood Chain
OUT = '/opt/hist/analysis/supply_extra.db'

def call(data, tries=6):
    for i in range(tries):
        try:
            r = json.loads(urllib.request.urlopen(urllib.request.Request(RPC, json.dumps(dict(jsonrpc='2.0', id=1, method='eth_call',
                 params=[{'to': MC3, 'data': data}, 'latest'])).encode(), {'Content-Type': 'application/json', 'User-Agent': UA}), timeout=40).read())
            if 'result' in r: return r['result']
        except Exception: pass
        time.sleep(4 * (i + 1))
    return None

def main():
    o = sqlite3.connect(OUT)
    o.execute('CREATE TABLE IF NOT EXISTS s(token TEXT PRIMARY KEY, supply_raw TEXT, decimals INT, fetched_at INT)')
    have = {r[0] for r in o.execute('SELECT token FROM s')}
    todo = set()
    for p in sorted(glob.glob('/opt/hist/rh/2026-*.db')):
        c = sqlite3.connect('file:%s?mode=ro' % p, uri=True, timeout=30)
        todo |= {r[0] for r in c.execute('SELECT token FROM launches WHERE supply IS NULL')}
        c.close()
    todo = sorted(todo - have); print('todo', len(todo), flush=True)
    for i in range(0, len(todo), 300):
        g = todo[i:i + 300]
        calls = [(t, True, bytes.fromhex(sel)) for t in g for sel in ('18160ddd', '313ce567')]
        r = call('0x82ad56cb' + encode(['(address,bool,bytes)[]'], [calls]).hex())
        if not r: continue
        res = decode(['(bool,bytes)[]'], bytes.fromhex(r[2:]))[0]
        rows = []
        for k, t in enumerate(g):
            (ok1, s), (ok2, d) = res[2 * k], res[2 * k + 1]
            rows.append((t, str(int.from_bytes(s[:32], 'big')) if ok1 and len(s) >= 32 else None,
                         int.from_bytes(d[:32], 'big') if ok2 and len(d) >= 32 else None, int(time.time())))
        o.executemany('INSERT OR REPLACE INTO s VALUES(?,?,?,?)', rows); o.commit(); time.sleep(0.3)
    print('done', o.execute('SELECT count(*) FROM s').fetchone()[0])

if __name__ == '__main__':
    main()
