For the complete documentation index, see llms.txt. This page is also available as Markdown.

Bonding curve and graduation

A launchpad token's life on-chain has three states: bonding, grace, and graduated. This page describes how a token moves between them and what each transition does.

The bonding curve

When FireLaunch.ignite() runs, FireGraduator.createPositions() deploys numPositions concentrated V3 positions (must be in [5, 50] per registry bounds) covering [tickLower, tickUpper]. Tokens-per-position is tokensToSell / numPositions (integer division; remainder goes to the last position). The bonding-curve effect emerges from the way these stacked positions are consumed in order as price moves through them.

There's also a tail position for post-graduation liquidity. It's anchored at the graduation tick and extends outward:

  • Token is token0 → tail covers [graduationTick, MAX_TICK_aligned]

  • Token is token1 → tail covers [MIN_TICK_aligned, graduationTick]

(Tick bounds are aligned inward to the fee tier's tick spacing using TickMath.minUsableTick / maxUsableTick.) Even before graduation, the tail provides liquidity outside the curve range so the pool never goes empty.

Trading on the pool is unmodified V3 - buyers swap via SwapRouter02 like for any other token. The "curve" is just an emergent property of how the seed positions are stacked.

Recording the graduation condition

function recordGraduationCondition(address token) external;

Anyone can call this. It checks whether the pool's current tick has crossed the graduation tick (the comparison direction depends on token0/token1 ordering: token0 launches need currentTick ≥ graduationTick, token1 launches need currentTick ≤ graduationTick). On success it sets graduationConditionMetAt = block.timestamp, which starts the grace period.

  • One-shot, not idempotent: the function reverts with ConditionAlreadyRecorded if graduationConditionMetAt != 0. The grace timer is set once and never reset.

  • Pre-graduation only: reverts with AlreadyGraduated if the token has already graduated.

  • Owner-disable switch: the registry owner can flip graduationRecordingEnabled (via FireLaunch.setGraduationRecordingEnabled) to pause this entry point in an emergency. Disabled state reverts with GraduationRecordingDisabled.

  • Caller incentive: none baked into the contract - but UIs and bots are motivated to call it because they want to trigger graduation downstream.

The grace period

Configured in FireRegistry:

  • gracePeriodDuration - bounded [1 hour, 7 days]. Read the live mainnet value with FireRegistry.gracePeriodDuration().

  • forceGraduationDelay - applied from the token's createdAt. Read with FireRegistry.forceGraduationDelay().

Two roles can graduate during/after grace:

Caller
When they can graduate

Guardian

Immediately, as long as canGraduate(token) returns true (no grace period required)

Anyone

After gracePeriodDuration elapses since graduationConditionMetAt, and canGraduate(token) is true

Anyone (force)

After block.timestamp ≥ createdAt + forceGraduationDelay, even if recordGraduationCondition was never called. Bypasses the grace check.

The grace period exists to give the protocol a window to react to abnormal conditions before liquidity reshapes.

graduate()

What it does, in order - straight from FireGraduator._graduate:

  1. FireToken.setGraduated() - runs first. Sets graduated = true, vestingStart = block.timestamp, and emits Graduated(). From this moment on the skim hook is disabled and the on-token vesting clock starts.

  2. Burn every bonding-curve and tail position. Loops over positions[token], calling IUniswapV3Pool.burn and collect for each. Tracks principal and fees separately.

  3. Distribute pre-graduation fees via _distributeFees: all to the protocol (the contract splits graduationFeeBps to the registry treasury and the remainder to its integrator, but both are the same Kumbaya protocol address). See Fees.

  4. Mint a full-range NFT via NonfungiblePositionManager.mint with tickLower = TickMath.minUsableTick(tickSpacing), tickUpper = TickMath.maxUsableTick(tickSpacing). Recipient is FireStream. Any unused principal (mint dust) is sent to the protocol.

  5. FireStream.receiveNFT(token, nftId, creator) - registers the NFT for fee streaming and pins the creator address.

  6. FuelVault.onGraduated(token) - sets graduated[token] = true and burnTime[token] = block.timestamp + registry.burnCountdownDuration().

  7. FireLaunch.setGraduated(token, nftId) - flips the launch state's graduated flag and stores graduatedNftId.

After graduate completes the pool is just an ordinary Uniswap V3 pool. There's nothing launchpad-specific in the trading path anymore.

State you can read

Use FireLaunch.getLaunchState(token) to inspect a token's lifecycle state:

FireGraduator.canGraduate(token) returns true when graduation is possible (price reached, OR forceGraduationDelay elapsed since createdAt).

You can also read FireToken.graduated (public bool) and FireToken.vestingStart (public uint256) directly off the token contract.

What happens to the tail liquidity?

The tail position is burned at graduation, just like the bonding-curve positions. It does not survive into the graduated state as a separate position. The full-range NFT held by FireStream is the only launchpad-related liquidity afterward - alongside whatever organic LPs add post-graduation.

Errors to handle

FireGraduator errors:

Error
When

AlreadyGraduated

graduate() called twice, or claimFees() called post-graduation on the graduator

ConditionNotMet

graduate() called but canGraduate(token) returns false (price not at tick AND force-graduation delay not elapsed)

GracePeriodNotElapsed

Non-guardian called graduate() after recordGraduationCondition but before gracePeriodDuration elapsed

FireLaunch errors relevant to graduation:

Error
When

AlreadyGraduated

recordGraduationCondition called on a graduated token

ConditionAlreadyRecorded

recordGraduationCondition called twice for the same token

ConditionNotMet

recordGraduationCondition called when the price hasn't crossed the graduation tick

GraduationRecordingDisabled

recordGraduationCondition called while the registry owner has paused the entry point

Note: OnlyGuardian is a FuelVault error (used for guardian-relayed gifts), not a graduation error. The guardian path on graduate() is gated by an if (isGuardian || forceGraduationAvailable) check rather than a single revert.

What changes for integrators after graduation

  • Pool address is unchanged - it's still the same V3 pool. Quotes, swaps, and existing tooling continue to work.

  • NFT ownership - NonfungiblePositionManager.ownerOf(positionTokenId) == FireStream.

  • Fee claim path - pre-grad fees came from FireGraduator.claimFees(); post-grad fees come from FireStream.claimFees(). See Fees and credits.

  • Vesting - FireToken.releaseVested() becomes meaningful (the schedule unlocks linearly from graduation).

  • FuelVault burn timer is now ticking. After it elapses, anyone can call executeBurn(token) to permanently destroy unclaimed user credits (creator-bucket credits are protected).

Last updated