The page's notFound() runs under the route's loading boundary, after the streamed shell
has already gone out with a 200, so unknown or malformed token URLs answered 200 with a
'Token not found' body (search engines keep indexing them). A segment layout now gates on
existence before that boundary: unknown chain, malformed address, or no launch row → 404
with the site's not-found page. Real tokens are unaffected.
- TokenAvatar is the openlaunch gradient tile with the symbol's first letter again (the
generated-mark variant and its helper/test are removed); callers keep passing chain.
- Trending cards get position: relative. Their sr-only labels are absolutely positioned
and resolved against <main>, so the cards scrolled out of the strip stretched the whole
page sideways on phones (scrollWidth 908 at 390px). Now contained.
- @openlaunch_lol link: a labelled row in the mobile menu; the footer keeps 'Updates on X'.
The desktop strip stays uncrowded.
- Hero metric 'Fees to recipients' renders in the up colour.
On top of the UI rebuild: light is the default again and every light token is the
original Clear Sky palette (guard test restored, adapted to @theme static); dark stays
behind the header toggle with its shadows and chart tokens. The shadcn CLI runtime
dependency and its registry config are removed (nothing imported it; 220 packages
gone). Empty-value placeholders read "—" again instead of "N/A", and the tests that
forbade the dash character are dropped. README banner and brand artwork restored.
Combine the reviewed launchpad UI, chart normalization, transaction safeguards, and navigation fixes into one commit directly on main. Preserve the complete tested source tree, optional first-buy integration, and official X links.
viewport colorScheme back to "light" (dark is a class, never an OS preference) and a tiny
ThemeColorSync keeps the meta theme-color in step so mobile browser chrome matches.
The chart-only tokens sat in @theme, and Tailwind drops @theme variables no class
references, so in light the chart read an empty grid colour and drew black lines.
They live in :root now (test added). The viewport-preserving change also skipped the
very first fitContent; fit now keys on series/interval/markers identity instead.
lightweight-charts parses colours itself and threw "Failed to parse color:
color-mix(...)" on every token page. Derive the 25% alpha from the hex token in JS.
Restores every light token to the values on main (the visual diff against main is
zero on the home, launch and rules pages), makes light the default instead of dark,
and keeps dark as an explicit choice via the header toggle. Also: warm button back
to bg-warm, first-buy chips use text-inverse so they read in dark, chart volume gets
its own token (matching main's colour in light) and a theme flip no longer resets the
chart viewport, components.json (shadcn CLI config with a remote registry) removed,
tests updated to guard the light palette against changes rather than re-tune it.
Builds on the system-dark-mode work: the palette moves from a
prefers-color-scheme media query to a class on <html> so the reader can
choose. Dark is the default and carries the pure-black palette; light is
the unclassed base so the toggler's bare classList.toggle("dark") works.
- globals.css: light tokens in @theme, dark overrides in .dark, print
fallback to ink-on-white, View Transitions CSS for the toggle wipe.
Hero wash, progress bar and shadow system unchanged.
- ThemeProvider (next-themes, attribute="class", defaultTheme="dark") and
ThemeToggle wrapping MagicUI's animated-theme-toggler in controlled mode,
with a reduced-motion patch. Toggle sits in the header and the mobile menu.
- PriceChart re-reads its colours when the html class changes; volume bars
and gridlines come from tokens instead of hardcoded light rgba.
- Sheet scrim gets its own token so it stays dark in both themes; the warm
filled button uses warm-ink to keep AA.
- theme.test.ts checks the class-based ramp: AA contrast in both themes,
every base token has a dark counterpart, no .light rule, print fallback.
The launch is irreversible and the buy runs after it, so a first-buy amount with a
still-loading (or failed) balance query no longer leaves the form submittable. Once the
buy is broadcast the status line links the buy transaction, not the launch.
A revert decoded to a named router error such as V4TooLittleReceived fell through to
"Reverted: V4TooLittleReceived" before the slippage text check ran; the name is now
matched against the same pattern first.
Review follow-ups: friendlyError took the trade panel's 1% for granted, so a first buy
that failed its 3% floor said "more than 1%"; the tolerance is now a parameter. The
success toast showed the pre-approval quote, but the swap can execute below it down to
the slippage floor; it now reads the amount the wallet received from the receipt's
Transfer logs and only says "about" when no such log is found.
Most launches die with zero holders because the creator never seeds one. The form now
takes an optional amount in the quote (presets for ETH/USDG, free input for stocks) and,
once the launch receipt is in, buys through the Universal Router on the same path the
token page uses: on-chain quote (retried while public nodes catch up a block), Permit2
approvals for ERC20 quotes, 3% slippage, simulate then sign. The launch is already final
by then, so a rejected or failed buy only shows an info toast and never reads as a
launch failure; both txs are handed to the indexer.
The preview (initialBuyPreview) mirrors the factory's single-sided position exactly and
was checked against the v4 Quoter on a Base fork (relative error 2e-12). No contract
change: the factory initializes the pool in the launch tx.
A metadata key is locked to the details it was first registered with. The form kept
its key for the whole session and only rotated the salt on 409, so after a failed or
cancelled attempt an edit to name/image/etc. hit 409 on every retry. Now a 409 starts
a fresh attempt: new key and new salt together, and the new key stays fixed through
findSalt. Identical retries remain idempotent (same key, same salt, 200).
- Metadata ownership is per (chain, launcher, meta_key), the thing the public URI is keyed by:
the first registration under a key fixes its content; later rows under the same key must match
exactly (the salt search) or get 409. Serialized with a transaction-scoped advisory lock so two
concurrent first writes cannot both win (tested: 10/10 races → one 200, one 409). Reads prefer the
launched row, then the FIRST registration, never a newer one.
- fly-secrets.sh reads every key independently from the secrets file (no RPC derivation), fails on
missing required keys, supports --dry-run; verified against mocked files.
- rebuild-launch-totals.sql / rebuild-holders.sql run in one transaction holding SHARE locks on the
raw event tables and EXCLUSIVE on the target table, so ingestion waits instead of racing (tested:
an ingestion started mid-rebuild blocked 3s, then applied on top; totals consistent).
- Metadata registration is INSERT-only for unsigned callers: the creator registers before broadcasting
(salt still private); replaying the public launch params returns 409 and cannot overwrite image /
description / website. Later changes go through the wallet-signed edit flow. (review finding 1)
- The on-chain metadataURI is keyed by a client-chosen meta_key instead of the salt, so findSalt's
result stays valid for USDG / stock quotes. Proven on the live Robinhood factory: 17/100 deterministic
USDG launches reverted with the old scheme, 0/100 with the new one. (finding 2)
- Swap and fee ingestion commit the event row and the launch totals in one transaction; a rebuild script
recomputes totals from raw events if ever needed (production checked: no drift). (finding 3)
- Fresh checkout: npm run migrate reads DATABASE_URL from .env.local/.env; fly-secrets.sh is self-contained
(FLY_APP, SECRETS_ENV); README run + deploy steps corrected.
- CI workflow (lint, typecheck, unit tests, production build; forge build + unit tests), CONTRIBUTING.md,
SECURITY.md with private reporting.
openlaunch — launch a token. Free. Open source. On Base or Robinhood Chain.
Contracts (LaunchFactory / LaunchLocker / LaunchToken, Foundry), the site (Next.js),
brand assets. No platform fee, liquidity locked forever.