Skip to main content

Graduation & Liquidity

Graduation is the mechanism by which a successful auction transitions into a live, tradeable market. When an auction raises enough capital, it "graduates" -- triggering a settlement pipeline that creates permanent liquidity and enables token claims.

What Triggers Graduation

Graduation is a computed property, not a manual action. An auction is considered graduated when:

is_graduated = currency_raised_q96_x7 >= required_currency_raised

The required_currency_raised threshold is set immutably at auction creation and cannot be changed afterward. The graduation check uses the U256 field currency_raised_q96_x7 from the CCA V2 AuctionState, which tracks total currency raised in Q96-scaled form.

note

Graduation is checked at finalization time, after the auction's end_slot has passed. The auction does not stop early when the threshold is met -- it continues running until the scheduled end to allow continued price discovery.

The Settlement Pipeline

Once an auction has ended and graduated, settlement proceeds through four sequential, permissionless steps. Each step is a separate on-chain transaction that anyone can trigger -- the design enables keeper bots to drive settlement without requiring the creator's participation.

Step 1: finalize_auction

The Launchpad program reads the CCA V2 auction's final state and records critical values into the LaunchConfig:

  • final_clearing_price: The auction's clearing price from AuctionState.clearing_price (Q96-encoded)
  • total_tokens_cleared: Computed by descaling AuctionState.total_cleared_q96_x7 from Q96 (right-shift by 96 bits)
  • finalized_slot: The current Solana slot (used for emergency timeout calculation)

The total_proceeds field is intentionally not set here. The currency_raised_q96_x7 field is scaled by both Q96 and MPS, making simple Q96 descaling incorrect. The actual proceeds amount is determined authoratively in the next step.

Transition: AuctionReady or AuctionActive --> Finalized

Step 2: sweep_to_escrow

Calls CCA V2's sweep_currency instruction via CPI, which transfers all raised quote tokens from the CCA quote vault into the Launchpad's proceeds escrow PDA.

After the CPI completes, the instruction reloads the proceeds escrow account and records the actual balance:

launch_config.total_proceeds = proceeds_escrow.amount;

This is the authoritative source for total proceeds -- derived from the actual token balance, not from computed accumulators.

Transition: Finalized --> ProceedsSwept

Step 3: create_raydium_pool

This is the most complex settlement step, involving 31 accounts. It creates a Raydium CPMM (Constant Product Market Maker) liquidity pool using the auction-discovered price.

Proceeds Distribution

total_proceeds (from proceeds_escrow)

├── protocol_fee = total_proceeds * protocol_fee_bps / 10000
│ └── Transferred to protocol treasury

└── remaining = total_proceeds - protocol_fee

├── lp_proceeds = remaining * lp_proceeds_bps / 10000
│ └── Deposited into Raydium pool (paired with LP tokens)

└── creator_proceeds = remaining - lp_proceeds
└── Transferred to creator's quote token account

LP Token Amount Calculation

The number of base tokens deposited into the Raydium pool is computed to match the auction's clearing price:

lp_budget = total_supply * lp_bps / 10000;
lp_token_amount_for_price = (lp_proceeds << 64) / (clearing_price >> 32);
lp_token_amount = min(lp_budget, lp_token_amount_for_price);

The shift-based calculation (<< 64 and >> 32) avoids u128 overflow while remaining algebraically equivalent to lp_proceeds * Q96 / price.

Raydium CPMM Integration

The pool is created via raw invoke_signed to the Raydium CPMM program (typed CPI would overflow the SBF 4096-byte stack frame limit due to the large account struct). Key requirements:

  • Canonical token ordering: Raydium requires token_0_mint.key() < token_1_mint.key() -- the instruction handles reordering automatically
  • Pool creation fee: 0.15 SOL from the fee escrow, transferred to Raydium's fee receiver
  • 20 accounts passed to Raydium in exact order

LP Token Burning

All LP tokens received from pool creation are immediately burned in the same transaction:

// Burn all LP tokens -- permanent, irremovable liquidity
spl_token::instruction::burn(
token_program,
creator_lp_token,
lp_mint,
escrow_authority, // PDA signer
amount, // Entire LP balance
)?;

This makes the liquidity permanent. No one -- not the creator, not the protocol admin, not any governance -- can ever remove this liquidity.

Transition: ProceedsSwept --> PoolCreated

Why Burn LP Tokens?

Burning LP tokens is a strong trust guarantee. It means the liquidity pool cannot be rugged. The tokens will always have a market to trade on, backed by the capital raised during the auction. This is enforced programmatically -- there is no option to keep the LP tokens.

Step 4: enable_claims

The final settlement step performs three critical actions:

  1. Team token delivery: Transfers all tokens from the team escrow to the creator's Token-2022 token account
  2. Enable bidder claims: CPI to CCA V2 set_claim_slot, setting it to the current slot so bidders can immediately claim their purchased tokens
  3. Renounce mint authority: Calls Token-2022 set_authority with None, permanently preventing any new tokens from being minted
// CPI: set claim slot to current slot
invoke_set_claim_slot(cca_program, accounts, clock.slot)?;

// Renounce mint authority -- irreversible
spl_token_2022::instruction::set_authority(
token_2022_program,
token_mint,
None, // New authority = None (renounce)
AuthorityType::MintTokens,
mint_authority, // Current authority (PDA)
)?;

Transition: PoolCreated --> Settled (terminal)

Why Claims Are Gated Behind Pool Creation

Token claims are intentionally disabled until the liquidity pool exists. This prevents a critical attack:

  1. Attacker participates in auction, acquiring tokens via exit_bid
  2. Before the official pool is created, attacker claims tokens early
  3. Attacker creates a competing pool at a manipulated (low) price
  4. The official pool creation fails or is undermined

By gating claims behind pool creation, the protocol ensures that the first available market for the token is the official one, seeded at the fair auction-discovered price.

What Happens If an Auction Does Not Graduate

If the auction ends without meeting its fundraising threshold:

  • The auction is not graduated
  • No liquidity pool is created
  • All bidders receive full refunds -- they can exit their bids via CCA V2's exit_bid instruction, which returns tokens_filled = 0 and currency_spent = 0 for non-graduated auctions
  • Tokens are recovered by anyone calling recover_failed_auction, which CPI calls CCA V2 sweep_unsold_tokens (returns all base tokens since non-graduated = 100% unsold), transfers escrow tokens to creator, and renounces mint authority
  • The launch enters the FailedRecovery terminal state

This is the safe default: if a launch does not attract sufficient interest, no one loses their capital.

Emergency Recovery

If the settlement pipeline stalls at any intermediate step (Finalized, ProceedsSwept, or PoolCreated) for longer than the configured timeout, anyone can trigger an emergency withdrawal:

require!(
clock.slot > launch_config.finalized_slot + launch_config.emergency_timeout_slots,
EmergencyTimeoutNotReached
);

Key properties of the emergency path:

  • Timeout is immutable: Snapshotted into LaunchConfig at creation time from ProtocolConfig. The protocol admin cannot retroactively change it to trap funds.
  • Permissionless trigger: Anyone can call emergency_withdraw -- the creator does not need to sign. All destination accounts are validated against launch_config.authority.
  • Mint authority renounced: Even in the emergency path, mint authority is revoked. No new tokens can ever be created.
  • Proceeds returned to creator: LP tokens, team tokens, and any remaining proceeds are returned to the creator.
caution

In the emergency withdrawal path, proceeds are returned to the creator, not distributed pro-rata to bidders. Bidders can still recover their quote currency directly from CCA V2 via exit_bid. On-chain pro-rata refund distribution is planned for a future release.