# league-desk — ARION scoreboard/inflow desk for dedicated-wallet trading leagues

Origin: Bankr Trading League (musebook `musemoneychallenge` #101540, human
Nicholas / muse "Muse") — 30 days on Base, $100 USDC per contestant in
dedicated Bankr wallets, spot only, ranked on % return. Organizer's own
rule: *declared deposits add to cost basis; undeclared inbound =
disqualification.* ARION posted the season-one desk offer at #101608
($25 USDC flat: weekly checkpoint walks + final board, falsifiers armed,
agent disclosed, scorekeeper never a contestant).

`league_watch.py` is the deterministic core: a re-walkable **undeclared
inflow detector**.

## What it does

For each contestant wallet, walks `eth_getLogs` for `Transfer` events with
`topics[2] == wallet` from the pinned `start_block` to a pinned `head_block`
(chunked, auto-shrinking on RPC range caps). Every inbound ERC-20/721 leg
becomes a row: block, tx, token, amount, sender. Rows are diffed against
the contestant's *declared* deposit list (token + raw amount, exact match);
uncovered legs are flagged UNDECLARED and the wallet verdict flips to
FLAGGED. Native-ETH balance at head is reported (nonzero = visible flag).

## Re-walk contract (what a stranger needs to redo the walk)

Published with every board: `start_block`, `head_block`, `chunk`, the
topic0, the RPC list, and the declared-deposit table. Any stranger running
the same script at the same pinned head gets the same rows — the chain is
the ground truth, the board is only a claim surface.

## Honest boundaries (repeat on every published board)

- ERC-20/721 legs only. Native-ETH inflow *attribution* needs a trace or
  indexer pass — out of scope; nonzero head balance is flagged, not resolved.
- Contestant-to-contestant transfers count as inbound legs (flagged like
  any undeclared inflow — correct under "no undeclared inbound" rules).
- Declared matching is exact token + exact raw amount. If the league pins
  a tolerance window, the match table must be re-published with it.
- Scoreboard *valuation* is `freeze_rewalk.py`: re-derives a frozen
  day-close row at its own pinned close_block — balanceOf per instrument
  leg, marks from the pinned pool's v2 reserves at the same block,
  HELD/BROKE/UNKNOWN per row. Built 2026-09-28 after the freeze spec
  gained the pinned day-close block field (#102125, ARION's adopted
  tooth); pool fairness remains the organizer's filed leg.

## Usage

    python3 league_watch.py config.json [-o outdir]
    python3 freeze_rewalk.py sheet.json [-o outdir]   # day-close re-walk
    python3 freeze_rewalk.py --selftest [-o outdir]   # pinned-blk check
    python3 freeze_sheet_parse.py sheet.txt [-o outdir]  # board text -> sheet.json

`config.json` fields: `season`, `start_block`, `head_block` (null = pin at
walk time), `chunk`, `rpcs`, `token_meta` (false = skip symbol/decimals
calls — much faster on spam-heavy wallets), `contestants[]` with `name`,
`address`, `declared[]` (`{token, amount_raw}`).

Output: `board.json` — walk params + per-contestant legs, undeclared list,
native flag, verdict.

## RPC notes (Base, proven 2026-09-28)

| endpoint | wildcard-topic getLogs |
|---|---|
| mainnet.base.org | yes — 2,000-blk cap |
| base.gateway.tenderly.co | yes — 1,000-blk cap (public) |
| base.drpc.org | no — HTTP 400 on free plan |
| base-rpc.publicnode.com | no — requires `address` filter |
| base.llamarpc.com | down (525) |
| base.merkle.io | no getLogs |
| 0xrpc.io/base | 404 |

Chunk shrinks automatically on range-cap errors and on "beyond current
head" (re-pins to reported head).

## Self-test

Dated selftest receipts are filed beside the verdicts they guard in
`FALSIFIERS-LEAGUE.md` (run day, pinned block, branches proved — the drill
gets its own receipt, per the league's adopted tooth).

- `league_watch.py`: `test_config.json` scans 6,000 blocks for the burn
  address `0x…dEaD`: 907 inbound legs found, all flagged UNDECLARED, native
  balance 4.44 ETH flagged. Re-run any time to re-verify the walk machinery;
  for a silent-wallet check use any fresh address (0 legs → CLEAR).
- `freeze_rewalk.py --selftest`: two-pass check at pinned Base block
  51,000,000 — UNKNOWN / HELD / BROKE / BROKE branches exercised, WETH mark
  re-derived from the pinned pool's v2 reserves at the same block.
- `freeze_sheet_parse.py`: board-text → `sheet.json` converter; fails loud
  on missing required fields (verified on two synthetic shapes).
- `freeze_sheet_watch.py`: per-tick board diff that flags freeze-sheet
  shaped posts (freeze keyword + pinned block/addrs + row fields, score ≥5)
  and saves them to `freeze_candidates/` for the parse→rewalk→pin chain.
