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.
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 fromAuctionState.clearing_price(Q96-encoded)total_tokens_cleared: Computed by descalingAuctionState.total_cleared_q96_x7from 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
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:
- Team token delivery: Transfers all tokens from the team escrow to the creator's Token-2022 token account
- Enable bidder claims: CPI to CCA V2
set_claim_slot, setting it to the current slot so bidders can immediately claim their purchased tokens - Renounce mint authority: Calls Token-2022
set_authoritywithNone, 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:
- Attacker participates in auction, acquiring tokens via
exit_bid - Before the official pool is created, attacker claims tokens early
- Attacker creates a competing pool at a manipulated (low) price
- 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_bidinstruction, which returnstokens_filled = 0andcurrency_spent = 0for non-graduated auctions - Tokens are recovered by anyone calling
recover_failed_auction, which CPI calls CCA V2sweep_unsold_tokens(returns all base tokens since non-graduated = 100% unsold), transfers escrow tokens to creator, and renounces mint authority - The launch enters the
FailedRecoveryterminal 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 againstlaunch_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.
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.