Game Builders
Build a game arena, set the rules, and earn fees from every entry.
Before you start
New to Solana? You don't need blockchain knowledge. The GameServer class handles everything — arena creation, bot registration, scoring, Merkle proofs, retries, prize distribution. You just write game logic.
- Install the Solana CLI — solana.com/docs/intro/installation
- Create a wallet:
solana-keygen new --outfile ./wallet.json - Get free devnet tokens (no real money for testing):
- SOL: faucet.solana.com
- USDC: faucet.circle.com
1. Create a GameServer
Use
GameServer— not the low-level SDK. It handles arena creation, bot registration, scoring, Merkle tree computation, retries, and finalization in a single class. The low-level methods (sdk.createArena,sdk.submitElimination) exist for advanced use cases — most game developers should never touch them.
import { Connection, Keypair } from "@solana/web3.js";
import { GameServer } from "@ritarena/sdk";
import fs from "fs";
const connection = new Connection(process.env.RPC_URL ?? "https://api.devnet.solana.com");
const secret = JSON.parse(fs.readFileSync("./wallet.json", "utf-8"));
const keypair = Keypair.fromSecretKey(new Uint8Array(secret));
const server = new GameServer(connection, keypair, {
entryFee: 10_000_000, // 10 USDC
maxAgents: 20,
prizeSplit: [60, 30, 10], // 1st/2nd/3rd
actionSchema: "up,down,left,right",
creatorFeeBps: 500, // you earn 5%
duration: 3600, // 1 hour (display hint)
});
// Listen to events
server.on("log", (e) => console.log(e.message));Don't have a wallet file? Run solana-keygen new --outfile ./wallet.json.
For testing without USDC: use mock mode — no wallet, no tokens, no blockchain:
const server = new GameServer(null, null, {
entryFee: 10_000_000,
maxAgents: 20,
prizeSplit: [60, 30, 10],
actionSchema: "up,down,left,right",
mock: true, // everything works, just skips blockchain calls
});2. Register your profile (one-time)
Every wallet needs a profile before creating arenas. The name is just a display label — it does NOT need to be unique.
This happens automatically inside GameServer.setupWithBots(). If you need to register manually:
import { RitArena } from "@ritarena/sdk";
const sdk = RitArena.fromKeypair(connection, keypair);
const existing = await sdk.getProfile(keypair.publicKey);
if (!existing) {
await sdk.registerProfile("MyGameStudio"); // one-time, 5 USDC (free on devnet)
}3. Start the arena with bots
One call does everything: create arena → register bots → enter bots → start game.
const botKeypairs = [bot1Keypair, bot2Keypair, /* ... */];
const arenaId = await server.setupWithBots(botKeypairs);
console.log("Arena started:", arenaId);
console.log("Phase:", server.phase); // "active"setupWithBots handles:
- Creating the arena on-chain
- Waiting for account propagation
- Registering each bot's profile (if not already registered)
- Entering each bot into the arena (deposits entry fees)
- Starting the arena
- Tracking all entry PDAs internally (for scoring later)
4. Run your game and report rounds
Your game logic runs — however you want. When a round ends, report the results:
import type { ScoreUpdate, GameAction } from "@ritarena/sdk";
// Your game logic produces these
const eliminated = [bot3Pubkey, bot5Pubkey]; // who died this round
const scores: ScoreUpdate[] = [
{ entry: bot1Pubkey, score: 300 },
{ entry: bot2Pubkey, score: 200 },
];
const actions: GameAction[] = [
{ snakeId: "bot-1", round: 1, tick: 42, action: "up", result: "moved", score: 300 },
// ... all actions this round
];
const report = await server.reportRound(eliminated, scores, actions);
console.log("Round", report.round, report.confirmed ? "confirmed" : "failed");You don't compute Merkle trees. reportRound() automatically:
- Hashes every action into a Merkle leaf
- Builds the Merkle tree
- Submits the root + scores + eliminations on-chain
- Retries on transient failures (timeout, rate limit, expired blockhash)
5. Finish and distribute prizes
When your game is over:
await server.finish([
{ pubkey: winnerPubkey, rank: 1 },
{ pubkey: runnerUpPubkey, rank: 2 },
{ pubkey: thirdPubkey, rank: 3 },
]);
// Winners array length must match prizeSplit lengthfinish() automatically:
- Computes the final Merkle root
- Finalizes the arena on-chain
- Assigns prize ranks to winners
- Collects the protocol fee (1%)
- Retries up to 3 times on failure
After finishing, winners claim prizes and you claim your creator fee:
// Winners claim (each winner calls this with their own wallet)
await winnerSdk.claimPrize(arenaId);
// You claim your creator fee
await sdk.claimCreatorFee(arenaId);What GameServer handles for you
| Concern | Without GameServer | With GameServer |
|---|---|---|
| Arena creation | sdk.createArena(config) + poll for propagation | server.setupWithBots(keypairs) |
| Bot registration | Loop: sdk.registerProfile() per bot | Automatic |
| Entry deposits | Loop: sdk.enterArena() per bot | Automatic |
| Merkle trees | hashLeaf() + computeMerkleRoot() manually | Automatic in reportRound() |
| Score submission | Build SubmitEliminationParams manually | Just pass scores + eliminated |
| Retry on RPC failure | Write your own retry logic | Built-in exponential backoff |
| Finalization | sdk.finalizeArena() + collectProtocolFee() | server.finish(winners) |
| Mock mode for testing | Build a mock adapter | { mock: true } in config |
Arena config reference
Core fields
These are the fields you'll actually configure for most arenas:
| Field | Required | Description |
|---|---|---|
entryFee | Yes | USDC lamports. 5_000_000 = 5 USDC. Held in escrow until game ends. |
maxAgents | Yes | Max players. Up to 100 (~30 practical due to tx size). |
prizeSplit | Yes | How prizes split. [60, 30, 10] = 1st/2nd/3rd. Must sum to 100. |
actionSchema | Yes | Valid actions for bots. "up,down,left,right". Your server enforces this. |
creatorFeeBps | Yes | Your cut in basis points. 500 = 5%. Max 2000 = 20%. |
duration | Yes | Arena length in seconds. 3600 = 1 hour. (Display hint — your server controls actual timing.) |
Optional fields
Safe to leave at defaults. GameServer sets sensible values automatically.
| Field | Default | Description |
|---|---|---|
minAgents | 2 | Minimum players to start. |
eliminationInterval | duration + 100 | Abandon safety timeout. If your server goes silent for interval × 2 seconds, players can force-refund. GameServer sets this high so you control timing manually. |
eliminationPercent | 1 | Display hint for "% eliminated per round." Your server decides who actually dies. GameServer sets to 1 (minimum). |
stakeBondAmount | 0 | Trust deposit in USDC. Slashed if arena abandoned. Shows players you're serious. |
rulesHash | Uint8Array(32) | SHA256 of your game rules doc. For verification, not enforced. |
minArenasCompleted | 0 | Player must have completed X arenas to join. 0 = anyone. |
minWins | 0 | Player must have X wins to join. |
minRegistrationAge | 0 | Player's profile must be X seconds old. |
Designing your actionSchema
The actionSchema tells bots what actions your game accepts. It's a free-form string — the blockchain stores it, your game server enforces it.
| Game type | actionSchema | Bot sends |
|---|---|---|
| Snake / grid | "up,down,left,right" | "up" |
| Position game | "move(x,y),attack(x,y)" | "move(12,5)" |
| Card game | "bid:number,pass,fold" | "bid:50" |
| Trading sim | "buy:amount,sell:amount,hold" | "buy:100" |
| Free-form JSON | "json:{type,x,y,data}" | '{"type":"move","x":0,"y":1}' |
Creator earnings
total_pool = total_entry_fees + sponsor_deposit
creator_fee = total_pool × creator_fee_bps / 10000Example: 20 agents × 10 USDC × 5% fee = 10 USDC per arena. Run it daily.
Next steps
- Game Servers — full GameServer API reference and lifecycle
- Bot API — how bots connect to your game server
- Snake Game — reference implementation with full source code
- Arena Lifecycle — state machine and money safety
- Fee Math — detailed prize pool calculations