For Agents

Living index of themes for this project. Each H2 is a topic; bullets are wikilinks to related notes. Updated by obsidian-documenter when documenting work. Read by historian at bootstrap. Topics kept alphabetical.

Address Resolution (Phase 2A — shipped)

  • phase2-address-resolutionadapter-anchored resolution (commit 536e318): a protocol’s DeFiLlama module names its TVL adapter in the DefiLlama-Adapters GitHub repo → fetch adapter source, regex-extract 0x addresses, verify on BSCScan as the confidence gate, drop infra denylist (Pancake/WBNB/stables/CAKE), prefer Tier-1 (DeFiLlama record) over Tier-2 (adapter), cap per protocol, unresolved → needs-manual-address.csv. Auto-resolves ~60% (not ~100%), 36% of adapters 404, findings carry resolution_source. Solves the MVP bottleneck: DeFiLlama exposes no address for small protocols.

Architecture

  • contract-scanner — 5-stage pipeline (Discover → Resolve → Fetch → Analyze → Triage), each stage independently replaceable. Half the engineering lives in Stage 5 (triage), not Stage 4 (the scanner).
  • phase2-address-resolution — Phase 2A inserts automatic address resolution before Stage 3 so --slug / --batch need no manual --address; resolution pipeline diagram + tier/denylist/cap design.
  • supabase-findings-store — storage layer: ranked findings load into Supabase Postgres public.bsc_finding (per-run run_label); resolves the design doc’s deferred “storage past MVP” decision in favor of Postgres.
  • findings-loader — connects Stage-5 CSV → the Postgres store: pure build_rows (CSV→insert-tuples) + atomic replace_run (replace-by-run_label) + main CLI. Score is now carried in the CSV, so the loader is a pure transport.
  • exploit-stage-deploy-handoff — the LLM exploit-analysis stage (scanner.exploit) sits downstream of triage/storage: reads the day’s bsc_finding batch, fetches source (proxy-following Etherscan, slither-free, no compilation), pipes a prompt to the claude CLI, upserts verdicts to Postgres. Writes nothing to disk.
  • adhoc-analysis-and-queueon-demand analysis + request queue (commit 272a224): three entrypoints (daily batch, --address, --drain-requests) funnel through one extracted analyze_contract(cfg, dsn, cw, existing, run_label); the new --address open-ended-audit path is a new prompt variant; results stay in bsc_contract_analysis (no new result schema). Adds the public.bsc_analysis_request queue table.

Caveats / Honest Limits

  • contract-scanner — Slither FP rate is high; ~30–40% of small-protocol BSC contracts are source-unverified (skip in v1); “vulnerable” ≠ “exploitable” (workflow narrows 700 protocols to ~20 worth a human look, doesn’t find exploits); BSCScan free tier 5 req/sec / 100k req/day; Mythril too slow for batch; white-hat economics weak below ~5–10M most likely to actually pay).
  • phase2-address-resolutionBSCScan-verified ≠ the protocol’s own code (verification is a confidence gate, not attribution; routers/tokens/factories verify but aren’t the protocol); adapter resolution covers only ~60% (36% of adapters 404 on stale DeFiLlama module pointers); multi-chain adapters mix cross-chain addresses; full own-code attribution is deferred.
  • supabase-findings-storebsc_finding rows carry the Phase-2A resolution_source, so the same own-code-attribution caveat applies to DB leads (e.g. TheDeep’s ICHIVault arbitrary-send-erc20 resolved via the ICHIVaultFactory adapter — confirm attribution before disclosure).

Deployment / Runtime

  • adhoc-analysis-and-queue — needs a SECOND systemd timer running scanner.exploit --drain-requests every ~5–10 min (frequent, lightweight) alongside the daily 06:00 batch timer; new deploy pin 272a224. Handed to @deploy 2026-06-04, awaiting role write+dispatch — the queue worker is NOT live until this timer lands (enqueued requests sit pending).
  • exploit-stage-deploy-handoffthe exploit stage runs on the shared polymarket-infra e2-small box, provisioned by the @deploy cross-team agent (handed off 2026-06-03, accepted). Dedicated unprivileged user bsc-exploit (home /opt/bsc-exploit, no sudo); /opt/bsc-exploit/repo/ = shallow clone pinned to a specific commit (bce62380…), NOT tracking main (deliberate Ansible deploys only); uv sync --frozen --no-dev venv; Python 3.12. systemd oneshot + timer *-*-* 06:00:00 UTC, Persistent=true, after the 04:17 UTC GH scan+load; RuntimeMaxSec=7200, journald, OnFailure= alert on exit 1/2 (0 silent). NO solc-select install on the VM (stage is slither-free). Secrets via the new least-privilege GH secret BSC_EXPLOIT_ENV/opt/bsc-exploit/.env mode 0600 (3 keys: CLAUDE_CODE_OAUTH_TOKEN, DATABASE_URL, ETHERSCAN_API_KEY). Future updates: @bscvuln pings @deploy with a new SHA → bump pin + redeploy (→ workflow_dispatch later). Blocked on operator seeding the 3 secret values out-of-band.

Discovery Trail (BSC Scan, 2026-05-30)

  • contract-scanner — DeFiLlama funnel: 696 BSC protocols under 100k) → 154 total when widened to all TVL bands. Pattern: multi-chain reputable protocols with unaudited BSC port = sweet spot (parent has bounty money, BSC port is the buggy fork). Examples: Spectra V2 (40k), Deri V4 (87k), Maverick V2, Krystal, K3 Capital, Lombard Vaults.

Exploit Stage (LLM analysis)

  • proxy-source-merge-gotchafetch_analysis_source merges proxy + implementation source so Claude always sees both layers (commit pending). Fixes false needs_review rows when Slither flags a PROXY’s own code but the stage only analyzed the implementation; namespaced [proxy]/[impl] keys; non-proxy → plain source unchanged.
  • adhoc-analysis-and-queueon-demand front door to the LLM analysis layer (commit 272a224): --address 0x… analyzes any contract (finding-aware if it has High findings in the latest batch, else open-ended source audit via a new prompt variant), run_label='adhoc'; --drain-requests processes the bsc_analysis_request queue. All paths share analyze_contract. Bundles the build_source (text, truncated) fix for the misleading needs_review message.
  • exploit-stage-deploy-handoffscanner/exploit.py (commit bce6238, live-verified against a real claude -p): the LLM exploit-analysis layer downstream of triage. Slither-free — slither/solc-select are in the main closure (so --no-dev still installs them) but exploit.py never imports/invokes them (proxy-following Etherscan fetch, no compilation). Writes nothing to disk — source in memory, prompt→claude via stdin, JSON from stdout, verdicts upserted straight to Postgres; only writable need is the claude CLI’s own ~/.claude (optionally CLAUDE_CONFIG_DIR). Reads exactly 3 env vars (ETHERSCAN_API_KEY, DATABASE_URL, CLAUDE_CODE_OAUTH_TOKEN). Exit codes 0 ok / 1 DB-error|rate-limit-stop|all-failed / 2 misconfig (only 1 & 2 alert). A Claude rate-limit stops the batch cleanly and self-heals next run via the unchanged-source cache.

Findings Storage / Database

  • adhoc-analysis-and-queuenew table public.bsc_analysis_request (Supabase MCP-applied queue, NOT a result table): id, created_at, chain, contract_address, status (pending|running|done|error), error, requested_by, analyzed_at, claimed_at. RLS = authenticated insert + select (CRM enqueues + watches); the worker connects as the pooler postgres role and bypasses RLS. Analysis results still upsert to bsc_contract_analysis (no new result schema).
  • supabase-findings-storesource of truth for public.bsc_finding (Supabase project mkofmdtdldxgmmolxxhc, org “mgmt”, Postgres 17; created via MCP apply_migration create_bsc_finding). Schema mirrors the CSV output shape + DB-only run_label ('batch-YYYY-MM-DD', per-run idempotency key), resolution_source (tier1 | tier2-adapter), and a load-time score = SEV*CONF*tvl_usd (SEV {High:40,Medium:8,Low:2,Info:1}, CONF {High:3,Medium:2,Low:1} — matches config.yaml; CSV has no score column). Indexes (score desc), (contract_address), (detector). RLS on, no policies → service-role-only (anon frontend sees 0 rows). First load batch-2026-05-30: 96 findings / 8 protocols / 10 contracts (High 22 / Med 53 / Low 21), top lead BTCST incorrect-return ~75.6M. Reload = delete by run_label then re-insert.
  • findings-loader — the loader that writes to this table (python -m scanner.load): atomic replace-by-run_label, score carried in the scan CSV (no longer recomputed at load), Session-Pooler DSN. Durable fix for the hand-transcription gotcha below.

Gotchas / Bugs Avoided

  • proxy-source-merge-gotchaSlither flags the PROXY’s own code but the exploit stage fetched only the implementation → flagged snippets absent → false needs_review (NOT a budget issue: 0x9334 impl Swych was 132k, under 200k). The OZ TransparentUpgradeableProxy ifAdmin incorrect-return is a textbook FP (assembly returns). Proxy resolution is also nondeterministic across fetches (Etherscan Implementation field varies). Fix: fetch_analysis_source merges proxy + impl source with [proxy]/[impl] keys; honest “flagged code not found in the fetched proxy/implementation source within the budget” message. Refines (distinct root cause from) the whitespace-normalization fix below. After fix: bsc_contract_analysis = 16 rows, 0 needs_review.
  • adhoc-analysis-and-queuebuild_source (text, complete)(text, truncated) semantics: the old code conflated “did source fit the 200k budget” with “did every Slither snippet match as an exact substring,” emitting “source too large to retain finding code” even when the full source was present (the two 2026-06-03 proxy contracts were 37–70k chars, well under budget, but parked as needs_review). Fix: whitespace-normalized snippet matching + mark needs_review only when truncation ACTUALLY drops finding-bearing code, with an honest message. Supersedes exploit-stage-deploy-handoff’s 2026-06-03 source-budget explanation. Also: queue-worker invariants — atomic claim FOR UPDATE SKIP LOCKED, stale-row reaper (running + claimed_at < now()-30minpending), nested try/except so a failed row never strands in running, rate-limit re-queues to pending and stops the drain cleanly.
  • supabase-findings-storebulk-loading INSERTs by hand-transcription through the Supabase MCP execute_sql is error-prone: the 96-row load silently dropped one row and left a stray space in one bscscan_url (tool reported success regardless). Always verify after loading — count(*) vs expected + where col like '% %' malformed-value scan on address/url columns (valid values never contain spaces) — and prefer driving inserts from a generated SQL file over manual transcription.
  • contract-scannerDeFiLlama chain name is "Binance", not "BSC" in chains[] AND chainTvls keys (cost ~30 min in source session); DeFiLlama audits field is stale0 sometimes means “no audit,” sometimes “DeFiLlama doesn’t know”; canonical false-positive is Frax Swap (Frax core heavily audited, BSC port shows audits=0). Always cross-check the project’s own site before trusting the label.
  • verified-tool-behaviorthe high-reuse gotcha reference (empirical, several disproven assumptions that caused silent false-negatives): slither --solc-standard-json <file> does NOT load a JSON file (globs a dir) → compile in-process via crytic-compile SolcStandardJson; SOLC_VERSION per-process not solc-select use (races); serialize in-process Slither (not thread-safe); Etherscan V2 result STRING-on-error/LIST-on-success; Slither JSON has no code-snippet + non-zero exit on both fail and findings; Docker on Apple Silicon needs FROM --platform=linux/amd64 (solc-select x86_64-only → arm64 silently finds 0); pytest+uv needs pythonpath=["."].
  • phase2-address-resolution — Part-0 bugs found by pre-implementation re-audit and fixed: detect_language Solidity→Vyper substring misclassification (silent skip), malformed DeFiLlama record crashing the batch, run_slice missing exception isolation, proxy impl-fetch raise discarding recoverable analysis.
  • exploit-stage-deploy-handoffDATABASE_URL must be the Session Pooler :5432, NOT the :6543 transaction pooler for scanner.exploit (upserts with prepare_threshold=None; transaction pooler breaks prepared-statement-less upserts) — same rule as findings-loader. Also: don’t add a solc-select install step to the VM — scanner.exploit is slither-free (the deps are harmless dead weight in the venv). And “rotate the Supabase DB password” is stood down — the Round-53 rogue DB writer was the operator’s own forgotten macOS LaunchAgent com.levander.polymarket-bot, not a third party (resolved Round 54); do not resurface.

Implementation Status (shipped)

  • contract-scannerMVP + Phase 2A SHIPPED and live-validated (2026-05-30); status moved designed/planned → shipped. Status box at top of the design doc links the focused notes.
  • mvp-implementation-plan — all 10 MVP tasks shipped; Task 10 live validation unblocked (free Etherscan V2 key worked for BSC) and produced real leads on EA Finance’s wCC contract.
  • phase2-address-resolution — Phase 2A shipped (commit 536e318): --slug/--batch resolve addresses automatically; live --slug ea-finance → 16 findings, --batch --limit 8 → 116 findings across 7 protocols + 1 reported-unresolved, no crash.
  • contract-scanner — vulnerability research on deployed public contracts is broadly fine; triggering a withdraw (even to “save” funds) is a grey zone. Get team go-ahead in writing before moving real money.

MVP Scope

  • contract-scanner — BSC only, Slither only in Docker, DeFiLlama /protocol/{slug} for address resolution (skip the missing 30%), local JSON storage, CSV ranked by severity × confidence × tvl, ~50 protocols × ~5 contracts × ~10s ≈ 40 min wall-clock, ~200 lines Python+bash+Docker.

Open Questions

  • contract-scannerstorage past MVP RESOLVED → Supabase Postgres (see supabase-findings-store); scheduling RESOLVED → GH-Actions cron (scan+load, 04:17 UTC) + systemd timer (exploit stage, 06:00 UTC on polymarket-infra) (see exploit-stage-deploy-handoff); still open: address-resolution fallback for the ~30% of protocols missing from DeFiLlama’s contract list; disclosure workflow scope (email/Twitter generator? Immunefi?); triage-rule storage and versioning (per-chain YAML? per-detector?).

Operations / Loading

  • adhoc-analysis-and-queuepython -m scanner.exploit --address 0x… [--chain bsc] analyzes one contract on demand (run_label='adhoc'); python -m scanner.exploit --drain-requests [--limit N] drains the bsc_analysis_request queue. Drain is concurrency-safe (atomic claim) with a 30-min stale-row reaper and per-request error marking. To go live, @deploy must add a second ~5–10 min --drain-requests timer.
  • findings-loaderpython -m scanner.load --csv <path> --run-label <label> [--replace] [--allow-empty] [--dry-run] loads a scan’s CSV into bsc_finding. Idempotency = replace-by-run_label (atomic delete + bulk insert in one psycopg3 transaction); replacing a populated label needs --replace, 0-row loads need --allow-empty, --dry-run previews. DATABASE_URL = Supabase Session Pooler DSN + ?sslmode=require (not the IPv6-only direct endpoint, not the :6543 transaction pooler). Exit codes 0/2/1; DSN/password never printed. The one live round-trip test sits behind the db pytest marker (deselected by default). Historical score-less output/ CSVs are rejected.

Output Shape

  • contract-scanner — CSV one row per finding: chain, protocol, contract_address, contract_name, severity, detector, impact, confidence, line, code_snippet, defillama_url, bscscan_url, tvl_usd, age_days. Top of file = highest severity × confidence × tvl = the day’s triage queue.
  • supabase-findings-store — same shape persisted to Postgres public.bsc_finding, plus DB-only run_label, resolution_source, and a load-time score (the CSV has no score column). (score desc) index = the server-side triage queue.

Phases (Roadmap)

  • contract-scannerMVP (~1d): BSC + Slither. Phase 2 (after MVP shows signal): multi-chain via Etherscan v1 API family (Ethereum/Polygon/Arbitrum/Base/Optimism), Aderyn for second-opinion, Slitherin community detectors, source caching, daily diff. Phase 3 (after Phase 2 yields real bounties): Mythril/Halmos on the Slither shortlist (top 5–10/day), disclosure-draft generator, web triage UI.

Process / Validation Method

  • adhoc-analysis-and-queue — built brainstorm → writing-plans → subagent-driven TDD → two-stage review (spec-compliance + code-quality) → fix pass (8 review findings) → 37 tests pass. The two-stage review + dedicated fix pass caught real defects before merge (reaper, drain exception safety, --limit 50 sentinel bug, RateLimitError ordering, stale existing dict, double Etherscan fetch, --dry-run --address guard, live-test hygiene).
  • phase2-address-resolutionpre-implementation 3-agent validation (spec audit + empirical funnel test + MVP re-audit) caught a fundamental spec flaw (verified ≠ protocol’s-own-code) AND four real shipped bugs before building Phase 2A. High-value, cheap (3 read-only agents), worth repeating per phase.

Request Queue / On-Demand Analysis

  • adhoc-analysis-and-queue — analyze ANY contract by address on demand and a durable public.bsc_analysis_request queue so the CRM can enqueue analyses and watch status. CRM integration handed to @crm (2026-06-04): an “Analyze this contract” action inserts a row, watches status, refetches the verdict panel on done. Worker (--drain-requests) processes pending rows through analyze_contract; results land in bsc_contract_analysis (joined on contract_address). Concurrency-safe atomic claim + 30-min stale-row reaper + per-request error safety. Not live until @deploy’s second drain timer lands.

Repository / Commits

  • contract-scanner — code is in the private repo github.com/wowjeeez/bsc-vuln-scanner, local path ~/coding/bsc-vuln-scanner/ (hyphen). Commits: 5be49b6 (MVP initial), 536e318 (Phase 2A: adapter-anchored resolution + robustness hardening), bce6238 (LLM exploit-analysis stage scanner/exploit.py, the SHA the VM deploy is pinned to), 272a224 (on-demand --address analysis + --drain-requests queue worker + build_source needs_review fix; the new deploy pin for the drain timer).
  • adhoc-analysis-and-queue — commit 272a224 (pushed to main): the new deploy pin @deploy will use for the --drain-requests timer. Plan docs/superpowers/plans/2026-06-04-adhoc-analysis.md, spec docs/superpowers/specs/2026-06-04-adhoc-analysis-design.md.
  • exploit-stage-deploy-handoff — the VM deploy is pinned to commit bce62380a5bdf759facb512b0ba809180c63f79e (scanner.exploit), deliberately NOT tracking main. External infra: the @deploy cross-team agent owns the runtime on the polymarket-infra box; secrets live in a new GH-Actions secret BSC_EXPLOIT_ENV in wowjeeez/terraform.
  • supabase-findings-store — external infra: Supabase project mkofmdtdldxgmmolxxhc (org “mgmt”, Postgres 17) holds the findings table public.bsc_finding. Schema lives in Supabase (migration create_bsc_finding), not in the repo.

Targets

  • contract-scannerTop-5 starting targets: EA Finance (Liquid Restaking, 135d, BSC-only, novel-category — recommended first; validated), TurboFlow (Derivatives, 205d, +25%/7d), Spectra V2 BSC port (6.7M), Deri V4 BSC port (Options). EA Finance is a LayerZero OFT, wCC contract 0x6050d829f5a5e0ea758d8357ddcdec1381699248 — the MVP found divide-before-multiply + unused-return there.

Tools

  • contract-scannerSlither (MVP workhorse, Trail of Bits, high FP rate is the cost), Aderyn (Phase 2, Cyfrin, Rust-based, complementary detectors), Slitherin (Phase 2, community-extended Slither), Mythril (Phase 3, symbolic execution, minutes-to-hours per contract), Halmos (Phase 3, needs Foundry tests), Sourcify / smart-contract-sanctuary (Phase 2 fallback when chainscan rate limits hurt).
  • verified-tool-behaviorverified runtime behavior of Slither, solc-select/SOLC_VERSION, crytic-compile SolcStandardJson, Etherscan V2 (chainid=56), DeFiLlama chainTvls, Docker amd64-on-Apple-Silicon, and pytest+uv — as actually observed during the build.

Triage Layer

  • contract-scanner — Stage 5: dedupe across proxy patterns, severity × confidence weighting, drop known FPs (OZ standard library reentrancy-events, etc.). This is where half the engineering effort lives, not in the scanner itself.

Implementation Plan

  • mvp-implementation-plan — 10-task TDD plan (2026-05-30); all tasks SHIPPED (Task 10 live validation unblocked + passed). Plan file: /Users/levander/coding/bsc-vuln-scanner/docs/superpowers/plans/2026-05-30-mvp-bsc-scanner.md. Build strategy: vertical-slice-first — one real contract (EA Finance) end-to-end (Tasks 2–7) before the batch funnel (Task 8). New tech decisions: pin Python 3.12 (not system 3.14) via uv (Slither lags 3.14); Etherscan V2 unified API (chainid=56, single key, config-driven base URL) instead of legacy per-chain BscScan → Stage 3 already Phase-2-multichain-ready; Slither as a PATH subprocess (local uv == final container) with the whole pipeline in ONE Dockerfile at the end (Task 9); API key is a placeholder (build/test on fixtures, live EA Finance validation deferred to Task 10, blocked on a free Etherscan key); each stage splits pure logic (unit-tested) from I/O (thin wrappers). Cross-refs existing topics: Architecture, Gotchas (Binance-not-BSC regression test in Task 2), Tools (Slither/solc-select), Triage Layer (Stage 5 = largest task), Targets (EA Finance), Phases (Phase-2-ready Stage 3).

Project Location / Environment

  • mvp-implementation-plan — actual project folder is ~/coding/bsc-vuln-scanner (hyphen), NOT the design doc’s ~/coding/contract_scanner (underscore) — using the existing hyphen folder. Verified dev box state (2026-05-30): folder was empty / not a git repo; uv 0.10.9; Docker 28.5.2 daemon running; Slither not previously installed (added via uv add slither-analyzer solc-select); no chain API keys in env.