Skip to main content

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 ValuePercentage of Supply
10,000,000100%
1,000,00010%
100,0001%
10,0000.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 equal 10,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_block and end_block are 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:

StepMPS per BlockDuration (blocks)Total MPSCumulative
1100,00010010,000,000100%

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:

StepMPS per BlockDuration (blocks)Total MPSCumulative
1200,000204,000,00040%
2100,000303,000,00070%
330,0001003,000,000100%

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:

StepMPS per BlockDuration (blocks)Total MPSCumulative
110,0001001,000,00010%
250,000804,000,00050%
3250,000205,000,000100%
Choosing a Schedule

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%:

AllocationDescription
Auction BPSTokens placed into the CCA auction for price discovery and sale to bidders
LP BPSTokens reserved for seeding the Raydium liquidity pool after graduation
Team BPSTokens 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:

ParameterEnforced ByPurpose
min_auction_bps / max_auction_bpsProtocolConfigEnsure sufficient auction allocation
min_lp_bpsProtocolConfigEnsure minimum liquidity
max_team_bpsProtocolConfigPrevent excessive team allocations
min_lp_proceeds_bpsProtocolConfigEnsure 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)
note

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.