import json, sys, time, threading, os, bisect
from concurrent.futures import ThreadPoolExecutor
sys.path.insert(0, '/tmp/run'); from rpc import *
A = json.load(open('anchors.json')); AB = [a[0] for a in A]; AT = [a[1] for a in A]
def blk(t):
    i = bisect.bisect_left(AT, t); i = min(max(i, 1), len(AT) - 1)
    (b0, t0), (b1, t1) = A[i - 1], A[i]
    return int(b0 + (t - t0) * (b1 - b0) / max(1, t1 - t0))
H = int(rpc('eth_blockNumber', []), 16)
coins = json.load(open('coins_rpc.json'))
done = set()
if os.path.exists('raw.jsonl'):
    for l in open('raw.jsonl'):
        try: done.add(json.loads(l)['tok'])
        except Exception: pass
lock = threading.Lock(); out = open('raw.jsonl', 'a')
pad = lambda a: '0x' + '0' * 24 + a[2:]
def first_mint(tok, t_est, exact):
    b = blk(t_est)
    wins = [(b - 60000, b + 60000)] if exact else [(b + 20000 - 9_990_000, b + 20000), (b + 20000 - 19_980_000, b + 20000 - 9_990_000)]
    for lo, hi in wins:
        r = rpc('eth_getLogs', [{'address': tok, 'topics': [TR, '0x' + '0' * 64], 'fromBlock': hex(max(0, lo)), 'toBlock': hex(min(H, hi))}])
        if not isinstance(r, list): raise RuntimeError('mint query failed')
        if r: return r
    return []
def one(c):
    tok = c['tok']; o = dict(tok=tok)
    mints = first_mint(tok, c['t_est'], 't_exact' in c)
    if not mints: o['err'] = 'no_mint'; return o
    mb = int(mints[0]['blockNumber'], 16); o['mint_block'] = mb; o['mint_tx'] = mints[0]['transactionHash']
    o['mints'] = [(int(m['blockNumber'], 16), '0x' + m['topics'][2][-40:], m['data']) for m in mints[:20]]
    pools = []
    for tp in ([INIT, None, pad(tok)], [INIT, None, None, pad(tok)]):
        r = rpc('eth_getLogs', [{'address': PM, 'topics': tp, 'fromBlock': hex(mb), 'toBlock': hex(min(H, mb + 9_000_000))}])
        if isinstance(r, list): pools += r
    pools.sort(key=lambda l: (int(l['blockNumber'], 16), int(l['logIndex'], 16)))
    o['pools'] = [dict(b=int(l['blockNumber'], 16), id=l['topics'][1], c0='0x' + l['topics'][2][-40:], c1='0x' + l['topics'][3][-40:], data=l['data'], tx=l['transactionHash']) for l in pools[:12]]
    L = o['pools'][0]['b'] if pools else mb; o['launch_block'] = L
    if pools:
        tx = rpc('eth_getTransactionByHash', [pools[0]['transactionHash']])
        if isinstance(tx, dict): o['ptx_from'] = tx.get('from'); o['ptx_to'] = tx.get('to'); o['ptx_value'] = tx.get('value')
    def grab(lo, hi, depth=0):
        r = rpc('eth_getLogs', [{'address': tok, 'topics': [TR], 'fromBlock': hex(lo), 'toBlock': hex(hi)}])
        if isinstance(r, list): return r
        if depth < 8 and hi > lo:
            m = (lo + hi) // 2; return grab(lo, m, depth + 1) + grab(m + 1, hi, depth + 1)
        raise RuntimeError('transfer query failed ' + str(r)[:120])
    r = grab(L, min(H, L + 18000))
    o['tr'] = [(int(l['blockNumber'], 16), int(l['logIndex'], 16), '0x' + l['topics'][1][-40:], '0x' + l['topics'][2][-40:], l['data'], l['transactionHash'][:18]) for l in r]
    bl = rpc('eth_getBlockByNumber', [hex(L), False]); o['launch_ts'] = int(bl['timestamp'], 16) if isinstance(bl, dict) and 'timestamp' in bl else None
    return o
def work(c):
    if c['tok'] in done: return
    o = None
    for k in range(3):
        try: o = one(c); break
        except Exception as e: o = dict(tok=c['tok'], err=repr(e)[:200]); time.sleep(10)
    o['kind'] = c['kind']
    with lock: out.write(json.dumps(o) + '\n'); out.flush()
todo = [c for c in coins if c['tok'] not in done]
print('todo', len(todo), flush=True)
t0 = time.time()
with ThreadPoolExecutor(3) as ex:
    for i, _ in enumerate(ex.map(work, todo)):
        if i % 200 == 0: print(i, round(time.time() - t0), flush=True)
print('done', round(time.time() - t0))
