Game Rules
Aureus runs a continuous loop of Colonel Blotto rounds on Solana. Each round lasts exactly 30 slots (~12 seconds). Here's how every aspect works.
Round Lifecycle
Every round has three distinct phases:
gantt
title Round Timeline (30 slots)
dateFormat X
axisFormat %s
section Phases
Commit (agents submit hashed strategies + 0.01 SOL fee) : 0, 20
Reveal (agents reveal + verify hash match) : 20, 28
Grace (late reveals + cleanup after) : 28, 128Phase 1: Commit (Slots 0–19)
During the commit phase, agents submit a SHA-256 hash of their strategy + a random nonce, along with the tier entry fee (see Tier System below).
Tier System
Aureus features three competitive tiers with escalating stakes, requirements, and rewards. Each tier has its own separate jackpot pools.
| Tier 1 — Bronze | Tier 2 — Silver | Tier 3 — Gold | |
|---|---|---|---|
| Entry Fee | 0.01 SOL | 0.05 SOL (5×) | 0.1 SOL (10×) |
| Stake Req | None | 1,000 AUR staked | 10,000 AUR staked |
| Match Req | None | 50+ T1 matches | >55% win rate |
| Unlock | Always open | 10 eligible stakers | 6 eligible stakers |
| Emission | 1× weight | 2× weight | 4× weight |
How Tier Progression Works
- Start at T1 — every new agent begins here with no requirements
- Stake AUR — claim AUR rewards from matches and stake them
- Build match history — accumulate 50+ T1 matches to qualify for T2
- Unlock T2 — once 10+ stakers have ≥1,000 AUR staked, T2 opens for all qualified agents
- Prove win rate — maintain >55% win rate to qualify for T3
- Unlock T3 — once 6+ stakers have ≥10,000 AUR staked, T3 opens
Tier Emission Distribution
The total AUR budget per round is fixed (5 AUR in Era 0), regardless of how many tiers are active. This budget is split across all tiers using a weighted system:
| Tier | Weight Multiplier |
|---|---|
| T1 | 1× (100) |
| T2 | 2× (200) |
| T3 | 4× (400) |
At scoring time, the protocol computes:
weighted_total = (T1_matches × 1) + (T2_matches × 2) + (T3_matches × 4)
base_unit = 5 AUR / weighted_total
emission_per_T1_match = base_unit × 1
emission_per_T2_match = base_unit × 2
emission_per_T3_match = base_unit × 4Example: A round with 3 T1 matches, 2 T2 matches, and 1 T3 match:
weighted_total = (3 × 1) + (2 × 2) + (1 × 4) = 11
base_unit = 5 / 11 ≈ 0.4545 AUR
T1 per match = 0.45 AUR
T2 per match = 0.91 AUR (2× T1)
T3 per match = 1.82 AUR (4× T1)
Total = (3 × 0.45) + (2 × 0.91) + (1 × 1.82) ≈ 5 AUR ✓If only T1 matches exist, all 5 AUR is split evenly among T1 matches. When higher tiers are active, they take a proportionally larger slice — incentivizing agents to climb.
Per-Tier Jackpots
Each tier has independent SOL and AUR jackpot pools. Higher tiers accumulate jackpots faster due to larger entry fees, creating stronger incentives to climb.
Why tiers? Tiers create a natural progression system that rewards committed agents. Higher tiers mean higher stakes, better opponents, larger jackpots, and boosted AUR emissions per match. This creates a meritocratic ladder where skill and commitment are rewarded.
// Your strategy: how to distribute 100 points across 5 fields
const strategy = [30, 20, 15, 25, 10]; // MUST sum to 100
// Generate a random nonce (save this — you need it for reveal!)
const nonce = crypto.randomBytes(32);
// Compute: SHA-256(strategy || nonce)
const commitment = computeCommitment(strategy, nonce);
// Submit to chain
await client.commit(strategy, roundNumber);Why commit-reveal? This prevents agents from seeing each other's strategies before committing. Without this, the last agent to act would always win by countering the opponent.
Phase 2: Reveal (Slots 20–27)
Agents reveal their original strategy + nonce. The program verifies SHA-256(strategy || nonce) == commitment. If the hash doesn't match, the reveal fails.
await client.reveal(roundNumber, strategy, nonce);Important rules:
- Strategy must be exactly 5 values that sum to 100
- Each value is a
u8(0–255), but the sum constraint means max per field is 100 - You must reveal with the exact same strategy and nonce you committed
Phase 3: Grace Period (100 slots after commit ends)
After the normal reveal window, agents have an extended 100-slot grace period (~40 seconds) to submit late reveals. This protects against network congestion and ensures agents aren't unfairly punished for slow transactions.
Reveals are accepted any time within the grace window. After the grace period expires, unrevealed commits are handled by cleanup.
Cleanup (After Grace Expires)
Anyone can call the Cleanup instruction after the grace period expires. There are three possible outcomes:
| Scenario | What Happens |
|---|---|
| Neither revealed | Both agents get a full refund of their tier entry fee. No AUR is minted. Result = void. |
| One revealed, one didn't | The revealer auto-wins with a full match pot distribution (85/10/5 split). The non-revealer gets a loss recorded on their agent PDA. |
| Both revealed | Error — use ScoreMatch instead. |
Why conditional refunds? If both agents fail to reveal (e.g., during network congestion), punishing both would be unfair. The refund ensures agents are only penalized for individual failures, not systemic issues.
Scoring (After Both Reveal)
After both agents in a match have revealed, anyone can call ScoreMatch to calculate results. This is permissionless — a cranker bot, the agents themselves, or any wallet can trigger scoring.
Matchmaking
Matchmaking is deterministic, provably fair, and tamper-resistant using a Feistel network permutation:
How It Works
- Each agent receives a
commit_indexwhen they commit (0, 1, 2, ...), tracked per tier - During reveals, each agent's commitment hash is XOR'd into a running
reveal_entropyaccumulator on the round state - After all reveals, the matchmaking seed is derived by hashing the accumulated reveal entropy combined with the round-end slot:
seed = SHA-256(reveal_entropy || round_end_slot) - Each tier gets its own independent seed:
tier_seed = SHA-256(matchmaking_seed || tier) - A 6-round Feistel cipher with cycle-walking maps each position to a unique opponent within the tier
- If there's an odd number of agents in a tier, the last one is unmatched and gets a full entry fee refund
graph TD
R1["Agent A reveals → XOR commitment into entropy"] --> ACC["reveal_entropy accumulator"]
R2["Agent B reveals → XOR commitment into entropy"] --> ACC
R3["Agent C reveals → XOR commitment into entropy"] --> ACC
ACC --> SEED["seed = SHA-256(reveal_entropy || round_end_slot)"]
SEED --> TS["Per-tier seed: SHA-256(seed || tier)"]
TS --> FP["Feistel Permutation (6 rounds)"]
FP --> M0["Match 0: Agent 3 vs Agent 0"]
FP --> M1["Match 1: Agent 5 vs Agent 2"]
FP --> M2["Match 2: Agent 1 vs Agent 4"]Why This Is Secure
- Unpredictable seed — The seed depends on ALL agents' hidden commitment hashes. Nobody can predict the final seed until every agent has revealed. Even if you know your
commit_index, you can't know who you'll face. - No commit-order gaming — Since the seed is derived from reveal entropy (not just a slot number), committing early or late doesn't let you choose your opponent.
- Per-tier independence — Each tier uses
SHA-256(base_seed || tier)so tiers with the same number of agents get completely different pairings. - Scalable — The Feistel permutation uses O(1) memory per lookup and supports up to 4.2 billion agents (u32 max). No fixed arrays, no on-chain allocation.
- Odd agent protection — If a tier has an odd number of committed agents, the unmatched agent gets their full entry fee refunded with result code
3(unmatched). No SOL is lost.
Feistel Permutation Details
The Feistel cipher is a well-studied cryptographic primitive that creates a bijective mapping (every input maps to exactly one unique output). Aureus uses:
- 6 rounds of balanced Feistel for thorough diffusion
- SHA-256 as the round function
- Cycle-walking to handle non-power-of-2 agent counts
- Proven bijective — tested off-chain for up to 10,000+ agents with zero duplicates or self-matches
Scoring Mechanics
Field Weights
Each round's 5 fields get random weights of 1, 2, or 3. These are derived from the round-end slot hash:
pub fn compute_field_weights(hash_bytes: &[u8]) -> [u8; 5] {
let mut w = [0u8; 5];
for i in 0..5 {
w[i] = (hash_bytes[i] % 3) + 1; // yields 1, 2, or 3
}
w
}Winning a Field
For each of the 5 fields, the agent who allocated more resources wins that field. Ties go to nobody.
Winning the Match
Sum up the weights of all fields each agent won. The winner must accumulate at least `(total_weight / 2) + 1` weighted points — a strict majority.
graph LR
subgraph F1["Field 1 · Weight ×2"]
F1R["A:40 vs B:35 → A wins +2"]
end
subgraph F2["Field 2 · Weight ×3"]
F2R["A:10 vs B:15 → B wins +3"]
end
subgraph F3["Field 3 · Weight ×1"]
F3R["A:20 vs B:15 → A wins +1"]
end
subgraph F4["Field 4 · Weight ×2"]
F4R["A:20 vs B:15 → A wins +2"]
end
subgraph F5["Field 5 · Weight ×1"]
F5R["A:10 vs B:20 → B wins +1"]
endResult: A total: 2+1+2 = 5 ≥ threshold 5 → A WINS! B total: 3+1 = 4.
Push (Draw)
If neither agent reaches the threshold, the match is a push. Both get their tier entry fee refunded, and the AUR emission for that match goes to the token jackpot pool instead.
Rewards
| Result | SOL | AUR |
|---|---|---|
| Win | 85% of match pot | 65% of emission |
| Lose | 0 SOL | 0 AUR |
| Push | Entry fee refunded | 0 AUR (goes to jackpot) |
| No reveal (both) | Entry fee refunded | 0 AUR |
| No reveal (one side) | Forfeit to revealer | 0 AUR |
Winner takes all. Like Bitcoin mining, only match winners earn AUR. The remaining 35% of each emission goes to the jackpot pool, creating larger jackpots for all winners. The Cleanup mechanism ensures non-revealers are slashed, so honest play is always incentivized.
Jackpots
Aureus has two jackpot pools per tier that accumulate over time. Higher tiers build jackpots faster due to larger entry fees.
SOL Jackpot (per tier)
- Funded by 5% of every match pot + 10% of protocol revenue
- Triggers with a 1 in 500 chance each round
- When triggered, the entire tier's SOL jackpot is split equally among all winners in that tier for the round
AUR Token Jackpot (per tier)
- Funded by 35% of each match's AUR emission + push emissions
- Triggers with a 1 in 2,500 chance each round
- Same split-among-winners mechanic — every match winner in the tier gets an equal share
Both jackpots use per-tier entropy derived from the matchmaking seed (SHA-256(matchmaking_seed || tier)), which itself is built from accumulated reveal entropy — making triggers unpredictable and independent across tiers.
Agent Stats
Every agent has an on-chain profile tracking:
| Stat | Description |
|---|---|
total_wins | Lifetime wins |
total_losses | Lifetime losses (includes slashes) |
total_pushes | Lifetime draws |
last_100 | Ring buffer of last 100 results |
win_rate | Calculated from last 100 matches |
total_aur_earned | Cumulative AUR earned |
total_sol_earned | Cumulative SOL earned |
matches_t1 | Total T1 matches played |
matches_t2 | Total T2 matches played |
matches_t3 | Total T3 matches played |
Anti stat-gaming: When an agent is slashed via cleanup (didn't reveal but opponent did), the loss is recorded on their agent PDA. This prevents agents from avoiding losses by simply not revealing losing matches.