Skip to content

Settle battles in the backend with publicly verifiable signed receipts - #69

Merged
aichannode merged 77 commits into
mainfrom
feat/backend-authoritative-battles
Aug 4, 2026
Merged

Settle battles in the backend with publicly verifiable signed receipts#69
aichannode merged 77 commits into
mainfrom
feat/backend-authoritative-battles

Conversation

@heyradcode

Copy link
Copy Markdown
Collaborator

Description:

Implements the backend-authoritative battle architecture from
docs/plan-backend-battle-architecture.md, §L Phases 1 through 6. Battles no
longer settle on chain per fight. They are authorized by a wallet-signed intent,
seeded from a drand round committed to before it publishes, computed by a
versioned deterministic engine, signed as a receipt, and anchored in Merkle
batches that back capped aggregate season rewards.

74 commits, 498 files. Reading it commit by commit is the intended path: the
order follows §L's build order, and each commit is self-contained.

Why

Per-battle settlement paid gas for every fight and put the simulation on chain,
which capped both throughput and how much combat logic could exist. The goal was
to remove per-battle gas without making results unfalsifiable. The design answer
is that anyone can recompute a battle from published artifacts and check the
operator's signature over it, so trust in the backend is bounded by what a third
party can independently replay.

The flow

  1. POST /api/battle/intents takes an EIP-712 (or Solana message) signature over
    the matchup. The JWT says who is calling, the signature is what authorizes the
    fight. A session token alone cannot start a battle on a pet's behalf.
  2. POST /api/battle/intents/:hash/accept freezes a snapshot of both pets,
    picks a drand round that has not published, signs the commitment, and returns
    it in that same response. Commit before reveal is enforced by that ordering.
  3. A worker verifies the drand BLS beacon, derives the seed, runs the combat
    engine, and has indexer-go independently recompute the result before anything
    is signed. The two ports were written to disagree if either drifts.
  4. The signed receipt is appended to a global and a per-pet hash chain, then
    batched into a Merkle root anchored by BattleBatchRegistry.
  5. Clients verify the receipt locally before animating it, so the fight shown is
    the fight the receipt commits to, or nothing is shown.

New packages

  • protocol (@cryptopets/protocol, MIT): canonical encodings, hashing, seed
    derivation, drand beacon verification, ruleset versioning, and the TS combat
    engine moved out of shared. Dependency-free, no clock reads, no ambient
    randomness, no I/O. It is MIT on purpose, because the verifier depends on it.
  • verifier (@cryptopets/verifier, MIT): standalone receipt verifier with no
    backend access and no database. Checks seed derivation, operator signature,
    beacon, combat replay, progression, and hash-chain continuity, reporting each
    independently. CI replays a committed corpus and asserts a tampered corpus is
    rejected.

Deployment notes

  • Solana account layout changed. CURRENT_ACCOUNT_VERSION is now 7, which needs
    a redeploy plus GlobalState reinit and re-minted pets.
    indexer-go/internal/solana/idl/cryptopets.json is hand-edited to match because
    it drives positional Borsh decoding, and a stale IDL corrupts silently rather
    than failing. Re-diff it against what anchor build generates.
  • GameConfig is not behind a proxy, so its changes required a fresh deployment
    plus setGameConfig on both GameLogic and PetCore.
    scripts/upgrade-game-config.ts replays existing tunables onto the new
    instance so live-tuned values are not reset to source defaults.
  • Backend battle mode is behind BATTLE_BACKEND_MODE_ENABLED. Both settle keepers
    are off by default.

Review risk

Rust and Anchor changes were written without a local toolchain, so anchor build
and anchor test have not been run against them. That is the part of this branch
with the least mechanical verification behind it.

@vercel

vercel Bot commented Aug 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
do-not-stop-frontend Ready Ready Preview Aug 4, 2026 9:28pm
do-not-stop-website Ready Ready Preview Aug 4, 2026 9:28pm

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

🧪 Coverage

Package Statements Branches Functions Lines Overall
backend 86.56% (1842/2128) 79.04% (924/1169) 86.79% (368/424) 87.14% (1742/1999) 85.24% (4876/5720)
frontend 67.39% (1257/1865) 68.67% (1039/1513) 62.91% (302/480) 67.81% (1142/1684) 67.48% (3740/5542)
shared 58.58% (1354/2311) 60.37% (867/1436) 60.04% (278/463) 60.84% (1254/2061) 59.85% (3753/6271)

@aichannode aichannode left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's amazing! Thanks @heyradcode 🙏

@aichannode
aichannode merged commit 7d14957 into main Aug 4, 2026
9 checks passed
@aichannode
aichannode deleted the feat/backend-authoritative-battles branch August 4, 2026 21:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants