Skip to main content

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:

FieldTypeDescription
max_priceu128 (Q96)The maximum price the bidder is willing to pay per token
raw_amountu64The total budget in quote currency (e.g., USDC) committed
amount_q96U256Effective demand, MPS-adjusted at placement time
ownerPubkeyThe wallet that will receive tokens and refunds
start_cumulative_mpsu32Supply 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, their amount_q96 values are summed into the tick's currency_demand_q96 field.
  • Linked-list insertion -- when a bid creates a new price level, the caller provides a prev_tick hint. 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:

  1. Higher max_price fills first. If Alice bids with a max_price of $0.20 and Bob bids $0.15, Alice's bid fills before Bob's at any clearing price at or below $0.20.
  2. Ties are resolved pro-rata. If two bids share the same max_price and there is not enough supply for both, they split the available tokens proportionally to their amount_q96 values.
  3. Below clearing price gets nothing. If a bid's max_price is 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:

  1. Start with total aggregate demand above the current clearing price (sum_currency_demand_above_clearing)
  2. Compute candidate price = ceil(sum_demand / total_supply_unlocked)
  3. 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
  4. Repeat until the candidate price stabilizes between two consecutive ticks
  5. 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):

Biddermax_priceBudget (USDC)amount_q96
Alice300 * Q96100B1e11 * Q96
Bob200 * Q9650B5e10 * Q96
Carol100 * Q9620B2e10 * 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.

Key Takeaway

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

FeatureCCADutch AuctionBatch AuctionBonding Curve
Price discoveryContinuous (block-by-block)Single moment (descending)Single moment (at close)Per-trade (algorithmic)
Timing gamesEliminatedHigh (race to accept)High (last-second sniping)Per-trade (MEV)
MEV riskLow (uniform pricing)HighLow-MediumVery High (sandwich)
Early participationRewardedPunished (pay more)NeutralNeutral
FairnessAll pay same price per blockFirst acceptable bid winsAll pay same priceIndividual prices
note

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.