Skip to main content
The app in apps/web is a Next.js App Router project using wagmi, viem, TanStack Query and RainbowKit. It serves every chain that has a deployment file at once; the top-bar switcher picks one and every hook and component reads that chain’s RPC, addresses and indexer through useChain().

Environment

apps/web/.env.example documents every variable. NEXT_PUBLIC_* values are inlined at build time, so rebuild after changing any of them.

Build and start

pnpm dev and pnpm build both start with scripts/gen-abis.mjs, which re-emits the JSON ABIs from @launchpad/abis as as const TypeScript (src/lib/abis.generated.ts, plus a union of every custom error so nested reverts decode) and writes src/lib/chains.generated.ts with the ids of the chains that have a deployment file. Run pnpm --filter @launchpad/web gen on its own after pnpm abis changes the ABIs. On the production box pm2 runs the JS entry directly, because pnpm’s .bin shims are shell scripts:
After changing apps/web/.env.local: rebuild, then pm2 restart windrose-web. If the build gets killed for lack of memory while three indexers backfill, stop one (pm2 stop windrose-indexer-arc), build, start it again.

Which chains appear

Three things decide what the switcher offers:
  1. A deployment file at build time. scripts/gen-abis.mjs scans packages/abis/deployments/*.json and bakes the chain ids into src/lib/chains.generated.ts; the wagmi config registers those chains when the module loads. Add a file, rebuild.
  2. Testnet visibility. A mainnet default hides testnets unless NEXT_PUBLIC_SHOW_TESTNETS=1. A testnet default lists everything.
  3. Coming soon. In a mainnet build, registry mainnets without a deployment (Arc, 5042, today) are listed as “Coming soon” and disabled.
GET /api/chains re-checks the files on every request so the UI can annotate a chain whose file appeared after the server started, and GET /api/deployment?chainId=<id> reads the addresses lazily (falling back to the NEXT_PUBLIC_* addresses for the default chain). Both are documented under HTTP endpoints.
Next.js only inlines literal process.env.NEXT_PUBLIC_* reads, so a chain id needs a row in both RPC_ENV and INDEXER_ENV of apps/web/src/lib/chain.ts before NEXT_PUBLIC_RPC_URL_<id> and NEXT_PUBLIC_INDEXER_URL_<id> work in the browser. Add the rows when you add a chain.

Copy driven by the registry

Everything that names the dollar, the chain, the gas token or the faucet comes from packages/abis/chains.json through useChain(): “Gas is paid in ETH”, “Get test USDG”, the faucet link, explorer links. Brand strings (“Windrose”) are not. The dollar’s logo is dollar.logo from the registry or /tokens/<symbol>.svg; the CoinLogo component falls back to initials when the file is missing.

Token logos and token lists

abis:logos never overwrites an existing USDC.svg or EURC.svg unless --force is passed. abis:tokenlist reads names, symbols and decimals from the chain (falling back to the deployment file and Currencies.sol with --offline), names the file after the registry key (robinhood.tokenlist.json) and bumps the version per the token-list rules, so re-running is a no-op when nothing changed. The naming prefixes of the coins are read back from the deployment file (synthNamePrefix, synthSymbolPrefix).

SEO files

  • /og.png is the Open Graph image referenced by the root metadata.
  • robots.ts allows everything except /api/ and points at the sitemap.
  • sitemap.ts lists the static pages plus one currency page (/c/<CODE>) per registered currency of the default chain, read from its deployment file, and revalidates hourly.
The Docs entry in the rail, the footer link and the “How it works” tile open NEXT_PUBLIC_DOCS_URL in a new tab, and /guide (the old in-app docs page) redirects there, so bookmarks keep working. The docs are hosted by Mintlify at docs.windrose.market and live in apps/docs of this repository; see Self-hosting for the DNS shape.