For Agents
Living index of themes for this project. Each H2 is a topic; bullets are wikilinks to related notes. Updated by
obsidian-documenterwhen documenting work. Read byhistorianat bootstrap. Topics kept alphabetical.
Address Resolution (Phase 2A — shipped)
- phase2-address-resolution — adapter-anchored resolution (commit
536e318): a protocol’s DeFiLlamamodulenames its TVL adapter in the DefiLlama-Adapters GitHub repo → fetch adapter source, regex-extract0xaddresses, 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 carryresolution_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/--batchneed 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-runrun_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) + atomicreplace_run(replace-by-run_label) +mainCLI. 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’sbsc_findingbatch, fetches source (proxy-following Etherscan, slither-free, no compilation), pipes a prompt to theclaudeCLI, upserts verdicts to Postgres. Writes nothing to disk. - adhoc-analysis-and-queue — on-demand analysis + request queue (commit
272a224): three entrypoints (daily batch,--address,--drain-requests) funnel through one extractedanalyze_contract(cfg, dsn, cw, existing, run_label); the new--addressopen-ended-audit path is a new prompt variant; results stay inbsc_contract_analysis(no new result schema). Adds thepublic.bsc_analysis_requestqueue 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-resolution — BSCScan-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-store —
bsc_findingrows carry the Phase-2Aresolution_source, so the same own-code-attribution caveat applies to DB leads (e.g. TheDeep’sICHIVaultarbitrary-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-requestsevery ~5–10 min (frequent, lightweight) alongside the daily 06:00 batch timer; new deploy pin272a224. Handed to @deploy 2026-06-04, awaiting role write+dispatch — the queue worker is NOT live until this timer lands (enqueued requests sitpending). - exploit-stage-deploy-handoff — the exploit stage runs on the shared
polymarket-infrae2-small box, provisioned by the @deploy cross-team agent (handed off 2026-06-03, accepted). Dedicated unprivileged userbsc-exploit(home/opt/bsc-exploit, no sudo);/opt/bsc-exploit/repo/= shallow clone pinned to a specific commit (bce62380…), NOT trackingmain(deliberate Ansible deploys only);uv sync --frozen --no-devvenv; 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). NOsolc-select installon the VM (stage is slither-free). Secrets via the new least-privilege GH secretBSC_EXPLOIT_ENV→/opt/bsc-exploit/.envmode 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_dispatchlater). 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-gotcha —
fetch_analysis_sourcemerges proxy + implementation source so Claude always sees both layers (commit pending). Fixes falseneeds_reviewrows 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-queue — on-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-requestsprocesses thebsc_analysis_requestqueue. All paths shareanalyze_contract. Bundles thebuild_source(text, truncated)fix for the misleadingneeds_reviewmessage. - exploit-stage-deploy-handoff —
scanner/exploit.py(commitbce6238, live-verified against a realclaude -p): the LLM exploit-analysis layer downstream of triage. Slither-free — slither/solc-select are in the main closure (so--no-devstill installs them) butexploit.pynever imports/invokes them (proxy-following Etherscan fetch, no compilation). Writes nothing to disk — source in memory, prompt→claudevia stdin, JSON from stdout, verdicts upserted straight to Postgres; only writable need is theclaudeCLI’s own~/.claude(optionallyCLAUDE_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-queue — new 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 =authenticatedinsert + select (CRM enqueues + watches); the worker connects as the pooler postgres role and bypasses RLS. Analysis results still upsert tobsc_contract_analysis(no new result schema). - supabase-findings-store — source of truth for
public.bsc_finding(Supabase projectmkofmdtdldxgmmolxxhc, org “mgmt”, Postgres 17; created via MCPapply_migrationcreate_bsc_finding). Schema mirrors the CSV output shape + DB-onlyrun_label('batch-YYYY-MM-DD', per-run idempotency key),resolution_source(tier1|tier2-adapter), and a load-timescore = SEV*CONF*tvl_usd(SEV{High:40,Medium:8,Low:2,Info:1}, CONF{High:3,Medium:2,Low:1}— matchesconfig.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 loadbatch-2026-05-30: 96 findings / 8 protocols / 10 contracts (High 22 / Med 53 / Low 21), top lead BTCSTincorrect-return~75.6M. Reload = delete byrun_labelthen 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-gotcha — Slither flags the PROXY’s own code but the exploit stage fetched only the implementation → flagged snippets absent → false
needs_review(NOT a budget issue:0x9334implSwychwas 132k, under 200k). The OZTransparentUpgradeableProxyifAdminincorrect-returnis a textbook FP (assembly returns). Proxy resolution is also nondeterministic across fetches (EtherscanImplementationfield varies). Fix:fetch_analysis_sourcemerges 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, 0needs_review. - adhoc-analysis-and-queue —
build_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 asneeds_review). Fix: whitespace-normalized snippet matching + markneeds_reviewonly 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 claimFOR UPDATE SKIP LOCKED, stale-row reaper (running+claimed_at < now()-30min→pending), nested try/except so a failed row never strands inrunning, rate-limit re-queues topendingand stops the drain cleanly. - supabase-findings-store — bulk-loading INSERTs by hand-transcription through the Supabase MCP
execute_sqlis error-prone: the 96-row load silently dropped one row and left a stray space in onebscscan_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-scanner — DeFiLlama chain name is
"Binance", not"BSC"inchains[]ANDchainTvlskeys (cost ~30 min in source session); DeFiLlamaauditsfield is stale —0sometimes means “no audit,” sometimes “DeFiLlama doesn’t know”; canonical false-positive is Frax Swap (Frax core heavily audited, BSC port showsaudits=0). Always cross-check the project’s own site before trusting the label. - verified-tool-behavior — the 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-compileSolcStandardJson;SOLC_VERSIONper-process notsolc-select use(races); serialize in-process Slither (not thread-safe); Etherscan V2resultSTRING-on-error/LIST-on-success; Slither JSON has no code-snippet + non-zero exit on both fail and findings; Docker on Apple Silicon needsFROM --platform=linux/amd64(solc-select x86_64-only → arm64 silently finds 0); pytest+uv needspythonpath=["."]. - phase2-address-resolution — Part-0 bugs found by pre-implementation re-audit and fixed:
detect_languageSolidity→Vyper substring misclassification (silent skip), malformed DeFiLlama record crashing the batch,run_slicemissing exception isolation, proxy impl-fetchraisediscarding recoverable analysis. - exploit-stage-deploy-handoff —
DATABASE_URLmust be the Session Pooler:5432, NOT the:6543transaction pooler forscanner.exploit(upserts withprepare_threshold=None; transaction pooler breaks prepared-statement-less upserts) — same rule as findings-loader. Also: don’t add asolc-select installstep to the VM —scanner.exploitis 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 LaunchAgentcom.levander.polymarket-bot, not a third party (resolved Round 54); do not resurface.
Implementation Status (shipped)
- contract-scanner — MVP + 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/--batchresolve addresses automatically; live--slug ea-finance→ 16 findings,--batch --limit 8→ 116 findings across 7 protocols + 1 reported-unresolved, no crash.
Legal / Ethics
- 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 byseverity × confidence × tvl, ~50 protocols × ~5 contracts × ~10s ≈ 40 min wall-clock, ~200 lines Python+bash+Docker.
Open Questions
- contract-scanner —
storage past MVPRESOLVED → Supabase Postgres (see supabase-findings-store);schedulingRESOLVED → GH-Actions cron (scan+load, 04:17 UTC) + systemd timer (exploit stage, 06:00 UTC onpolymarket-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-queue —
python -m scanner.exploit --address 0x… [--chain bsc]analyzes one contract on demand (run_label='adhoc');python -m scanner.exploit --drain-requests [--limit N]drains thebsc_analysis_requestqueue. Drain is concurrency-safe (atomic claim) with a 30-min stale-row reaper and per-requesterrormarking. To go live, @deploy must add a second ~5–10 min--drain-requeststimer. - findings-loader —
python -m scanner.load --csv <path> --run-label <label> [--replace] [--allow-empty] [--dry-run]loads a scan’s CSV intobsc_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-runpreviews.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 thedbpytest marker (deselected by default). Historical score-lessoutput/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 = highestseverity × confidence × tvl= the day’s triage queue. - supabase-findings-store — same shape persisted to Postgres
public.bsc_finding, plus DB-onlyrun_label,resolution_source, and a load-timescore(the CSV has no score column).(score desc)index = the server-side triage queue.
Phases (Roadmap)
- contract-scanner — MVP (~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 50sentinel bug, RateLimitError ordering, staleexistingdict, double Etherscan fetch,--dry-run --addressguard, live-test hygiene). - phase2-address-resolution — pre-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_requestqueue so the CRM can enqueue analyses and watchstatus. CRM integration handed to @crm (2026-06-04): an “Analyze this contract” action inserts a row, watchesstatus, refetches the verdict panel ondone. Worker (--drain-requests) processes pending rows throughanalyze_contract; results land inbsc_contract_analysis(joined oncontract_address). Concurrency-safe atomic claim + 30-min stale-row reaper + per-requesterrorsafety. 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 stagescanner/exploit.py, the SHA the VM deploy is pinned to),272a224(on-demand--addressanalysis +--drain-requestsqueue worker +build_sourceneeds_reviewfix; the new deploy pin for the drain timer). - adhoc-analysis-and-queue — commit
272a224(pushed tomain): the new deploy pin @deploy will use for the--drain-requeststimer. Plandocs/superpowers/plans/2026-06-04-adhoc-analysis.md, specdocs/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 trackingmain. External infra: the @deploy cross-team agent owns the runtime on thepolymarket-infrabox; secrets live in a new GH-Actions secretBSC_EXPLOIT_ENVinwowjeeez/terraform. - supabase-findings-store — external infra: Supabase project
mkofmdtdldxgmmolxxhc(org “mgmt”, Postgres 17) holds the findings tablepublic.bsc_finding. Schema lives in Supabase (migrationcreate_bsc_finding), not in the repo.
Targets
- contract-scanner — Top-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-scanner — Slither (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-behavior — verified runtime behavior of Slither,
solc-select/SOLC_VERSION, crytic-compileSolcStandardJson, Etherscan V2 (chainid=56), DeFiLlamachainTvls, 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) viauv(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 (localuv== 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;uv0.10.9; Docker 28.5.2 daemon running; Slither not previously installed (added viauv add slither-analyzer solc-select); no chain API keys in env.