Supply Schedule & Tokenomics
Runner Protocol uses a flexible, step-based supply schedule to control how tokens are released during an auction. This page explains the MPS system, the on-chain SupplySchedule struct, schedule configuration, and how token allocations work.
MPS: Milli-Basis Points
Token supply release rates are expressed in MPS (Milli-Basis Points), where the total denominator represents 100% of the auction's token supply:
// Source: packages/v2/programs/cca-v2/src/constants.rs
pub const MPS: u32 = 10_000_000; // 1e7 = 100% of supply
Each unit of MPS equals one ten-millionth of the total supply (0.00001%). This granularity allows precise control over release rates without floating-point math.
| MPS Value | Percentage of Supply |
|---|---|
| 10,000,000 | 100% |
| 1,000,000 | 10% |
| 100,000 | 1% |
| 10,000 | 0.1% |
On-Chain SupplySchedule
The supply schedule is stored as a separate PDA account with seeds ["cca_supply", auction_config].
// Source: packages/v2/programs/cca-v2/src/state/supply_schedule.rs
pub struct SupplySchedule {
pub auction_config: Pubkey, // Parent auction reference
pub num_steps: u16, // Number of supply steps
pub total_blocks: u64, // Total auction blocks across all steps
pub steps: Vec<SupplyStep>, // Step definitions (max 50)
pub bump: u8,
pub _reserved: [u8; 32],
}
pub struct SupplyStep {
pub mps: u32, // Release rate per auction block (in MPS units)
pub block_delta: u64, // Number of auction blocks this step lasts
pub start_block: u64, // Computed: inclusive start block
pub end_block: u64, // Computed: exclusive end block [start, end)
}
Validation Rules
At auction initialization, the supply schedule is validated:
- Must sum to exactly MPS (10,000,000):
sum(step.mps * step.block_delta)must equal10,000,000 - Maximum 50 steps:
steps.len() <= MAX_SUPPLY_STEPS(50) - At least 1 step:
steps.len() > 0 - Last step must be meaningful: Last step's
mps > 0 - Contiguous blocks:
start_blockandend_blockare computed automatically from the step ordering
Key Methods
// Returns the MPS rate for a specific auction block
pub fn get_mps_for_block(&self, auction_block: u64) -> u64 {
for step in &self.steps {
if auction_block >= step.start_block && auction_block < step.end_block {
return step.mps as u64;
}
}
0 // beyond schedule
}
// Computes cumulative MPS issued between two auction blocks
pub fn compute_delta_mps(&self, from_block: u64, to_block: u64) -> Result<u32> {
// Walks through steps, computing overlap for each
// delta_mps += step.mps * blocks_in_range
}
Step-Based Schedule Examples
Constant Release
A simple schedule that releases all tokens evenly over 100 auction blocks:
| Step | MPS per Block | Duration (blocks) | Total MPS | Cumulative |
|---|---|---|---|---|
| 1 | 100,000 | 100 | 10,000,000 | 100% |
Each block releases 1% of the total supply. After 100 blocks, all tokens have been released.
Decelerating Release
A schedule that front-loads supply, rewarding early participation:
| Step | MPS per Block | Duration (blocks) | Total MPS | Cumulative |
|---|---|---|---|---|
| 1 | 200,000 | 20 | 4,000,000 | 40% |
| 2 | 100,000 | 30 | 3,000,000 | 70% |
| 3 | 30,000 | 100 | 3,000,000 | 100% |
40% of supply is released in the first 20 blocks (high rate), then 30% over the next 30 blocks (medium rate), and the final 30% trickles out over 100 blocks (low rate). Block ranges are computed automatically: [0,20), [20,50), [50,150).
Accelerating Release
A schedule that ramps up, with a slow start and fast finish:
| Step | MPS per Block | Duration (blocks) | Total MPS | Cumulative |
|---|---|---|---|---|
| 1 | 10,000 | 100 | 1,000,000 | 10% |
| 2 | 50,000 | 80 | 4,000,000 | 50% |
| 3 | 250,000 | 20 | 5,000,000 | 100% |
Decelerating schedules tend to reward early participants with more tokens at lower prices, which can incentivize early commitment. Accelerating schedules give late participants access to more supply but at potentially higher prices due to accumulated demand. Constant schedules are the simplest to reason about.
Auction Block Interval
On Solana, a slot (~400ms) is much faster than an Ethereum block (~12s). Runner Protocol uses a configurable auction block interval that groups multiple Solana slots into a single logical auction block:
auction_block_number = (current_slot - start_slot) / auction_block_interval
For example, with an auction_block_interval of 30 slots:
- 1 auction block = 30 Solana slots = ~12 seconds
- This closely matches Ethereum's block time, maintaining parity with Uniswap's CCA timing assumptions
The supply schedule's MPS values are per auction block, not per Solana slot. Checkpoints are created at auction block boundaries.
Supply Computation During Checkpoints
At each checkpoint, the protocol computes how much new supply has been unlocked:
delta_mps = supply_schedule.compute_delta_mps(
last_checkpoint_auction_block,
current_auction_block
);
// New cumulative MPS
new_cumulative_mps = prev_cumulative_mps + delta_mps;
// Total supply unlocked (in raw token units) for clearing price computation
total_supply_unlocked = total_supply * new_cumulative_mps / MPS;
The delta_mps feeds directly into the settlement accumulator update:
cumulative_mps_per_price += delta_mps * Q96 / clearing_price
BPS Token Allocations
When a launch is created through the Launchpad, the total token supply is split using BPS (basis points), where 10,000 BPS = 100%:
| Allocation | Description |
|---|---|
| Auction BPS | Tokens placed into the CCA auction for price discovery and sale to bidders |
| LP BPS | Tokens reserved for seeding the Raydium liquidity pool after graduation |
| Team BPS | Tokens allocated to the project team (delivered at settlement) |
The three allocations must sum to exactly 10,000 (100%):
require!(auction_bps + lp_bps + team_bps == BPS_DENOMINATOR, InvalidBpsSum);
The protocol enforces configurable bounds on each allocation:
| Parameter | Enforced By | Purpose |
|---|---|---|
min_auction_bps / max_auction_bps | ProtocolConfig | Ensure sufficient auction allocation |
min_lp_bps | ProtocolConfig | Ensure minimum liquidity |
max_team_bps | ProtocolConfig | Prevent excessive team allocations |
min_lp_proceeds_bps | ProtocolConfig | Ensure minimum proceeds go to LP |
Token Distribution at Step 2
During create_launch_step_2, tokens are minted and distributed to escrows:
auction_amount = total_supply * auction_bps / 10000
lp_amount = total_supply * lp_bps / 10000
team_amount = total_supply - auction_amount - lp_amount (remainder avoids rounding loss)
Team tokens are currently held in escrow and delivered to the creator at settlement (the enable_claims step). On-chain vesting with cliff and linear schedules is planned for a future release.