Chapter 02 · Launch lifecycle
Launch lifecycle
One transaction for 1 USDC: token, pool, rules, seed, lock. Every step and every revert.
A launch is one call to LaunchFactory.launch or launchWithBlocks with the creation fee as value. Inside that call the factory mints the token, prices and initialises the pool, freezes the hook state, mints the creator NFT and seeds the whole supply as a single locked position. If any step fails, the whole launch reverts and nothing exists. On Arc the whole thing is final about half a second after it is sent.
Inputs
struct LaunchParams {
string name; // up to 48 bytes in the UI; the contract does not limit it
string symbol; // ticker, leading "$" stripped by the UI
Currency quote; // USDC (0x3600…0000) or cirBTC (0x171A…bAA0), as registered
bytes32 salt; // caller-chosen; the token address is CREATE2(creator, salt)
}
launch uses an empty block config. launchWithBlocks takes a Blocks.Config and validates it first (see Blocks). The creation fee is CREATION_FEE = 1 USDC, paid as native value (msg.value = 1e18, because Arc's native USDC balance has 18 decimals) and must be sent exactly; it accrues in factory.creationFees for the factory owner. Gas is paid in native USDC too, so a launcher needs a little over 1 USDC on Arc and nothing else.
Choosing the quote
The quote is the currency the pool is priced in, pays fees in and, later, that the creator claims in. Two are registered:
| Quote | Address | Decimals | Feed |
|---|---|---|---|
| USDC (ERC-20 view of native USDC) | 0x3600000000000000000000000000000000000000 | 6 | Chainlink USDC / USD |
| cirBTC | 0x171A4217b86A807A64eB94757Db6849fb4bDbAA0 | 8 | Chainlink BTC / USD |
Both open the pool at the same $10,000 valuation; the feed only decides how many tokens one USDC or one satoshi buys at the open. A USDC pool is the plain choice: one token is a fixed fraction of a dollar at launch and stays legible. A cirBTC pool denominates the token in Bitcoin, so its quote-side price moves with BTC even when nobody trades. The Launch page shows both as "Paired asset" chips with their registry state and feed age; a quote that is unregistered, disabled or halted right now cannot be picked, because priceUsd8 would revert the launch.
The Launch page
/launch is one form: two cards on the left (Token details, Rules), a live preview of the pool card on the right, and one Create token button at the bottom. Nothing is signed until the review dialog is confirmed.
- 01Token detailsImage (drop a PNG, JPG, GIF or WebP; it is centre-cropped to a 512 px square in the browser and pinned to IPFS when the server has a Pinata key, or paste an https / ipfs URL), name (48 bytes), ticker (12, upper-cased), an optional 280-character description and optional website / X / Telegram links, and the paired asset. Everything except the details is immutable.
- 02RulesA preset picks which blocks run: Fair launch (Anti-Snipe + Halt Guard, the default), Community (Nth-buy Pot + Auto Burn), Full stack (all four), Bare pool (none) or Custom (the Builder’s workbench inline; a ?stack= link or a stack saved from the Builder lands here). Every enabled block shows its settings as sliders with an exact input. The ring on the right is the drag the hook blocks add to a buy, on top of the fixed 0.30 % protocol + 0.50 % creator fees; the gas estimate sits under it.
- 03Dev buy · optionalAn amount of the paired asset the creator buys right after the launch. It is a separate transaction (approve + Router.buy, exact input, the slippage saved in the swap settings), never part of the launch call: the launch lock makes a same-transaction buy impossible for everyone, including the creator. With Anti-Snipe on, the form caps it at the per-block cap (max buy per block × the opening FDV in quote units), because the first buy lands inside the guard window.
- 04Create token → reviewA dialog lists the creator wallet, the network, the quote, the opening FDV and price, every block with its settings, the dev buy, the details, the costs and how many signatures follow. Esc or Back to details returns to the form.
- 05Confirm → LaunchThe app re-reads launchesPaused, priceUsd8 and the release gate, then asks the wallet to sign launch / launchWithBlocks with the creation fee as value. Once mined, the pool id and token address come from the Launched event and the pool is live; a launch that is out is never re-sent, only re-checked.
- 06DetailsOnly when a detail was filled in: TokenMetadata.set(poolId, json), a second signature. Refusing or failing it does not affect the pool; the step can be retried, skipped, or done later from the token page.
- 07Dev buyOnly when an amount was entered: isLive(poolId) is re-read, the buy is quoted through the real hook path for a minimum output, the Router is approved if needed, then Router.buy is signed. A failed step offers Try again, Skip, or (when the quote itself is unavailable) an explicit buy with no minimum.
- 08ReadyisLive(poolId) is read once more and the token page opens after a few seconds (View token / Stay here).
The transaction
- 01PreconditionsThe hook must be set (one-shot after deploy), launches must not be paused, and msg.value must equal the creation fee. Otherwise HookNotSet, LaunchesPaused or WrongCreationFee.
- 02Quote priceregistry.priceUsd8(quote) returns the Chainlink answer only if the quote is known, still enabled for new launches, the answer is positive and no older than the registry heartbeat (90,000 s on mainnet). Any failure reverts the launch. The registry also hands back the quote’s decimals.
- 03Tokennew LaunchToken{salt: keccak256(creator, userSalt)}(name, symbol, lpLocker, 1e9 × 1e18). The entire supply is minted to the LPLocker. predictTokenAddress() gives the address before launching.
- 04OrientationUniswap orders currencies by address. quoteIsCurrency0 = quote address < token address. USDC at 0x3600… sorts first against most token addresses; cirBTC at 0x171A… does not. Every later code path handles both orders; the flag is stored in the hook.
- 05Opening priceOpeningPrice.opening(usd8, quoteDecimals, quoteIsCurrency0) turns the $10,000 FDV into a sqrtPriceX96 and a tick floored to spacing 60, in raw units of both currencies. See Pricing.
- 06InitialisepoolManager.initialize(key, sqrtP). The hook’s beforeInitialize accepts only the factory as sender and only a key with fee 0 and tick spacing 60.
- 07Registerhook.register stores token, quote, creator, orientation and launch block; copies the block config if the mask is non-zero; derives snipeCapQuote in raw quote units; and writes the EIP-1153 launch lock so no swap can happen in this transaction.
- 08Creator NFTcreatorFeeNFT.mint(creator, uint256(poolId)). The NFT is the claim on creator fees and is transferable.
- 09SeedlpLocker.seed(key, lower, upper, token, SUPPLY). The hook’s beforeAddLiquidity accepts the locker once and flips seeded = true. The position is single-sided: if the pool would owe any quote, NotSingleSided reverts. Tokens that do not fit the range are burned.
- 10LaunchedThe factory emits Launched(id, token, quote, creator, sqrtPriceX96, openingTick, liquidity). The indexer creates the pool row from it.
The launch lock
register() writes 1 into a transient storage slot namespaced by pool id. beforeSwap reads that slot and reverts LaunchLocked while it is set. Transient storage clears at the end of the transaction, so the lock exists exactly for the launch transaction and never needs clearing. The effect: nobody, including the creator, can be the first buyer inside the launch transaction, and no contract can atomically launch-and-snipe. Arc has no public mempool, so a launch is not visible before it lands either; the lock is what makes the guarantee hold regardless.
What the creator gets
- The
CreatorFeeNFTfor the pool. Its holder can callclaimCreatorFees(tokenId, to)at any time. - 50 bps of every buy and every sell in the quote currency, plus the AntiSnipe tax while that window is open.
- Nothing else: no tokens, no LP position, no admin key. The creator's only privileged action is claiming.
Optional details
Image, description and links (website, X, Telegram) are optional and live in a separate TokenMetadata contract, not in the launch. When any of them is filled in on the Launch page, the app asks for a second signature after the launch is mined: set(poolId, json), at most 2048 bytes, no fee beyond gas. Only the current holder of the pool's creator NFT can call it, so the right to edit travels with the fee rights; the token page shows an Edit card to that wallet and an empty string clears everything. Skipping or refusing the second signature does not affect the pool. The app sanitises what it reads (https or ipfs images, https links, x.com / t.me hosts, 280-character descriptions), so garbage JSON simply renders as no details.
What the creator cannot do
- Withdraw or move the liquidity. The locker has no such function and the hook reverts every
removeLiquidity. - Change the blocks, the fee, the quote or the price after launch.
- Mint, pause, blacklist or tax the token.
contracts/src/LaunchFactory.sol_launch(), predictTokenAddress(), poolKeyFor()contracts/src/LPLocker.solseed() and the single-sided checkcontracts/src/HookarcHook.solregister(), _beforeInitialize, _beforeAddLiquidity, _launchLockSlotcontracts/test/the USDC (6-decimal) and cirBTC (8-decimal) end-to-end launches in both orientations