Whitepaper · v2.1 · September 2026
The Countdown Family protocol
A permissionless token launchpad on Robinhood Chain that deploys every token straight into a permanently locked Uniswap V3 position — no bonding curve, no migration, no liquidity an administrator can touch.
Contents
1. Abstract
Countdown Family is a token launchpad on Robinhood Chain, an Arbitrum Orbit L2 that settles to Ethereum. A creator submits one transaction and receives, atomically, a fixed-supply ERC-20, a live Uniswap V3 pool paired with WETH, the full supply deposited as a one-sided liquidity position, and that position transferred into a locker contract with no withdrawal function. Trading begins in the same block, on Uniswap V3 itself, with no intermediary contract between the trader and the pool.
The protocol has no bonding curve and no graduation migration. The concentrated-liquidity position is the curve: because all of the supply sits above the opening price and none of the WETH sits below it, buying moves the price up along the V3 tick range in exactly the way a bonding curve would, except the liquidity is already in the place it would eventually have to migrate to. What other launchpads call “graduation” is here a milestone read from the position’s WETH principal, not a state transition that moves funds.
Fees are charged at two layers. Uniswap’s 1% pool fee accrues inside the locked position and is split on claim between the creator (70%) and the protocol (30%), a ratio snapshotted per token at launch. On top of that, trades sent through the app’s router pay a creator fee the deployer sets once (0–5%) and a 1% protocol fee, both in ETH, both capped by constants. For 300 L2 blocks after launch (about thirty seconds) per-wallet and cumulative caps bind every buyer except the creator’s atomic opening buy. Everything the app shows is indexed from chain reads with block and transaction provenance; the repository contains no mock data.
contracts/launch/ and the data layer in lib/pons*.ts as they are written. Every number is either a constant in the code or is derived from one by a formula shown alongside it. It is not marketing, and it does not describe features that do not exist yet — those are in §17.2. The problem with launchpads
The dominant launchpad pattern — a custom bonding-curve contract that later “graduates” to a DEX — works, but it carries four structural costs that this design was built to remove.
2.1 The migration is a moment of custody
A bonding-curve pool holds every buyer’s ETH until the curve completes. Graduation is a transaction the pool contract executes: withdraw the ETH, withdraw the tokens, call a router, receive LP tokens, burn them. Every step is code the launchpad wrote, and for the duration of the curve the launchpad — not a DEX with years of battle-testing — is the custodian. An earlier version of this project ran exactly that design (BondingCurvePool.sol, retired and kept only as history), and the migration path was where most of its risk lived.
2.2 Two price regimes mean two sets of bugs
Pre-graduation trades go through the curve; post-graduation trades go through the DEX. The UI, the indexer and the wallet integration all need two code paths, and the seam between them — the block in which the curve closes and the pool opens — is where price can be manipulated, where a stuck migration strands funds, and where an indexer that missed one event drifts permanently.
2.3 Snipers are a clock problem, not a wallet problem
Per-wallet caps are defeated by splitting across addresses for the cost of gas. The only constraint a sniper cannot buy around is time: what happens in the launch block and the blocks right after it. A protection that does not reason about block numbers is decoration.
2.4 Fake numbers
Launchpad frontends routinely display synthesised charts, invented volume and placeholder holders. A trader cannot tell a random walk from a market. A launchpad that earns from trades has an incentive to make markets look more alive than they are, and the only defence is a data layer where every number points at a block.
3. Design principles
One transaction, one venue
The liquidity is not ours to move
Time-bound protection
ArbSys.arbBlockNumber()), apply only to buys from the pool or handed out by the router, and expire 300 L2 blocks — about thirty seconds — after launch. After that the token is a plain ERC-20.Snapshot, don’t trust
Every number has a block
Simulate before you sign
4. Protocol architecture
The protocol is four contracts plus shared Uniswap V3 infrastructure that already exists on the chain.
| Contract | Role | Ownership |
|---|---|---|
| CountdownLaunchFactory | Entry point. launchToken checks the configuration, pays the launch fee, has the token deployed via CREATE2, creates and initialises the pool, mints the position, hands it to the locker, records the launch, passes the mint’s rounding dust to the creator, and executes the opening buy. Holds the DEX and launch configurations. Exposes graduationStatus. | Ownable2Step |
| CountdownTokenDeployer | Holds the token’s creation code and performs the CREATE2 on the factory’s behalf, with the salt namespaced by caller so no one else can occupy an address the factory predicts. It exists because the token bytecode, embedded in the factory, put the factory over the 24,576-byte limit (§14.3). | None (no owner, no state) |
| CountdownLaunchLocker | Permanent custodian of every position NFT. Accepts NFTs only from the factory, verifies custody in lockPosition, splits collected fees, tracks per-token protocol share and fee redirects. No withdrawal path exists. | Ownable2Step |
| CountdownTradeRouter | The app’s swap entry point. Wraps SwapRouter02 and charges the creator’s per-token fee plus the protocol fee in ETH. Independent of the factory; trades on unregistered tokens pay only the protocol leg. | Ownable2Step |
| CountdownLauncherToken | Fixed-supply ERC-20 with immutable launch parameters, self-describing metadata (logo, description, socials), and a transfer hook that enforces the launch-window caps on pool-to-wallet and router-to-wallet transfers only, measured in L2 blocks (§7). | None (immutable) |
| Shared infrastructure (Robinhood Chain mainnet) | Address |
|---|---|
| Uniswap V3 Factory | 0x1f7d7550B1b028f7571E69A784071F0205FD2EfA |
| Nonfungible Position Manager | 0x73991a25C818Bf1f1128dEAaB1492D45638DE0D3 |
| SwapRouter02 | 0xCaf681a66D020601342297493863E78C959E5cb2 |
| Quoter V2 | 0x33e885eD0Ec9bF04EcfB19341582aADCb4c8A9E7 |
| WETH | 0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73 |
Off-chain, a single long-running indexer reads factory launch events, token metadata, pool state and swap logs into a libSQL database; a Next.js API serves that database to the browser. The browser touches the chain for exactly one purpose: signing a transaction the user asked for.
Robinhood Chain (factories, tokens, V3 pools)
│ eth_getLogs + view calls (paced, retried)
▼
scripts/pons-indexer.ts long-running worker (PM2)
│ writes markets / price_points / data_versions
▼
libSQL (local file in dev, Turso in production)
│
▼
GET /api/data?kind=updates|markets|market ETag + 304
GET /api/config
│
▼
lib/use-markets.ts → React UI
Wallet writes (launch, swap, claim) go straight from the browser to the chain.
Nothing is settled server-side; the pool's own events are the record.5. The launch transaction
launchToken(TokenParams, launchConfigId, dexId, salt) is payable and nonReentrant. msg.value must be at least the launch fee; whatever exceeds it becomes the creator’s opening buy. The full sequence, in order:
- Gate checks.
launchEnabled(true from construction on this factory) or the caller is allowlisted; fee paid; DEX and launch config exist and are enabled; name and symbol are non-empty. - Address prediction. The token’s creation code is built from the params and the config, and its CREATE2 address is computed. If a V3 pool for (predicted token, WETH, 1%) already exists, the call reverts
PoolAlreadyExists. This is what makes a collision free to detect by simulation. - Launch fee is forwarded to the locker’s
protocolFeeRecipient. Default 0.0005 ETH, owner-adjustable. - Token deploy via CREATE2 through the token deployer, with the caller’s salt namespaced by the factory. The constructor mints the entire supply (1,000,000,000 × 10¹⁸) to the factory and stamps
launchBlock,restrictionEndBlock = launchBlock + 300(L2 blocks), and the caps as immutables. - Pool creation. Token ordering is determined (
isToken0 = token < WETH); the initial tick is sign-flipped if the token is token1; the pool is created and initialised atsqrtPriceX96 = getSqrtRatioAtTick(tick). - One-sided mint. The factory approves the position manager for the supply and mints a position whose range is [initialTick, maxUsableTick] (token0 case) or [minUsableTick, −initialTick] (token1 case). Only token goes in;
amountMinis zero because the pool is fresh and nothing can front-run a pool that did not exist a moment earlier. - Record. A
LaunchedTokenstruct is stored: deployer, paired token, position manager and id, config ids, restriction end block, supply, ordering, pool fee, and the opening buy amount. The graduation threshold is copied per token. - Lock. The NFT is
safeTransferFrom’d to the locker, which only accepts transfers where both operator and sender are the factory.lockPositionre-readsownerOfand reverts unless the locker holds it, then snapshots the protocol fee share for that token. - Fee redirect. If
feeWalletwas supplied, the locker is told to pay the creator share there instead of to the deployer. - Event.
TokenLaunchedis emitted with a signature byte-identical to pons’s (§13). - Opening buy. If anything remains above the fee, the token opens a one-call recipient exemption, the factory calls
exactInputSingleon SwapRouter02 with the remaining ETH, and the exemption is closed. The buy lands with the creator (or theirfeeWallet) as recipient.
| Launch config 0 (deploy script) | Value | Meaning |
|---|---|---|
| pairToken | WETH | Every pool is token/WETH. |
| supply | 1,000,000,000 | Fixed. No mint function exists after construction. |
| initialTick | −204,200 | Opening price ≈ 1.3557 × 10⁻⁹ WETH per token (§6). |
| graduationThreshold | 4.2 WETH | Principal in the position at which graduated = true (§10). |
| maxWalletBps | 500 (5%) | Max balance a buyer may hold via pool buys during the window. |
| maxTxBps | 550 (5.5%) | Derived as 110% of maxWallet; cumulative pool-buy cap per wallet. |
| restrictionBlocks | 2 | Window = launch block plus the next two. |
| Pool fee tier | 10,000 (1%) | Uniswap fee on every swap; the only LP fee in the pool. |
| tickSpacing | 200 | Required by the 1% tier. |
| launchFee | 0.0005 ETH | Paid to the protocol fee recipient. Owner-adjustable. |
initialBuyAmount from getLaunchedToken, which is also shown on the token page, before deciding.6. Price discovery on concentrated liquidity
Uniswap V3 quotes price in ticks. The price of token1 in units of token0 at tick i is:
With the token as token0 and WETH as token1, the launch config’s tick of −204,200 gives an opening price of 1.0001⁻²⁰⁴²⁰⁰ ≈ 1.3557 × 10⁻⁹ WETH per token. If the token sorts as token1 the sign flips and the pool price is the reciprocal, but the economic price is the same. The indexer computes it from slot0().sqrtPriceX96:
At launch, all 10⁹ tokens are deposited into the range [P₀, Pmax] with no WETH. For a position entirely above the current price, V3 defines the token0 amount as x = L · (1/√P − 1/√Pmax). Since Pmax is the top of the tick range and effectively infinite, L ≈ S · √P₀, where S is the supply. As buyers add WETH the price rises and the WETH principal inside the position becomes:
S · P₀ is the fully diluted value at launch: ≈ 1.3557 ETH. That single number characterises the whole curve. Some consequences, ignoring the 1% swap fee:
| WETH principal in position | Price multiple vs launch | FDV | Supply sold |
|---|---|---|---|
| 0 | 1.00× | 1.36 ETH | 0% |
| 1.36 ETH | 4.00× | 5.42 ETH | 50.0% |
| 2.71 ETH | 9.00× | 12.2 ETH | 66.7% |
| 4.20 ETH (graduation) | 16.8× | 22.8 ETH | 75.6% |
| 8.00 ETH | 47.6× | 64.5 ETH | 85.5% |
| 20.0 ETH | 248× | 337 ETH | 93.7% |
This is the same shape as a constant-product bonding curve with virtual reserves — it is one, expressed in V3’s square-root coordinates — with the difference that there is nothing to migrate. The liquidity that traders are buying against is already on the DEX, the position already exists, and the NFT is already locked.
6.1 Why the quoter, not the chart
The displayed price is the pool’s spot price and ignores the impact of the trade’s own size. On a curve this steep a modest buy moves the price materially, so a slippage floor derived from the displayed price would sit above what the trade can actually fill and revert every large order. The app therefore quotes every trade through Quoter V2 — which simulates the swap against current pool state — and applies the user’s slippage tolerance to that quote, capped at 50%.
6.2 Trade mechanics
- Buy:
exactInputSingleon SwapRouter02 with native ETH asmsg.value; the router wraps it to WETH itself. Recipient is the buyer. - Sell: ERC-20 approve (once, unlimited) then a router
multicallofexactInputSinglewith recipientaddress(2)— the router’s “keep it here” sentinel — followed byunwrapWETH9(minOut, seller). The slippage floor is enforced on the unwrap, so the seller receives ETH, not WETH, or the whole multicall reverts. - Nothing is written to the database by the client. The pool’s
Swapevent is the record and the indexer reads it back. Writing client-side would double-count.
7. Launch-window protections
Protections live in the token’s _update override and are evaluated only while the current block is at or before restrictionEndBlock. They apply when from is a pool for this token and WETH registered by the Uniswap factory — at any fee tier, so a pool created at a different tier to dodge the hook is still recognised — and when from is the swap router, which can hold a buy in flight. Wallet-to-wallet transfers, sells into the pool, and everything after the window are untouched.
7.1 Which block
Robinhood Chain is an Arbitrum Orbit L2, and on such chains the EVM’s block.number is the parent chain’s height. This was measured rather than assumed: on 20 September 2026 a contract read of block.number returned 26,015,315 while eth_blockNumber returned 67,532,809, and the former moves once every ~12 seconds. A window written in that unit would be neither what the documentation promised nor comparable with anything the indexer stores. The token therefore reads ArbSys(0x64).arbBlockNumber() — the height the explorer, the RPC and the indexer all count — through a gas-capped static call, and falls back to block.number only where the precompile does not answer (a plain test network). launchBlock, restrictionEndBlock and the restrictionsEndBlock field of TokenLaunched are L2 heights.
L2 blocks arrive about every 0.1 s (303 blocks in 31 s, same measurement). The shipped launch configuration sets restrictionBlocks = 300: roughly thirty seconds of caps after the launch block, which is the same order of protection the earlier block.number version gave by accident, now in the unit it claims.
7.2 The rules
| Block | Rule for pool → wallet and router → wallet transfers |
|---|---|
| launchBlock | All pool buys revert (LaunchBlockBuyBlocked) except the single recipient the factory registered for its atomic opening buy, and only while that registration is open. A sniper who lands in the launch block gets a revert, not a fill. |
| launchBlock + 1 … + 300 | Resulting balance must not exceed maxWalletLimit() = 5% of supply (MaxWalletExceeded), and cumulative buys per recipient must not exceed maxTxLimit() = 5.5% of supply (MaxTxExceeded). The cumulative counter is per recipient, so splitting one large buy into several within the window does not help. A transfer to the router is not counted; the transfer from the router to a wallet is, keyed by that wallet, so parking a buy in the router and sweeping it out is capped exactly like a direct buy. |
| launchBlock + 301 onward | No rules. The first transfer to observe this records it, and the token is a plain OpenZeppelin ERC-20 from then on, without reading the block again. |
The relationship maxTxBps = ⌊1.1 × maxWalletBps⌋ is enforced by the factory when a launch config is added; a config with any other pairing reverts InvalidMaxTxBasisPoints. The 10% headroom exists so a wallet already near the cap can still complete a buy whose rounding would otherwise fail.
8. The liquidity lock
The locker is the contract the whole design rests on, and it is deliberately small. It is 5,426 bytes compiled — identical in size to pons’s deployed locker, which is a reasonable signal that the build reproduces the verified source.
onERC721Receivedreturns the magic value only when bothoperatorandfromare the factory. Any other NFT transfer reverts.lockPosition(token)isonlyFactory, reads the launch record back from the factory, and revertsPositionNotHeldunlessownerOf(positionId)is the locker itself. It cannot be called twice for one token.initialize(factory)binds the locker to one factory, once.AlreadyInitializedotherwise.- There is no function that calls
decreaseLiquidity,burn,transferFromorsafeTransferFromon the position manager, no genericexecute(address, bytes), noselfdestruct, and no proxy. The only call the locker makes on the position manager after locking iscollect, which withdraws accrued fees and cannot touch principal.
The consequence is that the liquidity cannot be rugged by the creator, and cannot be rugged by the protocol either. The owner of the locker can change who receives the protocol’s share of future fees, and what that share is for tokens launched after the change. It cannot remove a position, cannot redirect an existing token’s split, and cannot pause trading — trading happens on Uniswap, which the locker does not control.
9. Fees and revenue split
Trading fees are charged at two layers. The pool layer is Uniswap V3’s 1% tier, taken by Uniswap on every swap in both directions and accrued inside the locked position, because that position is the only liquidity in the pool. The router layer is CountdownTradeRouter, the app’s swap entry point, which charges a per-token creator fee and a flat protocol fee in ETH on every trade sent through it.
| Fee | Amount | Goes to |
|---|---|---|
| Launch fee | 0.0005 ETH (owner-adjustable) | Protocol fee recipient, at launch. |
| Creator trading fee (router) | 0–5%, set once by the deployer; default 2% | Creator wallet, in ETH, on every routed trade. |
| Protocol trading fee (router) | 1% (hard-capped at 2%) | Protocol fee recipient, in ETH, on every routed trade. |
| Pool fee (Uniswap) | 1% of every buy and sell | Accrues inside the locked V3 position. |
| — creator share on claim | 70% (100 − protocolFeeShare) | Deployer, or the feeWallet redirect. |
| — protocol share on claim | 30% (default; hard-capped at 50%) | Protocol fee recipient. |
9.0 The router layer
The pool’s fee tier is fixed by Uniswap and cannot be raised, so a creator-chosen fee has to be collected outside the pool. The router wraps SwapRouter02: on a buy it takes creatorBps + protocolBps off msg.value and swaps the remainder; on a sell it swaps to WETH, unwraps, takes the same share off the output and pays the seller the rest. The slippage floor is on the net figure. The UI shows the two router legs as one number — a 2% creator fee displays as 3% — with the pool fee listed separately.
setTokenFeeis callable only by the token’sdeployer(), only once, and only up toMAX_CREATOR_FEE_BPS = 500. The fee a trader sees is the fee for the life of the token; only the recipient wallet can move. The 5% ceiling is a constant, not an owner setting, chosen because a round trip at 5% costs 10% and a round trip at anything higher starts to resemble a honeypot.- The protocol leg is owner-set between 0 and
MAX_PROTOCOL_FEE_BPS = 200. - Fees are pushed at trade time under a 30k gas stipend; a recipient that cannot accept ETH has the amount booked to
pendingand claims it withclaimPending(). A fee recipient can never revert a trade. - A fee-on-transfer hook in the token was rejected: it breaks sells on Uniswap V3, gets the token flagged as a tax token, and pays the creator in tokens they must then sell. The cost of the router design is that a trade sent directly to Uniswap pays only the pool’s 1%. That trade-off is deliberate and disclosed.
9.1 Claiming
collectFees(token) on the locker calls the position manager’s collect for the maximum of both assets, reverts NoFeesToCollect if both are zero, splits each asset by the token’s snapshotted share, and transfers all four legs. Authorised callers are the locker owner, the token’s deployer, the current fee redirect recipient, and any address in feeCollectors. Fees arrive in the assets they were paid in — a mix of WETH and the token — not converted.
9.2 Why fees are read by simulation
Uniswap V3 exposes no view that returns a position’s uncollected fees after accounting for fee growth; the honest number is whatever collect would return right now. The app therefore simulates collectFees with eth_call to display the pending amount and sends the real transaction only when the creator clicks claim. A NoFeesToCollect revert in simulation is rendered as zero. Zero means “nothing accrued yet”, never “already paid” — these contracts never push fees on their own.
9.3 Snapshotting
tokenProtocolFeeShares[token] is written once, in lockPosition, from the locker’s current protocolFeeShare. setProtocolFeeShare changes only what future launches will copy. A fork test launches a token at 30%, raises the share to 50%, and asserts the first token still splits at 30%.
9.4 Fee redirects
The deployer (or the factory, during launch) can point a token’s creator share at another wallet with setFeeRedirect. The locker maintains a reverse index (feeRecipientTokens) so a profile page can list every token a wallet earns from, whether it deployed them or was assigned them.
10. Graduation
Graduation on this protocol is a label, not a migration. Nothing moves. The factory’s graduationStatus(token) view computes the WETH principal currently inside the locked position — from slot0, the position’s tick bounds and its liquidity, via the same amount formulas V3 uses — and compares it to the threshold copied at launch:
The indexer stores progress = min(pairedPrincipal / threshold, 1). With the deployed config (4.2 WETH) and the curve in §6, graduation corresponds to roughly a 16.8× price move from launch, an FDV near 22.8 ETH, and about 75.6% of supply sold, before fees.
11. Admin surface and what it cannot do
Both the factory and the locker use OpenZeppelin Ownable2Step: an ownership transfer must be accepted by the new owner, which prevents a typo from bricking administration. The complete list of owner-only functions:
| Contract | Function | Effect | Affects existing tokens? |
|---|---|---|---|
| Factory | addDexConfig / setDexStatus | Register or disable a V3 deployment. | No |
| Factory | addLaunchConfig / updateLaunchConfig | Add or replace a launch parameter set. | No — params are immutables in each token |
| Factory | setLaunchFee | Change the ETH fee for future launches. | No |
| Factory | setLaunchEnabled | Open or close public launching. | No — trading is on Uniswap |
| Factory | setWhitelistedLauncher | Allowlist an address while public launching is closed. | No |
| Locker | initialize | Bind to the factory, once. | — |
| Locker | setProtocolFeeRecipient | Where the protocol share is paid. | Yes, for future claims |
| Locker | setProtocolFeeShare | Share snapshotted by future launches (≤ 50). | No — snapshotted |
| Locker | setFeeCollector | Allow an address to trigger collectFees for any token. | Only who may call; not the split |
| Router | setProtocolFeeBps (≤ 200) | Countdown Family’s leg on routed trades. | Yes, from that block on |
| Router | setProtocolFeeRecipient | Where Countdown Family’s leg goes. | Yes, for future trades |
Not possible for any owner: withdrawing or transferring a locked position; minting tokens; changing a token’s supply, caps, or restriction window; changing an existing token’s fee split; raising a creator’s trading fee or the 5% / 2% caps; pausing or blocking swaps; upgrading any contract. None of these functions exist.
12. Data integrity
The frontend serves nothing it computed itself and nothing it could not point at a block. The rules the indexer will not bend:
- Every row in
marketskeepsblock,blockHashandhashfrom its launch transaction. Everyswapprice point keeps itstxHash. - A price point is either a real
Swaplog (source: "swap") or a realslot0read (source: "sample"). They are labelled and never merged. No interpolation, no jitter, no synthesised history. - Reserves and prices are re-read from the pool on every pass rather than adjusted by event deltas, so a missed log costs one stale poll instead of permanent drift.
live.volumeis the count of trades in the last 24 hours, not a notional USD figure. It is named honestly rather than inflated into a dollar amount that was not computed.live.staleis set when a snapshot is older thanPONS_STALE_MS(default 5 minutes), so the UI can say the data is old instead of presenting it as current.- Liquidity USD values both sides of the pool separately. A V3 pool is not balanced, so “double the WETH side” would overstate a pool that has drifted.
/api/configreportssimulated: falseanddataSource: "indexed-onchain". If a simulated mode is ever added, that flag is how the UI must gate a banner.- The repository contains no seed data for markets. An empty list means nothing has launched.
13. Interoperability with pons
pons is a third-party launchpad on Robinhood Chain whose contracts are verified on Blockscout and published under MIT. This protocol’s contracts are adapted from that source with two changes: type names carry a Countdown Family prefix, and launchEnabled is true from construction. pons ships it false and admits launchers by allowlist (checked 7 September 2026 on both of their factories), which is the entire reason for running a separate deployment.
Event signatures are byte-identical. TokenLaunched’s topic0 on this factory equals pons’s, so one indexer reads both factories with no second code path, and the app lists tokens from either. A fork test asserts the hash so it cannot drift silently. Trading is factory-agnostic anyway — a swap needs only the pool fee tier and WETH — and only launch metadata and fee claims walk the factory list.
| pons factory | Address | Creator share |
|---|---|---|
| Legacy (PONS itself launched here) | 0x0c37a24F5D23A486FA692d1500881d698B1F77a4 | 90% |
| Active | 0xA5aAb3F0c6EeadF30Ef1D3Eb997108E976351feB | 70% |
14. Security considerations
14.1 What has been verified
- Nineteen tests run against a fork of Robinhood Chain mainnet and the real Uniswap V3 deployment (
FORK=1 npm run contracts:test): unallowlisted launch succeeds; the whole supply reaches a live pool and the rounding dust reaches the creator; everything above the fee becomes the opening buy; the creator’s buy is exempt from the caps; a later buyer is held to the caps inside the window both directly and through a parked-and-swept router buy; the caps lift after the window; the launch fee reaches the recipient; the position is locked and graduation reports progress; the fee split is snapshotted and redirects work; the launch block admits nobody but the creator; theTokenLaunchedhash matches pons’s. Last full run: 60 of 60 on 20 September 2026. - Forty-one offline tests: seven for the token’s transfer hook against a mock pool and router (every branch of §7), twenty-two for the factory and locker (construction, the size guard, event hashes, configuration validation including tick spacing and the router requirement, the launch gate, address prediction and the deployer’s salt namespace, two-step ownership), twelve for the trade router (fee arithmetic on both legs of both directions, once-only capped creator fees, owner-only capped protocol fee, after-fee slippage floor, deferred fees, refusal of stray ETH, token rescue, zero-amount rejection).
- The full deployment sequence — four contracts, initialisation, both configs, read-back checks — was rehearsed on a fork of mainnet with the real Uniswap addresses on 20 September 2026.
ReentrancyGuardonlaunchToken,collectFees,buyandsell;SafeERC20for all token transfers;forceApprovereset to zero after the mint.- The deploy script refuses any chain other than 4663 and verifies each dependency address holds code.
14.2 What has not
14.3 Known limitations and risks
- Mainnet only. Uniswap V3 is not deployed on Robinhood Chain testnet; there is no way to rehearse a launch or a swap with test ETH. The first of each is real money.
- Uncapped creator buy. A creator can buy a large fraction of supply at launch. It is public in the event and the token page, but it is possible.
- Curve steepness. With FDV ≈ 1.36 ETH at launch, small buys move price a lot. This is the nature of a launchpad, not a bug, but slippage settings matter.
- Rate limiter is database-backed. Server-side write limits live in a
rate_limitstable, so every instance shares one window; a limit is only as fresh as the database round-trip, and the client IP behind a proxy is only as trustworthy as the proxy. - Public RPC. Robinhood’s public endpoint rate limits hard. The indexer paces and backs off, but production needs a dedicated endpoint or snapshots will go stale.
- Build settings are load-bearing.
viaIRis required or the factory does not compile;evmVersion: shanghaiis what the artefacts were verified with. Cancun is avoided because TSTORE/MCOPY support varies across Orbit chains. Bytecode size is no longer marginal: the factory is 15,699 bytes of the 24,576 allowed since the token bytecode moved into the deployer.
14.4 What the September 2026 review changed
An internal review before mainnet found four things in the adapted contracts. Each was fixed in Solidity, tested, and recorded with its measurements in docs/CONTRACT-NOTES.md.
- The window unit. The token measured its window in
block.number, which on this chain is Ethereum’s height (§7.1). It now measures it in L2 blocks. - The router sweep. SwapRouter02 could leave a buy inside itself and hand it out with
sweepToken; that second leg was not a pool transfer and was uncapped. It is capped now. - Tick alignment. A launch config whose
initialTickwas not a multiple of the pool’s spacing was accepted and would have reverted every launch deep inside Uniswap after the fee was paid. The factory now checks it first (InvalidInitialTick). - Smaller items. A DEX config without a router is rejected; the mint’s rounding dust goes to the creator instead of sitting in the factory; the router refuses ETH from anything but WETH and can return tokens sent to it by mistake; licence headers say what each file actually is.
15. Robinhood Chain
Robinhood Chain is an Arbitrum Orbit L2 that settles to Ethereum and uses ETH for gas. It is a different network from Unichain. The app pins chain 4663 for every write regardless of other configuration.
| Mainnet | Testnet | |
|---|---|---|
| Chain ID | 4663 | 46630 |
| RPC | rpc.mainnet.chain.robinhood.com | rpc.testnet.chain.robinhood.com |
| Explorer | robinhoodchain.blockscout.com | explorer.testnet.chain.robinhood.com |
| Uniswap V3 | Deployed | Not deployed |
| This protocol | Target | Cannot run (no V3) |
| Measured on 20 September 2026 | Value |
|---|---|
| eth_blockNumber (L2 height) | 67,532,809 |
| block.number inside the EVM (Ethereum height) | 26,015,315 |
| ArbSys(0x64).arbBlockNumber() | 67,532,835 |
| L2 block time | ≈ 0.10 s (303 blocks in 31 s) |
| Gas for ArbSys.arbBlockNumber() | ≈ 1.4k |
Roughly 860,000 L2 blocks a day. Two things follow. Anything on this chain that reasons in block.number is reasoning in Ethereum’s clock, twelve seconds per tick; and a launch window of 300 L2 blocks is about thirty seconds of wall-clock time. The protection is about ordering, not duration.
The public RPC hostname is DNS-filtered on Indonesian ISPs. The app relays browser RPC through its own origin (/api/rpc), and the repository ships a local forwarder (npm run rpc:proxy) for development and deployment from such networks.
16. Licensing
CountdownLauncherToken.sol, CountdownLaunchLocker.sol, CountdownTradeRouter.sol and CountdownTokenDeployer.sol are MIT. TickMath.sol and LiquidityMath.sol are Uniswap’s, GPL-2.0-or-later, and copyleft carries into any contract that includes them, so CountdownLaunchFactory.sol is GPL-2.0-or-later. The contracts are published and verified either way; the practical consequence is that the factory cannot be relicensed as proprietary.
17. Status and roadmap
| Item | Status |
|---|---|
| Contracts written, compiled, fork-tested | Done |
| Pre-mainnet review: window unit, router sweep, tick alignment, bytecode size | Done — 20 September 2026, see §14.4 |
| Deployment rehearsed end to end on a mainnet fork | Done — 20 September 2026 |
| Indexer running under PM2 against pons factories | Done |
| Client hooks for launch / swap / quote / fee claim | Done |
| Deploy the four contracts to mainnet | Pending — needs a funded deployer key and a treasury address (docs/DEPLOY-MAINNET.md); until then launching falls back to pons’s gated factory and is blocked with a clear message; trading works |
| First mainnet swap and launch, reconciled against the indexer | Pending |
| Hosted libSQL (Turso) for production | Pending — code supports it |
| Shared-store rate limiter | Done — a rate_limits table in the same database, so every instance sees one window |
| Automated tests for client write modules | Pending |
| Independent audit | Not scheduled |
| Remove the retired database-settled trading path (curve maths, its API routes and indexer) | Done |
18. Questions a careful reader asks
If nothing migrates, what is “graduation” for?
It is a label, and only a label. When the locked position holds 4.2 WETH of principal the market is marked graduated. The number is exposed by graduationStatus so a screener can sort by it; no transaction happens and nothing moves (§10).
Who can withdraw the liquidity?
Nobody. The locker has no function that transfers, burns or decreases a position; the only position-manager call it makes after locking is collect (§8). This is the easiest claim in the document to check: read the verified source and look for the absence.
Why is the creator’s opening buy uncapped?
Because it is atomic with the launch and there is nothing to race. Capping it would only move the same purchase to the next block, where it would compete with everyone else. It is public in the launch event and on the token page (§5, §14.3).
Why are there two fees on a trade through the app and one on a trade elsewhere?
The creator fee and the protocol fee are collected by the trade router in ETH, not by the token. A trade sent straight to Uniswap pays only the pool’s 1%. The alternative — a fee-on-transfer token — breaks sells on V3 and gets the token flagged as a tax token, so the router is a front door, not a wall (§9).
What does the owner of the protocol control?
Future fee policy within hard caps, which configurations are enabled, and whether public launching is open. Not any locked position, not any token’s supply, not any trade (§11).
Why measure the launch window in L2 blocks rather than Ethereum blocks?
Because on this chain block.number is Ethereum’s, which was measured and not assumed (§7.1). A window in that unit would be ~12 s per block and incomparable with the chain’s own height.
Where do the numbers on the site come from?
From an indexer that reads the chain: launch events, Swap logs, slot0 and position reads, each stored with its block and transaction. There is no seed data and no demo mode; an empty list that is true is preferred to a full one that is not (§12).
What happens if a contract turns out to be wrong after deployment?
Nothing is upgradeable. The response is a new deployment, appended to the list of factories the indexer follows so existing tokens keep being indexed and traded. Launching on an old factory can be closed; trading on its pools cannot be stopped by anyone (§11, §17).
Has anyone independent reviewed this?
No (§14.2). The tests, the fork rehearsal and the internal review are described; an audit is not.
19. Disclaimer
This document describes software. It is not investment advice, an offer to sell, or a solicitation to buy any token. Tokens launched through the protocol are created by their deployers, not by Countdown Family, and the protocol makes no representation about any of them. Concentrated-liquidity markets with small initial depth are volatile; a buyer can lose the entire amount spent. The contracts are unaudited and provided as-is under their respective licences. Nothing here should be read as a promise that any feature listed as pending will ship.