The launchpad has four parts. Contracts on Base (a factory, a Uniswap v4 hook and a small router) hold every rule that matters: supply, liquidity, fees and who may claim them. An indexer follows Base, waits for confirmations and stores confirmed launches, swaps, transfers and fee events. The web app renders pages and a public JSON API from those rows and talks to the chain only for quotes and live pool state. A shared core library holds the stock registry, the price math and the event decoders used by both.
The web app never writes to the database and never trusts client input for anything that is displayed. What you see is either a confirmed event, a number derived from confirmed events, or a live eth_call.
StockPairFactory is Ownable2Step. The owner can register stocks (address, Chainlink feed, symbol, decimals), enable or disable a stock for new launches, set the creation fee (capped at 0.01 ETH), set the opening valuation (between $100 and $1,000,000) and change the treasury. The owner cannot touch any launched token or pool, and the hook address can be set exactly once.
StockPairHook implements beforeInitialize (only the factory may create pools with this hook), beforeSwap and afterSwap with return deltas. It charges the fee on whichever side of the swap is the stock, mints the fee to itself as ERC-6909 claims inside the PoolManager and books 70% to the creator and 30% to the treasury. claim(stock) and claimMany(stocks) burn the claims and transfer real stock tokens to the caller.
StockPairRouter exposes swapExactIn(poolKey, zeroForOne, amountIn, minAmountOut, recipient, deadline). It pulls the input token from the caller, runs the swap through the PoolManager and pays the output to the recipient. Any Uniswap v4 router can trade these pools; this one is just the simplest.
launch(LaunchParams) takes a name, a symbol, an ERC-7572 contractURI, the stock address and a 32-byte salt, and must be sent with exactly the creation fee. In one transaction the factory:
createB20: version 1, 18 decimals, admin set to the zero address, and three bootstrap calls that cap the supply at 1,000,000,000, mint it to the factory and store the contractURI. The token address is predictToken(creator, salt), so the UI can open the token page before the block lands.sqrtPriceX96 so the full supply is worth the configured valuation in USD.0xdead, records the launch, registers the pool with the hook (which starts the anti-snipe clock), forwards the creation fee to the treasury and emits Launched.The factory keeps the position forever; it has no function that calls modifyLiquidity with a negative delta.
On a buy (stock in, token out) the fee is taken from the stock input in beforeSwap. On a sell (token in, stock out) it is taken from the stock output in afterSwap. The current rate is currentFeeBps(poolId); the quote endpoint reads it so the UI can warn during the anti-snipe window. Fees never sit in a server: they are ERC-6909 claims owned by the hook and booked per account, withdrawn with claim.
Uniswap prices are currency1 per currency0 in raw units; the token has 18 decimals and every stock has 8. With the token as currency0, P = 10^8 · FDV / (stockUsd · 10^27); as currency1 the fraction inverts. The factory computes sqrtPriceX96 = sqrt(P · 2^128) · 2^32 with full-precision integer math and derives the tick.
The indexer stores every swap's post-trade price as whole stock per whole token with 30 decimals; the web app multiplies by the stock's latest Chainlink USD price to show dollars. FDV is that price times one billion. Pool reserves are computed from the locked position (liquidity, tick range) and the live sqrtPriceX96 from StateView.
The indexer polls Base every 2 seconds and processes blocks that are at least 2 confirmations deep. It fetches Launched, FeeCharged and FeesClaimed logs from the factory and hook, PoolManager Swap logs filtered by the known pool ids, and ERC-20 Transfer logs of launched tokens, then writes everything in one database transaction: launches, swaps (with side, price and trader), fee events, transfers, balances and rebuilt one-minute candles.
Reorgs are detected by comparing the stored hash of the last processed block with the chain; on mismatch the indexer rolls back to the last common ancestor and replays. Every minute it refreshes the 13 Chainlink quotes, and it fills token metadata (description, image, website, X) from IPFS in the background. The web app's /api/health reports how far behind the chain head the indexer is.
Launch metadata (name, symbol, description, image, website, X) is written once into the token's ERC-7572 contractURI on IPFS. Because that record is immutable, presentation fields can be updated off-chain by the creator: description, image, website, X and Telegram. Name, symbol and supply never change.
An update is an EIP-712 message signed by the creator wallet over the token address, the normalised fields, the keccak256 of the new image (or zero when unchanged) and a timestamp. The server accepts it only when the signer equals the launch creator recorded onchain, the timestamp is within 15 minutes of its clock and newer than the stored profile, the uploaded image matches the signed hash, and the signature verifies (offline for EOAs, via ERC-1271 / ERC-6492 on Base for smart wallets such as Base Account). The stored profile overrides the launch metadata field by field and the token page says when the creator last updated it.
All responses are JSON, computed from the indexer's tables plus live pool reads. Missing data is null, never a placeholder. Reads are memoised server-side for 3 to 15 seconds.
A Telegram channel posts every launch, every large trade and every market cap milestone. It is fed by the indexer rather than by polling the API: a row is written into an outbox table inside the same database transaction that commits the swap it describes, so an announcement is exactly as durable as the fact behind it. A reorg deletes the unsent rows for the blocks it rolled back, so the channel cannot announce a trade that did not survive.
A trade qualifies by clearing an absolute dollar floor or a share of that token's own 24-hour volume, because one threshold cannot serve a token doing $200 a day and one doing $20,000. Individual buys and sells are deliberately not posted: nobody can filter a shared channel, so one busy token would bury every other. Milestones are recorded only after the post is actually delivered, so a failed send retries rather than silently skipping a level.
Coinbase tokenized stocks on Base, registered in the factory with their Chainlink total-return feeds (8 decimals, 24/5). Icons come from each token's onchain metadata.
nonReentrant; unlock callbacks accept calls only from the PoolManager.https://x.com/handle and https://t.me/handle.