Skip to main content

Seasons & Leagues

Time-bounded competitive periods with tiered skill brackets.


What Are Seasons?

A season is a time-bounded competitive period during which players compete for rankings, rewards, and achievements. When a season ends:

  • Final standings are calculated
  • Prize distributions are triggered
  • Promotion/relegation between leagues is applied
  • Achievement NFTs are minted for qualifying players

Seasons are the primary economic cycle of a competitive game.


What Are Leagues?

Leagues are skill tiers within a season. They:

  • Segment the player population by skill level
  • Determine matchmaking brackets
  • Define separate prize pools and reward tiers
  • Apply promotion and relegation at season boundaries
Season 1
├── League: Diamond
│ └── No promotion (top league)
│ └── Bottom 10% relegated to Gold
├── League: Gold
│ ├── Top 10% promoted to Diamond
│ └── Bottom 20% relegated to Silver
├── League: Silver
│ ├── Top 20% promoted to Gold
│ └── Bottom 20% relegated to Bronze
└── League: Bronze
└── Top 20% promoted to Silver

Season Configuration

{
season_id: 1,
name: "Season of Smoke",
start: "2026-04-01T00:00:00Z",
end: "2026-06-30T23:59:59Z",

leagues: [
{
id: "diamond",
name: "Diamond",
min_elo: 2000,
promotion_percent: 0, // top league
relegation_percent: 10,
reward_multiplier: 4.0
},
{
id: "gold",
name: "Gold",
min_elo: 1600,
promotion_percent: 10,
relegation_percent: 20,
reward_multiplier: 2.5
},
{
id: "silver",
name: "Silver",
min_elo: 1200,
promotion_percent: 20,
relegation_percent: 20,
reward_multiplier: 1.5
},
{
id: "bronze",
name: "Bronze",
min_elo: 0,
promotion_percent: 20,
relegation_percent: 0, // bottom league
reward_multiplier: 1.0
}
],

reward_pool: {
gameplay_token_total: 100000, // total gameplay currency per season
settlement_token_total: 0, // optional settlement currency pool
distribution: "proportional_rank" // or "tier_fixed", "winner_take_all"
}
}

All parameters are per-game. A casual puzzle game might have 2 leagues and a 2-week season. A hardcore competitive game might have 6 leagues and a 3-month season.


Season Lifecycle

┌───────────┐     ┌───────────┐     ┌───────────┐     ┌───────────┐
│ CONFIGURE │────►│ ACTIVE │────►│ LOCKING │────►│ FINALIZED │
│ │ │ │ │ │ │ │
│ leagues │ │ matches │ │ results │ │ rewards │
│ defined │ │ played │ │ locked │ │ claimable │
│ │ │ standings │ │ results │ │ promos / │
│ │ │ updated │ │ submitted │ │ relegation│
└───────────┘ └───────────┘ └───────────┘ └───────────┘

States

StateDescription
ConfigureGame operator sets season parameters, league definitions, reward pools
ActivePlayers compete. Matches are played. Standings updated in real-time
LockingSeason deadline passed. No new matches accepted. Final standings computed
FinalizedResults submitted on-chain. Prizes claimable. Promotions/relegations applied

On-Chain Settlement

Only the finalization step touches the blockchain:

struct SeasonResult {
uint256 seasonId;
address[] players; // ordered by rank
uint256[] rewards; // gameplay currency per player
uint8[] leagues; // league assignment for next season
uint256 timestamp; // finalization time
}

The game server signs the result payload, and either:

  • Server submits the transaction directly (server pays gas)
  • Players claim individually using a signed attestation (players pay gas)

Promotion & Relegation

At season end, the server applies league transitions:

function applyPromotionRelegation(season, standings) {
for (const league of season.leagues) {
const players = standings.filter(p => p.league === league.id);
const sorted = players.sort((a, b) => b.elo - a.elo);
const total = sorted.length;

// Promote top N%
const promoteCount = Math.floor(total * league.promotion_percent / 100);
sorted.slice(0, promoteCount).forEach(p => p.next_league = league.next_up);

// Relegate bottom N%
const relegateCount = Math.floor(total * league.relegation_percent / 100);
sorted.slice(total - relegateCount).forEach(p => p.next_league = league.next_down);

// Everyone else stays
sorted.slice(promoteCount, total - relegateCount).forEach(p => p.next_league = league.id);
}
}

Integration Guide

For Your Game

  1. Define leagues — how many tiers, what ELO/rating ranges
  2. Set season duration — weeks or months, based on your game's pace
  3. Configure rewards — total pool per season, distribution model
  4. Implement matchmaking — pair players within the same league tier
  5. Track results — server records wins/losses and updates standings
  6. Finalize on-chain — submit results when season ends, enable claims
  7. Apply transitions — promote/relegate and start next season