Skip to main content

The trigger

There is no graduation call. The buy that fills the curve graduates the launch inside the same transaction — through buy 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 the recipient, 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 read config().graduationReward for 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

  1. Takes the graduation fee from the raise and pays the reward to the filling buy’s msg.sender out of it. The remainder, plus the type’s reserved LP supply, seeds the pool.
  2. 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’s graduationPoolType on 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.
  3. 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.
  4. 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, and collectFees() pays them to the fee recipient directly. The creator’s curve fees stay in the launch either way.
  5. Marks the token graduated — graduated() becomes true and the token emits Graduated(). It is a signal only: the token has been freely transferable since its first block.

The pool you get

  • The launched token is always token0 and the quote token1 — creation refuses any salt that sorts the other way — so the pool’s sqrtPriceX96 is quote per token, and Graduated.tokensSeeded is the token0 side.
  • 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(), the Graduated event, or poolFactory.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 in Trading 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) or PriceRequired(), or the factory’s own ListedOnAnotherCurve() / 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 / burnFrom work in any phase. totalSupply() falls; sold, progress() and graduation do not change (the LP supply is fixed). Compute market cap from token.totalSupply(), not from config().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. protocolBps is set at lock time and changeable by nobody. The creator’s recipient is movable by its holder; the protocol’s side follows factory.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 / protocolOwed and withdrawn later with withdrawOwed(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 reverts PreviousProgrammeStillOwed while the old one still owes the position.
On the launch itself, the curve quoters now return zeros; collectFees() remains for any creator curve fees not yet collected.

Receiving fees in a contract

Name a contract as creatorFeeRecipient 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, enterFarming and setCreatorFeeRecipient are the creator side’s alone; collectFees, exitFarming, collectFarmingRewards and withdrawOwed are 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 setCreatorFeeRecipient makes the arrangement irrevocable; one with that code makes it revocable by whoever can reach it.
  • Farming is a decision in both directions. Exposing enterFarming lets 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 tokenId or token — never by its own balance.