Skip to main content
On-Chain vs Off-Chain State for Solana Games: 6 Rules - Featured image
Gaming

On-Chain vs Off-Chain State for Solana Games: 6 Rules

BUILDERZ

BUILDERZ

Share

In this guide

The decisions and working steps ahead.

    On chain or off chain: the short answer

    Rules 1 to 3: ownership, scarcity, and settlement

    Rule 1: Put ownership on chain, not progress

A Solana game rarely dies because the chain is slow.

It dies because someone put the wrong state on chain.

Each move asks for a wallet prompt. Each match writes a new account.

Players quit before the economy even matters.

Draw a clear line instead. Ownership and settlement go on chain.

Tick-rate state stays off. Here are 6 rules, the failure each prevents, and 1 Anchor account to copy.

Save this as a design checklist for your next build.

On chain or off chain: the short answer

Put state on chain when players gain from checking it without trusting you.

Item ownership, supply, and season winners pass that test. Keep the rest on your servers, where it's fast, private, and cheap to change.

A quick test helps. Ask these three questions about each piece of state:

  • Will a player own or trade it outside your game?
  • Does someone other than you need to verify the result?
  • Can it move value, such as a prize, a reward, or a fee?

If all three answers are no, the state stays off chain. If one is yes, it's a candidate, and the rules below decide the rest.

Here's how that sorts a typical game:

  • On chain: item ownership, supply caps, season results, prize payouts, and reward claims.
  • Off chain: player movement, physics, matchmaking queues, chat, anti-cheat signals, and telemetry.
  • Both, with a link: a score your server computes and settles on chain once per match or season.

Sort state this way before writing program code. Our Solana game development page covers the same boundary. The player loop comes first. The chain serves the loop, not the other way around.

Rules 1 to 3: ownership, scarcity, and settlement

Rule 1: Put ownership on chain, not progress

An item a player can trade belongs in a token or NFT account. Their XP bar, quest flags, and session state don't. Failure it prevents: a wallet prompt for every level up, and a program you redeploy each time a designer tunes a quest.

Our historical Reavers build is an NFT idle game where players earn as they progress, level by level. They plunder cities, raid docks, and steer clear of prison or mutiny. In a loop like that, progress is the bulk of the state, and the NFTs are the part players own.

Rule 2: Enforce scarcity where you mint

If an item is rare, the cap has to live in the program that mints it. A database flag that says "only 500 swords" means nothing to a buyer, because you can change it. Failure it prevents: a market that loses trust the first time supply looks off.

Rule 3: Settle results, don't stream them

Write the outcome of a match or a season, not every action inside it. One signed write per result keeps fees and prompts low. Failure it prevents: a game that burns fees on moves no one will audit.

For Zeta Markets, we built the Mad Wars leaderboard xNFT for their Mad Wars trading competition. A competition board shows why this rule matters. Results are what people check and argue about, so they're the part worth making verifiable.

Rules 4 to 6: speed, trust, and data

Rule 4: Keep tick-rate state off chain

Anything that changes many times a second runs on a game server or the client. Movement, hit checks, and physics need replies in milliseconds. Failure it prevents: a game that feels like a form you submit, not a game you play.

Rule 5: Keep matchmaking and anti-cheat private

Matchmaking needs live data on skill, region, and queue time. Anti-cheat needs signals you don't want cheaters to read. Public accounts are readable by anyone, so both belong on your servers.

Failure it prevents: bots that read your rules from the chain and farm them.

Rule 6: Send telemetry to analytics, not accounts

Session length, drop-off points, and crash logs feed your design work. They're high volume and change shape often, so store them in a pipeline you control. Failure it prevents: paying rent for data nobody outside your team needs.

The server side is scoped on our game development services page: authoritative state, matchmaking, ranking, and telemetry. The chain should see only what the rules above let through. Frame-by-frame play stays off chain unless the design gives a clear reason.

A reference architecture you can copy

Think of the game as three layers. The client renders and sends inputs. A game server owns live state, runs matches, and decides results.

A Solana program records what players need to verify: items, scores, and payouts. The server signs each result with a settlement key, and the program checks that key before it writes. Players sign only when value moves, such as a prize claim or a trade.

Here is a minimal Anchor account for a season leaderboard. It stores one row per player per season, not one per match.

Code examplerust
use anchor_lang::prelude::*;

#[account]
#[derive(InitSpace)]
pub struct SeasonScore {
    pub season: Pubkey,            // 32 bytes: season config account
    pub player: Pubkey,            // 32 bytes: player wallet
    pub best_score: u64,           // 8 bytes
    pub matches_settled: u32,      // 4 bytes
    pub last_match_hash: [u8; 32], // 32 bytes: hash of the off-chain match log
    pub bump: u8,                  // 1 byte
}

#[derive(Accounts)]
pub struct SettleMatch<'info> {
    #[account(
        init_if_needed,
        payer = settler,
        space = 8 + SeasonScore::INIT_SPACE, // 8-byte discriminator + 109
        seeds = [b"score", season.key().as_ref(), player.key().as_ref()],
        bump
    )]
    pub score: Account<'info, SeasonScore>,
    /// CHECK: used only as a PDA seed
    pub season: UncheckedAccount<'info>,
    /// CHECK: used only as a PDA seed
    pub player: UncheckedAccount<'info>,
    #[account(mut)]
    pub settler: Signer<'info>, // game server key; compare it to the season config
    pub system_program: Program<'info, System>,
}

The byte sizes follow the Anchor space reference, which adds 8 bytes for the account discriminator. In production, load the season config and reject any settler that isn't its stored authority.

The match log hash lets anyone check a dispute later without storing the full log on chain. Keep the log in your own storage with a clear retention rule. If a player disputes a result, you hash the stored log and compare.

Where wallet prompts belong

Map every prompt before you build. A player should see one when they mint, buy, trade, or claim a prize, and almost never during a match. If a design needs a prompt mid-match, treat that as a sign the state belongs off chain.

Show the item, the amount, and what happens next before each prompt.

Costs, limits, and mistakes

You can model each choice from the Solana fee docs. The base fee is 5,000 lamports per signature, split 50% burned and 50% to the validator. A priority fee is extra and goes to the validator.

Storage costs a refundable deposit. The Solana accounts docs set the minimum at (account size + 128) x 3,480 lamports per byte-year x 2 years. For the 117-byte account above, that's 1,705,200 lamports, which you get back when you close it.

Two more limits shape the design. Per the Solana transactions docs, a transaction can be at most 1,232 bytes, and its recent blockhash is valid for 150 slots. Batch settlements to fit, and plan retries for results that expire before they land.

Mistakes to avoid

  • One account per match. Rent piles up and indexing slows down. Keep one row per player per season.
  • Signing every move. Prompts kill flow. Ask for a signature only when value moves.
  • Trusting the client's score. The server decides results, and the program checks the server key.
  • Rewards before fun. A token won't save a weak loop. Prove the loop first.
  • No pause path. Give settlement an admin switch so you can stop one feature without taking down the game.

FAQ

Should all game state live on Solana?

No, only state that players need to own, trade, or verify should live on chain. Movement, physics, matchmaking, and telemetry run better on your servers. Moving them on chain adds prompts, fees, and public data without giving players anything back.

How do I stop players from faking scores?

Have your game server decide the result and sign the settlement with its own key. The program then rejects any write that isn't signed by that stored authority. Never accept a score the client reports on its own.

What does it cost to store a leaderboard entry on Solana?

It costs a refundable rent deposit based on the account's size. The formula in the Solana accounts docs puts a 117-byte account at the figure shown above, and you get it back when you close the account. Each write also pays the base fee per signature.

Can I move state on chain later?

Yes, but plan for it on day one. Give each piece of state a clear owner, so a later move is a migration, not a rewrite. Ownership changes are the hardest to undo, so decide those first.

Draw the line before you build the economy

The right split keeps your game fast and your assets worth trusting. Ownership, scarcity, and settlement go on chain, and everything with a tick rate stays off.

Want a second pair of eyes on where your line sits? Join a call about your Solana game.

Want to turn your idea into reality?

Book a 20-minute intro call, or send a short project brief. We reply within one business day.