The trigger
There is no graduation call. The buy that fills the curve graduates the launch inside the same transaction — throughbuy or buyWithNative, and at creation too, when an opening buy is large enough to fill the curve. By the time that buy returns the pool is open: phase() has gone from Trading straight to Graduated, and pool() is set. A type-1 launch never reports ReadyToGraduate.
- The reward goes to that buy’s
msg.sender— the payer, not therecipient, so a buy through a contract pays the contract. It is paid in the quote token:min(graduationReward, raised · graduationFeeBps / 10000). Both values are snapshotted into the launch at creation, so readconfig().graduationRewardfor the exact figure —factory.quoteConfig(quote)shows what new launches will be created with. - That buy costs more gas than an ordinary one, because it also lists the pool and mints and locks the position — and nobody knows in advance whose buy will fill the curve. Budget for it when quoting a buy that reaches the end of a curve.
What it does
- Takes the graduation fee from the raise and pays the reward to the filling buy’s
msg.senderout of it. The remainder, plus the type’s reserved LP supply, seeds the pool. - Lists the pool through the factory’s lister. The factory calls
lister().listPool(token0, token1, abi.encode(sqrtPriceX96)): the DEX lister, which holds the exchange’s pool-creation role, creates a pool of the launch’sgraduationPoolTypeon the terms governance registered and sets its price. The factory then checks it got that pool at that price. Neither the launch nor the factory holds any DEX role. - Mints the position directly to the locker — one locker shared by every launch — full range, and locks it with a
protocolBps: the protocol’s share of everything the position earns, the creator receiving the rest. The functions that would move the position or reduce its liquidity are not in the locker’s bytecode. - Tries to move the launch’s accrued protocol fees into the factory’s pot for that quote (
feesAccrued). The move is not depended on: if the quote token refuses the transfer, graduation still completes, the fees stay in the launch, andcollectFees()pays them to the fee recipient directly. The creator’s curve fees stay in the launch either way. - Marks the token graduated —
graduated()becomes true and the token emitsGraduated(). It is a signal only: the token has been freely transferable since its first block.
The pool you get
- The launched token is always
token0and the quotetoken1— creation refuses any salt that sorts the other way — so the pool’ssqrtPriceX96is quote per token, andGraduated.tokensSeededis thetoken0side. - The fee coin comes from the listing terms. On this deployment graduated pools take their fees in the quote; read
pool.feeToken()rather than assume, and handle both tokens if you receive fees. - Find the pool via
launch.pool(), theGraduatedevent, orpoolFactory.getPool(token0, token1, poolType).
When graduation cannot complete
If graduation cannot complete, the buy that would have filled the curve reverts with the cause, and the launch stays inTrading just short of full: sells, and buys too small to fill it, keep working, so no raise is held waiting. The causes to model:
- The target pool already exists. Graduation refuses to seed any pre-existing pool of the target type, initialized or not — a squatter who created it first would own the market’s opening terms. Constant-product pools are not open to public listing, so normal operation does not reach this.
- The listing is refused: the DEX lister’s
NoTermsRegistered(address)orPriceRequired(), or the factory’s ownListedOnAnotherCurve()/ListedAtAnotherPrice(). These are retryable: the lister is governance-set and read live, so once it is fixed the next buy that fills the curve graduates the launch.
The token
LunyaLaunchToken is a standard ERC-20 with ERC20Burnable:
- Transfers are free from the first block — no gate, no allowlist, before or after graduation.
burn/burnFromwork in any phase.totalSupply()falls;sold,progress()and graduation do not change (the LP supply is fixed). Compute market cap fromtoken.totalSupply(), not fromconfig().totalSupply.- Identity is permanent: name (up to 128 bytes), symbol (up to 32) and
contractURI(up to 2,048) are set at creation, with no setter.
After graduation: the locker
The locker is where a creator’s income lives for the rest of the token’s life, so it is integration surface:- The percentage is fixed; the addresses move.
protocolBpsis set at lock time and changeable by nobody. The creator’s recipient is movable by its holder; the protocol’s side followsfactory.feeRecipient(), so one governance call redirects it for every position. - Failed payouts are booked, not lost. If a token refuses a recipient, the amount is recorded in
creatorOwed/protocolOwedand withdrawn later withwithdrawOwed(tokenId, token). - Farming trades a position’s pool fees for a DEX reward programme’s emissions, split by the same
protocolBps. Moving to a new programme revertsPreviousProgrammeStillOwedwhile the old one still owes the position.
collectFees() remains for any creator curve fees not yet collected.
Receiving fees in a contract
Name a contract ascreatorFeeRecipient at creation and it holds the creator’s side of the launch — on the curve, and of the graduated position for as long as it exists. What such a contract has to survive:
- It receives a role, not a stream.
setFarmingApproval,enterFarmingandsetCreatorFeeRecipientare the creator side’s alone;collectFees,exitFarming,collectFarmingRewardsandwithdrawOwedare open to either side. A contract can only exercise the rights it has code for. - Nothing calls it. There is no callback on receipt, and collecting is not permissionless — only a recipient may trigger it. Expose a function that collects, and let anybody call it.
- Two tokens can arrive. Handle the launched token and the quote, whatever the pool’s fee coin.
- A payment can be deferred. Without a path to
withdrawOwed, a deferred payment is stuck for good. - Permanence is the absence of a function. A contract with no code calling
setCreatorFeeRecipientmakes the arrangement irrevocable; one with that code makes it revocable by whoever can reach it. - Farming is a decision in both directions. Exposing
enterFarminglets whoever reaches it swap the pool fees for emissions; exposing nothing means the position can never farm. - Keep books per position. A contract serving many launches must key everything by
tokenIdor token — never by its own balance.