Skip to main content

Session — Stadium: the launchpad end to end

Open, 20 Sep 2026. A planning thread, not a building one. It produced notes/PLAN.md, the decisions D33–D39, and a services/ section in PROCESS.md. Nothing was built.

The product acquired a name — Stadium — from a design demo the developer commissioned (tmp/Stadium-Money-Launchpad.html, a single 12.9 MB self-contained SPA built against commit 242d046). It is the reference for v1's scope.

What the demo turned out to be

Worth recording, because the file is large and reading it again is expensive. Eight routes — Discover, Launches, The lab, Watchlist, Portfolio, FAQ, Create, Token — and a window.stadiumProtocol adapter it expects someone to supply: connectWallet, getQuoteAssets, getLaunchConfig, createLaunch, quoteTrade, trade. It references an INTEGRATION.md that is not in the repo.

Its create flow is our contracts, faithfully. The five mechanics are our five modules with our field names and limits; the fee model is D17; the graduation copy is D23. That half is buildable against what exists.

Its lab is ten hooks we have never built, and it is not a shopping list:

  • Auto-Compounding LP says "runs on a schedule with a keeper fee" — D1 forbids it outright.
  • Circuit Breaker pauses trading — breaks I1, the exit guarantee.
  • Gated Launch allowlists early buyers — contradicts the no-exemption rule (D32).
  • Dynamic Fee needs afterInitialize, a flag this hook cohort did not mine. Permanently impossible here, not merely unbuilt.

The rest — TWAMM, limit orders, holder rebate, auto-compounding, vesting lock, TWAP oracle — need dispatching liquidity callbacks, which our hook reserves as no-op flags.

It also contradicts itself: the create form offers ARB, nine Robinhood stocks and RHUB, while the catalog's tokens are paired ARB/USDC/ETH/RHUB and no stock-paired token appears anywhere.

Settled

Recorded properly as D33–D39; this is the index.

D33master is a permanent staging environment, services included
D34Services are Rust, Compose per environment, named volumes
D35Supabase managed, CLI-driven, never the dashboard
D36Everything generated — Supabase types, Rust ABI bindings from contracts/out
D37The web is rebuilt beside the old one; chain layer and token layer isolated
D38The indexer observes. Nothing settles against it
D39ARB and RHUB only; no new hook this cohort

And in PLAN.md, the spine: S1 the read surface (LaunchLens ships, static/live split, USD through the pools and owned by the indexer), S2 the data contract, S3 the local stack.

Two answers that arrived late and are already folded in: staging gets its own free-tier Supabase project, and the watchlist is per wallet, which is why "how a wallet session is authorised" is now an open question rather than a detail.

Tried and rejected — do not relitigate

  • Node for the first indexer, Rust later. Argued and overruled; the reasoning on both sides is in D34 so it is not argued a third time.
  • A reduced portfolio for v1 — holdings without cost basis or P&L. The developer wants the whole thing. Noted cost: per-wallet trade history is the heaviest thing we index and the easiest to get subtly wrong.
  • An off-chain USD price feed. Rejected for pool-derived prices, even though D38 would have permitted it.
  • Keeping the existing web and theming it. Rejected for a rebuild (D37); Mantine's look is what makes a redesign expensive.
  • Badges and points in v1. The developer's own idea, and his own call to defer it.
  • Serving the catalog from the chain. The Lens exists for the local stack, the token page and the indexer's reconciliation — not as the catalog API.

Closed at the end of the session

  • Wallet sessions — D40. SIWE-shaped: one signature per session, an edge function mints a Supabase JWT carrying the wallet, RLS enforces it. Rejected: the address as a plain column, and a function invocation on every write.
  • The public internal docs site — D8 amended. Password-protected, notes/ stays put. The GitHub repository is private (stealthy-town/stadium), so the site was the only leak.

Still open

  • Whether Deployment Protection is on the current Vercel plan. If it needs a paid one, the internal site stops being hosted rather than staying open.
  • CLAUDE.md's stack table still says "Services | Node." D34 supersedes it; the developer will word the change.

Found while reading, not yet fixed

Cheap to fix, and each is a place the notes lie about the code:

  • ARCHITECTURE.md still describes a hookless pool and IHooks = address(0).
  • FEATURES.md says pools are quotable in Uniswap's own interface (D32 made that false), and marks anti-snipe, the web and the router-in-the-deploy-script as unbuilt.
  • VISION.md milestone one still cites D15's observing-only hook.
  • GraduationTaxModule's header claims a module only runs on the pool; the curve seam exists.
  • FeeVault.onFeeReceived is called by nothing in production — the strategy notification seam is unreachable.
  • Deploy.s.sol mines a hook address with no guard against the 0x91 prefix D15 forbids.
  • Nothing in the deploy path verifies contracts, though the Uniswap allowlist route needs it.

Learned while building the screens

  • A rollup-logic change is recomputed by nothing. rollup::candles and friends run only on a pass that recorded trades, so changing the arithmetic leaves every existing row as it was and the chart keeps drawing last week's answer. The replay path is update indexer.state set last_block = 0 — seconds on a local chain, and the same property the sweep relies on.

  • A candle opens at the previous bar's close, not at its own first trade. On a curve the price between trades is exactly the last trade's price, so this is the truth rather than a smoothing convention. Before it, every seeded bar was a bodiless line.

  • app/.prettierrc pins 100 columns. There was no config, so yarn format rewrapped the whole app at prettier's default 80 and format:check had never been run.

  • A curve preview is not a quote. BondingCurve.quoteBuy cannot include the module charge — onCurveTrade is not a view — so on a launch running the fee module it said 657,503 tokens where the trade gives 644,360. It also cannot tell you a size module would refuse the trade outright. Simulating the call that is about to be sent is the only quote that cannot drift.

  • eth_simulateV1 quotes an unapproved trade. The approval goes in the bundle, so a first-time trader gets a number before approving anything; a state override supplies ether so a quote does not depend on a funded wallet. Proven against the local chain for both the curve and the ETH route through v3.

  • .workspace header reached every page's header. A descendant selector in the shell gave each page's own <header> the shell bar's height and right alignment, which reads as the page's CSS not working rather than as the shell overreaching. Now .workspace > header.

  • curation is filled by nothing, so a fresh local stack has no podium and no endorsements and no way to tell that apart from a bug. make dev-curate fills it by rule.

  • A simulation that does not revert is not a correct encoding. Every field of a module's Terms pads to a word, so swapping two of them produces bytes the module decodes happily and stores backwards. A tax module meant as 1% on buys and 5% on sells, encoded the other way round, simulates clean. The fix is to read the module back inside the same simulation and phrase what it says — which is the disclosure the token page already does, moved to where it is still fixable. (AntiSnipeModule happened to revert on the same mistake, but only because 9000 seconds exceeds its window cap — a range check, not the encoding.)

  • The seeder only ever bought. Every candle was green, every portfolio had realised nothing, the ticker had no sells and the activity table had one word in it — which is indistinguishable from those paths being broken. It now sells out of a quarter of the positions it opens, and has to check locked() first: the buy immediately before can be the one that graduates the curve, so the state the caller decided on is not the state the sell would find. 47 sells, 16 positions with realised profit.

  • The watchlist's RLS was proved from the wrong side. Nonce, signature, JWT, insert, read-back all work — but the property that matters is what a client cannot do. Anon with no session reads []; a signed-in wallet asking for another wallet's rows reads [] rather than leaking; writing a row as another wallet is 42501, refused by the policy rather than by the client.

  • signIn() was called bare from the star. Declining the wallet prompt became an unhandled rejection with nothing on screen. Refusing is a choice, so it is now caught and said plainly.

  • The mechanic catalogue was written three times — token page, create page, lab — and had already started to drift in wording. chain/mechanics.ts now owns the sentence and the scope; chain/create.ts keeps only the form fields and the encoding, joined by key.

  • The lab states a limitation in public: fees are accounted per asset rather than per launch, so a strategy would see more than its own, and until that changes the strategies are ours. Not a vulnerability — there is no third-party strategy to abuse it — but it is public-facing copy about an open design gate, and worth a second opinion before launch.

  • A FAQ that quotes a setting has to read it. Fees, the supply split and the ceiling are all owner-settable, so they come from baseConfig, poolCreatorShareBps and the curve's MAX_FEE_BPS rather than from a sentence. Verified against the chain: 1% / 1%, 10% ceiling, 60/40 split, 50% of pool fees to the creator.

  • The FAQ says plainly that nothing is audited. Deliberate, and worth keeping through launch.

  • The responsive pass was measured, not looked at. documentElement.scrollWidth against clientWidth, per route, at 320 and 390 — which found two overflows no screenshot showed: the chart column and the discover hero, both refusing to shrink below their content because a grid item's min-width is auto and the chart canvas keeps its last measured width.

  • A narrow viewport is not reachable by resizing Chrome's window (a minimum width, and page zoom decouples innerWidth from the window). An iframe of the chosen width is, and it needs a realistic height too or the fixed bottom rail lands off-screen and looks missing. That is what 5.1's note about the tooling was about; it is solved.

  • The bottom rail clipped every label to four letters at phone width: as flex children the links shrank instead of the row scrolling, so the navigation looked broken rather than long.

Next

This thread is closed. Phases 0–3 are done: contracts, indexer, database, local stack and all eleven screens, proved against a seeded local chain of 305 launches.

Phase 4 continues in notes/sessions/staging-deploy.md, with notes/DEPLOY-STAGING.md as its runbook.

Handed over unproved: the wallet write path. Every trade, launch and metadata write is simulated against the local chain and correct there, but writeContract → receipt → invalidate has never once run. And make check still points at web/, so CI has never typechecked app/.