#!/usr/bin/env python3 """RM Circle: warn when an organisation grows deeper than its apex can collect from. The rule is read off the contract's own _payUpline, not inferred: a member at depth d under the apex pays the apex on their upgrade to level d+1, and only when apex.level >= d. Upgrades stop at level 8, so levelIndex never exceeds 6 and DEPTH 7 IS A HARD CEILING -- nothing deeper than 7 pays the apex at any level, so that is not a miss and must not alert. Pings Marty's phone only when the verdict changes, so a steady state stays silent. APEX=1154 python3 depth_watch.py # normal DRY=1 APEX=21 python3 depth_watch.py # print, never send, never write state """ import json import os import pathlib import sys import urllib.request CONTRACT = "0x33bdaeefd6d17d80ae53816c916dfb26c4fb2daf" RPCS = ["https://polygon-bor-rpc.publicnode.com", "https://1rpc.io/matic"] APEX = int(os.environ.get("APEX", "1154")) DRY = os.environ.get("DRY") == "1" CEILING = 7 # levelIndex tops out at 6, so depth 7 is the last that can pay an apex MAX_WALK = 16 # MAX_MATRIX_DEPTH in the contract STATE = pathlib.Path(os.environ.get("STATE_FILE", f"/root/.rmc-depth-watch-{APEX}.json")) TOKEN_FILE = pathlib.Path(os.environ.get("TG_TOKEN_FILE", "/root/.mbhermes-telegram-token")) CHAT_ID = os.environ.get("TG_CHAT", "1289244227") def call(data): body = json.dumps({"jsonrpc": "2.0", "id": 1, "method": "eth_call", "params": [{"to": CONTRACT, "data": data}, "latest"]}).encode() last = None for url in RPCS: try: # publicnode 403s the default Python-urllib agent; the Node client gets through # because it sends one at all, so send a plain identifiable agent of our own. req = urllib.request.Request(url, data=body, headers={ "Content-Type": "application/json", "User-Agent": "rmc-depth-watch/1.0"}) out = json.load(urllib.request.urlopen(req, timeout=30)) if "error" in out: raise RuntimeError(out["error"].get("message", "rpc error")) return out["result"] except Exception as exc: # try the next endpoint before giving up last = exc raise last def enc(n): return format(int(n), "064x") def word(res, i): return int(res[2 + i * 64: 2 + (i + 1) * 64], 16) def member(mid): r = call("0xc92463fa" + enc(mid)) return {"id": mid, "level": word(r, 5), "directCount": word(r, 6)} def children(mid): """getMatrixChildren returns a FIXED-size array, inlined: read the words straight off. Decoding it as a dynamic array (offset + length) yields garbage.""" r = call("0x04c8cc3d" + enc(mid)) n = (len(r) - 2) // 64 return [w for w in (word(r, i) for i in range(n)) if w > 0] def send(text): token = TOKEN_FILE.read_text().strip() body = json.dumps({"chat_id": CHAT_ID, "text": text}).encode() req = urllib.request.Request(f"https://api.telegram.org/bot{token}/sendMessage", data=body, headers={"Content-Type": "application/json"}) urllib.request.urlopen(req, timeout=30).read() def main(): # breadth-first: one call per member, not one per path depth, frontier, seen, count = 0, [APEX], {APEX}, 0 while frontier and depth < MAX_WALK: nxt = [] for mid in frontier: for c in children(mid): if c not in seen: seen.add(c) nxt.append(c) count += 1 if not nxt: break frontier, depth = nxt, depth + 1 apex = member(APEX) covered = min(apex["level"], CEILING) reachable = min(depth, CEILING) missing = list(range(covered + 1, reachable + 1)) behind = bool(missing) at_edge = (not behind) and reachable == covered and covered < CEILING and depth > 0 nxt_rung = apex["level"] + 1 line = f'#{APEX} level {apex["level"]}, {count} in the org, {depth} deep' msg = None if behind: listed = (", ".join(str(d) for d in missing[:-1]) + " and " + str(missing[-1]) if len(missing) > 1 else str(missing[0])) msg = (f"RM Circle: #{APEX} is missing generations.\n{line}.\n" f"Depth {listed} upgrade{'s' if len(missing) > 1 else ''} route past you. " f"Level {nxt_rung} picks up depth {covered + 1}" + (", and each rung after that adds one more generation." if len(missing) > 1 else ".") + ("\nDepth 8 and below never pays an apex at any level, so that part is not a miss." if depth > CEILING else "")) elif at_edge: msg = (f"RM Circle: #{APEX} is at its edge.\n{line}.\n" f"One more generation and that depth's upgrade routes past you. " f"Level {nxt_rung} covers it.") key = f"{reachable}:{covered}:{'behind' if behind else 'edge' if at_edge else 'ok'}" prev = {} try: prev = json.loads(STATE.read_text()) except Exception: pass changed = prev.get("key") != key verdict = ("missing depth " + ", ".join(str(d) for d in missing)) if behind else \ ("at the edge" if at_edge else "covered") print(f"{line} -> {verdict} ({'changed' if changed else 'unchanged'})", flush=True) if msg and changed and not DRY: send(msg) print("telegram: sent", flush=True) elif msg: print(("would send" if DRY else "nothing new to say") + ":\n" + msg, flush=True) if not DRY: STATE.write_text(json.dumps({"key": key, "depth": depth, "level": apex["level"], "count": count})) if __name__ == "__main__": try: main() except Exception as exc: print(f"depth_watch failed: {exc}", file=sys.stderr) sys.exit(1)