How CCA Works
A Continuous Clearing Auction (CCA) is an auction mechanism that distributes tokens over time at a fair, market-determined price. Unlike traditional auctions that clear in a single moment, a CCA clears continuously -- discovering the price block by block as supply is gradually released.
The Core Idea
In a CCA, tokens are not sold all at once. Instead, a fixed total supply is released according to a configurable schedule over the duration of the auction. At each auction block (a configurable group of Solana slots), a clearing price is computed based on current demand, and tokens are allocated to bidders at that price.
The clearing price is defined as:
The highest price at which all tokens available for the current block can be sold.
This means the price naturally rises when there is more demand and falls (to the floor) when there is less. Every participant in the same block pays the same clearing price -- no one gets a better deal by being faster.
How Bids Work
A bid in the CCA V2 program consists of these on-chain fields:
| Field | Type | Description |
|---|---|---|
max_price | u128 (Q96) | The maximum price the bidder is willing to pay per token |
raw_amount | u64 | The total budget in quote currency (e.g., USDC) committed |
amount_q96 | U256 | Effective demand, MPS-adjusted at placement time |
owner | Pubkey | The wallet that will receive tokens and refunds |
start_cumulative_mps | u32 | Supply already released when bid was placed |
When you place a bid, you are saying: "I am willing to buy tokens at any price up to my max_price, and I am committing this much capital to do so."
Your bid remains active across all remaining auction blocks. You do not need to bid again each block. The amount_q96 field is computed at placement time as:
amount_q96 = raw_amount * Q96 * MPS / mps_remaining
Where mps_remaining = MPS - cumulative_mps at the time of placement. This scaling ensures bids placed later -- when less supply remains -- have their effective demand adjusted upward, maintaining correct pro-rata distribution across the auction's lifetime.
The Tick Linked List
Bids are organized by price level using on-chain Tick accounts. Each tick represents a discrete price level and stores the aggregate demand from all bids at that price.
Floor Tick ($0.10) ──► Tick ($0.15) ──► Tick ($0.20) ──► Tick ($0.25) ──► (end)
demand: $0 demand: $2,000 demand: $500 demand: $8,000
Ticks form a singly-linked list ordered by ascending price. Each tick's next_tick_price field points to the next higher price level. The list is traversed during checkpoint creation to discover the clearing price.
Key properties:
- Tick spacing defines the minimum price granularity (configurable per auction, minimum of 2). Prices must align to this spacing from the floor price.
- Demand aggregation -- when multiple bids share the same
max_price, theiramount_q96values are summed into the tick'scurrency_demand_q96field. - Linked-list insertion -- when a bid creates a new price level, the caller provides a
prev_tickhint. The protocol validates ordering (prev.price < new.price < prev.next_tick_price) and splices the new tick into the list.
Each Tick is a separate PDA account with seeds ["cca_tick", auction_config, price.to_le_bytes()].
Bid Priority and Filling
Bids are filled according to a strict priority system:
- Higher
max_pricefills first. If Alice bids with amax_priceof $0.20 and Bob bids $0.15, Alice's bid fills before Bob's at any clearing price at or below $0.20. - Ties are resolved pro-rata. If two bids share the same
max_priceand there is not enough supply for both, they split the available tokens proportionally to theiramount_q96values. - Below clearing price gets nothing. If a bid's
max_priceis below the current clearing price, it receives zero allocation for that block.
Price Floor and Monotonicity
Every auction has a configured floor price (floor_price). The clearing price can never go below this floor, regardless of demand:
let new_clearing_price = candidate_price.max(config.floor_price);
Additionally, the clearing price is monotonically non-decreasing across checkpoints:
let final_clearing_price = new_clearing_price.max(state.clearing_price);
This ensures that once a price level is established, it cannot decrease -- preventing clearing price manipulation through strategic bid withdrawal.
Clearing Price Discovery
At each auction block, a checkpoint is created by walking the tick linked list. The algorithm:
- Start with total aggregate demand above the current clearing price (
sum_currency_demand_above_clearing) - Compute candidate price =
ceil(sum_demand / total_supply_unlocked) - If the candidate price is at or above the next tick, subtract that tick's demand from the running sum and advance to the next tick
- Repeat until the candidate price stabilizes between two consecutive ticks
- Apply floor price and monotonicity constraints
This process walks at most 10 ticks per transaction (MAX_TICKS_PER_TX = 10). If more ticks need processing, the checkpoint enters a Resolving state and can be resumed in subsequent transactions. Once all ticks are processed, it becomes Finalized.
Checkpoint States: Uninitialized ──► Resolving ──► Finalized
▲ │
└────┘ (multi-TX resumption)
Worked Example
Consider an auction with:
- Total supply: 1,000,000,000 tokens
- Floor price: small (effectively allowing any price above zero)
- MPS per block: 500,000 (5% of supply per auction block)
Three bids are placed (all at cumulative_mps = 0):
| Bidder | max_price | Budget (USDC) | amount_q96 |
|---|---|---|---|
| Alice | 300 * Q96 | 100B | 1e11 * Q96 |
| Bob | 200 * Q96 | 50B | 5e10 * Q96 |
| Carol | 100 * Q96 | 20B | 2e10 * Q96 |
Tick state after all bids:
Tick @ 100*Q96: demand = 2e10 * Q96, next = 200*Q96
Tick @ 200*Q96: demand = 5e10 * Q96, next = 300*Q96
Tick @ 300*Q96: demand = 1e11 * Q96, next = 0 (highest)
sum_demand = 1.7e11 * Q96
Checkpoint resolution (Block 0):
candidate_price = ceil(1.7e11 * Q96 / 1e9) = 170 * Q96
Is 170*Q96 >= next_tick (100*Q96)? Yes.
Subtract Carol's tick: sum = 1.5e11 * Q96
candidate = ceil(1.5e11 * Q96 / 1e9) = 150 * Q96
next_tick = 200*Q96
Is 150*Q96 >= 200*Q96? No. Stop.
Result: Clearing price converges at 150 * Q96. Carol's bid (max_price = 100 * Q96) is below clearing and receives no allocation. Alice and Bob's bids are fully above clearing and receive proportional token allocations.
The clearing price is not set by any individual bidder. It emerges from the collective demand of all participants, ensuring fair market-driven pricing.
How CCA Compares
| Feature | CCA | Dutch Auction | Batch Auction | Bonding Curve |
|---|---|---|---|---|
| Price discovery | Continuous (block-by-block) | Single moment (descending) | Single moment (at close) | Per-trade (algorithmic) |
| Timing games | Eliminated | High (race to accept) | High (last-second sniping) | Per-trade (MEV) |
| MEV risk | Low (uniform pricing) | High | Low-Medium | Very High (sandwich) |
| Early participation | Rewarded | Punished (pay more) | Neutral | Neutral |
| Fairness | All pay same price per block | First acceptable bid wins | All pay same price | Individual prices |
A batch auction is effectively a CCA with a single auction block. CCA generalizes batch auctions into continuous time, adding gradual price discovery and early participation rewards.