Skip to content

Latest commit

 

History

53 Commits

Folders and files

Repository files navigation

CheddaBoards

The open-source backend behind cheddaboards.com. Use the hosted service, or run your own.

Leaderboards, achievements, player accounts, anti-cheat and moderation for games, running as a single canister on the Internet Computer.

Website Docs Status License


What's in this repo

This is the backend canister: the on-chain logic and storage behind every CheddaBoards game.

Path What it is
src/cheddaboards_v2_backend/main.mo The canister actor: games, scoreboards, sessions, achievements, anti-cheat, moderation, admin
src/cheddaboards_v2_backend/ (types, apikeys, players, scoreboards) Supporting Motoko modules imported by main.mo
dfx.json Build config, as used for the production canister
CHANGELOG.md Per-release notes, including upgrade notes for self-hosters

The Candid interface is generated from the source when you build (dfx generate).

This public repo is a mirror of the private source, published one commit per release, so it can trail the live canister by a release.

Not in this repo:

Looking for Go to
Documentation, quick starts, REST reference docs.cheddaboards.com
The REST proxy behind api.cheddaboards.com cheddaboards-proxy
Godot 4 add-on (4.3+) cheddaboards-godot-addon, also on the Godot Asset Store
Godot 4 full game template cheddaboards-godot, also on the Godot Asset Store
Godot 3.6 add-on cheddaboards-godot3-addon
Unity SDK cheddaboards-unity
A worked example Dodge the Creeps × CheddaBoards (play it)
Service status status.cheddatech.com

Every SDK talks to the same REST API, so anything that can make an HTTP request works too: Unreal, GameMaker, Bevy, native mobile, web, or your own engine.


Architecture

Game → REST proxy (verifier) → ICP canister. The proxy verifies OAuth tokens and API keys and signs canister calls; only the verifier principal can mint sessions or write scores for API-key players. The canister is this repo; the proxy is cheddaboards-proxy.

Games talk plain HTTPS to a thin REST proxy. The proxy checks the game's API key or the player's sign-in, then calls the canister using its own signing identity, the verifier. The canister stores everything and enforces the rules.

The trust model, in short:

  • Writes go through the verifier. Sign-in, session minting, and every score, achievement and nickname change for API-key players are accepted only from the verifier principal. Calling the canister directly for these returns Unauthorized.
  • Signed-in developers and players act through sessions. A session token is checked by the canister on every call, lasts 30 days, and renews on use.
  • Board reads are public and can skip the proxy. The canister serves scoreboards over HTTP itself (see Direct HTTP reads).
  • Lookups that could leak information are closed. API key validation, lookups by email address and lookups by API-key player ID are verifier-only.
  • Sign-in only trusts the platform's own OAuth credentials. The proxy accepts Google and Apple tokens issued for its own client IDs and bundle ID, never ones a developer registers.

The proxy that runs api.cheddaboards.com is open source too, at cheddaboards-proxy. Self-hosters can run it or build their own against the Candid interface (see Self-hosting).


Features

  • Leaderboards: server-validated scores, with all-time, weekly and daily boards created for every game
  • Timed scoreboards: daily, weekly, monthly and custom-interval boards that reset on calendar boundaries and archive each period
  • Category scoreboards: per-level, per-mode or per-category boards you submit to by ID, without registering a separate game for each
  • Board limits: per-game caps by developer tier, checked when a board is created: free 50 boards with at most 5 fan-out, pro 250 with at most 15. Targeted boards count only toward the total
  • Player accounts: anonymous players identified by device under the game's API key, plus Google, Apple and Internet Identity sign-in. Device-code login for linking a game client to an account is implemented in the hosted proxy, not in the canister
  • Account linking: an anonymous profile merges into a signed-in account without losing progress. Scores and streaks merge by maximum, achievements are combined, play counts are summed
  • Cross-game profiles: a signed-in player keeps one identity across every CheddaBoards game
  • Achievements: per-player unlocks, with a batch call for unlocking many at once
  • Anti-cheat: per-player rate limiting, per-round and absolute score caps, optional play-session time validation, and a suspicion log the game owner can read, with players shown only as opaque keys
  • Moderation: game owners can delete a single score entry or wipe a player from their boards, with a capped audit log that stores player identifiers as hashes
  • Game metadata: play URL, website, engine tag and an optional developer contact email
  • API keys: one key per game, generated from IC randomness, revoked automatically when a game is removed
  • Safe deletion: deleted games can be recovered for 30 days, or the owner can remove one for good straight away. Permanent removal clears the game's boards, scores, archived periods and API key
  • Operations: a memStats query and a GET /metrics endpoint for capacity monitoring, and an admin command set for support and recovery

Using the hosted service

You don't need to run anything. Register a game at cheddaboards.com/developers, copy its API key, and submit a score:

curl -X POST https://api.cheddaboards.com/scores \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "X-Game-ID: your-game-id" \
  -d '{"playerId":"player_123","gameId":"your-game-id","score":1200,"streak":3}'

The hosted service is free: 3 game slots to start (more on request from the dashboard), up to 50 boards per game, unlimited players, no per-player fees.

For Godot and Unity, use an SDK from the table above. Setup guides, the REST reference and error codes are at docs.cheddaboards.com. AI coding assistants can start from docs.cheddaboards.com/llms.txt.


Self-hosting

The canister is fully open source and you can run your own instance. It is more assembly than a one-click template: you deploy the canister, then run an API layer in front of it, either cheddaboards-proxy or your own. The self-hosting guide covers the proxy side in more detail.

Prerequisites

  • dfx (the IC SDK). Production is built with dfx 0.32.0
  • Cycles to run a canister on mainnet
  • Basic familiarity with Motoko and the Internet Computer

1. Clone

git clone https://github.com/cheddatech/cheddaboards.git
cd cheddaboards

2. Configure principals

Open src/cheddaboards_v2_backend/main.mo and replace the placeholder principals (aaaaa-aa) with your own:

// Your proxy's signing identity: the only principal allowed to make
// privileged calls. REPLACE with your own.
private transient let VERIFIER_PRINCIPAL : Text = "aaaaa-aa";

// Super admin (your dfx identity). REPLACE with your own.
private var CONTROLLER : Principal = Principal.fromText("aaaaa-aa");

// Bootstrap admin in postupgrade() (can be the same as controller). REPLACE.
let firstAdmin = Principal.fromText("aaaaa-aa");

Get your principal with dfx identity get-principal. A quick check before any deploy: grep -n "aaaaa-aa" src/cheddaboards_v2_backend/main.mo should print nothing.

Board limits per game are set by the FREE_MAX_BOARDS, FREE_MAX_FANOUT_BOARDS, PRO_MAX_BOARDS and PRO_MAX_FANOUT_BOARDS constants in main.mo. They are transient, so you can change them on any upgrade.

⚠️ Set these before your first deploy. The actor is persistent, so top-level variables are stable and survive upgrades. Editing a literal afterwards does not change the running value. VERIFIER_PRINCIPAL is re-applied in postupgrade(), so it can be rotated by upgrading. CONTROLLER is not, so get it right the first time.

3. Deploy

# Local testing
dfx start --background
dfx deploy

# Mainnet, first install
dfx deploy --network ic

The canister uses enhanced orthogonal persistence (--enhanced-orthogonal-persistence in dfx.json).

4. Upgrade safely

dfx canister --network ic stop cheddaboards_v2_backend
dfx canister --network ic snapshot create cheddaboards_v2_backend
dfx deploy cheddaboards_v2_backend --network ic --mode upgrade --wasm-memory-persistence keep
dfx canister --network ic start cheddaboards_v2_backend
  • Read the changelog first. Each release in CHANGELOG.md has notes for self-hosters. Some upgrades need two steps, and removing a stable field needs a one-shot migration.
  • Snapshot before every upgrade. It is your only rollback. Each snapshot is a full copy of the heap and counts towards memory and cycle burn, so keep one at a time (snapshot create --replace <id>) and delete it once the upgrade is proven.
  • Check the module hash afterwards. dfx canister --network ic info <name> should match the shasum -a 256 of the wasm you built.

5. Run a proxy

The simplest route is cheddaboards-proxy, the same code that runs api.cheddaboards.com. Its README covers the environment variables and deployment. To build your own instead, generate the Candid bindings it will call:

dfx generate cheddaboards_v2_backend

Either way, your API layer translates HTTP requests into canister calls. It needs:

  • A signing identity (for example an Ed25519 keypair) whose principal you set as VERIFIER_PRINCIPAL. Keep the private key secret: it can mint sessions for any user and write scores for any game
  • To sign every canister call as that identity, queries included. External score submits, achievement unlocks, play-session starts and API key lookups are all rejected or return nothing for any other caller
  • To validate each game's API key itself, through validateApiKeyQuery, on every request. The canister trusts the proxy to have done this for API-key players, so never accept a key without asking the canister
  • To verify OAuth tokens itself (for example via JWKS) before calling socialLoginAndGetProfile, and to accept only tokens issued for your own client IDs and bundle ID. Sessions are tied to the email, not the game, so accepting a developer-supplied audience would let that developer sign in as anyone who uses their app
  • Rate limiting, since the canister only applies a per-player submit throttle
  • Your canister ID and an IC host (https://icp-api.io, or http://127.0.0.1:4943 for local dfx)
  • OAuth client IDs for the providers you support
  • A CORS policy. Games run on arbitrary domains, so the hosted proxy reflects the request origin; that is safe only because auth rides on headers, never cookies

After upgrading to v0.13.0 or later, run adminGate("sweepOrphanApiKeys", []) once to report API keys left behind by deleted games, then again with ["confirm"] to revoke them.


API reference

The Candid interface is the full contract. Every deployed canister gets the Candid UI for free. Here it is against the live production canister:

Candid UI showing the live CheddaBoards canister's method list

Key methods by area. Most owner-facing methods come in two forms: a principal variant (the caller is the owner) and a BySession variant (a session token identifies the owner).

Sign-in and sessions: socialLoginAndGetProfile, createSessionForVerifiedUser, iiLoginAndGetProfile, validateSession, destroySession

Account linking: migrateAnonymousAccount, migrateAnonymousToII (both verifier-only)

Games: registerGame, updateGame, deleteGame, recoverDeletedGame, permanentlyDeleteGame, getGame, listGames

Game metadata: setGameEngine, getGameEngines, setGameWebsite, getGameWebsites, setDeveloperContact, getMyDeveloperContact

API keys: generateApiKey, getApiKey, revokeApiKey, validateApiKeyQuery (verifier-only)

Scores and leaderboards: submitScore, submitScoreToBoard, getScoreboard, getScoreboardsForGame, getLeaderboard, getPlayerRank, getPlayerScoreboardRank

Scoreboard management: createScoreboard, updateScoreboard, resetScoreboard, deleteScoreboard, getBoardLimitsBySession

Archives: getScoreboardArchives, getLastArchivedScoreboard, getArchivedScoreboard

Play sessions: startGameSessionByApiKey, startGameSessionBySession, getPlaySessionStatus, cancelPlaySession

Achievements: unlockAchievement, unlockAchievementBatch, getAchievements

Profiles: getMyProfileBySession, getUserProfile, getGameProfile, changeNicknameAndGetProfile, isNicknameAvailable

Moderation: getScoreboardAdmin, removeScoreEntry, removePlayerScores, getEntryDeletionLog, getSuspicionLogBySession

Stats and operations: getSubmissionStats, getSystemInfo, memStats, adminGate

Fan-out vs targeted: submitScore writes a score to every fan-out board on the game: the default all-time, weekly and daily boards plus any other non-targeted board. submitScoreToBoard writes to one board only, which is how per-level and per-category boards work. A board opts in to targeted behaviour in its config, which is fixed at creation. Because every plain submit writes to all fan-out boards, they have their own, lower limit.

User ID types: player-facing methods take a type and an ID. Use session with a session token, external with an API-key player ID, or principal. The email type is verifier-only, and so is the external type on getUserProfile.

Direct HTTP reads

The canister answers plain HTTPS on its raw domain, with open CORS, so game clients can read boards without going through a proxy:

# A scoreboard, same JSON as the REST API
curl "https://<canister-id>.raw.icp0.io/games/<gameId>/scoreboards/<boardId>?limit=10"

# Capacity figures: cycles, memory and map sizes, with a warn status
curl "https://<canister-id>.raw.icp0.io/metrics"

The production canister ID is fdvph-sqaaa-aaaap-qqc4a-cai.


Security

Found a vulnerability? Please email info@cheddaboards.com instead of opening a public issue; SECURITY.md has the details. Security fixes are listed under a Security heading in the changelog, and self-hosters should upgrade when one appears.


Contributing

Issues and pull requests are welcome. Because this repo is a published mirror, accepted changes are applied to the source and appear here with the next release, with credit.

  1. Fork the repo
  2. Create a feature branch
  3. Make your changes
  4. Open a PR describing what it changes and how you tested it

Links


License

MIT. See LICENSE.


Built by CheddaTech Ltd on the Internet Computer.

Releases

Packages

Contributors

Languages