Settle battles in the backend with publicly verifiable signed receipts - #69
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🧪 Coverage
|
aichannode
approved these changes
Aug 4, 2026
aichannode
left a comment
Collaborator
There was a problem hiding this comment.
That's amazing! Thanks @heyradcode 🙏
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description:
Implements the backend-authoritative battle architecture from
docs/plan-backend-battle-architecture.md, §L Phases 1 through 6. Battles nolonger 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
POST /api/battle/intentstakes an EIP-712 (or Solana message) signature overthe 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.
POST /api/battle/intents/:hash/acceptfreezes 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.
engine, and has indexer-go independently recompute the result before anything
is signed. The two ports were written to disagree if either drifts.
batched into a Merkle root anchored by
BattleBatchRegistry.the fight the receipt commits to, or nothing is shown.
New packages
protocol(@cryptopets/protocol, MIT): canonical encodings, hashing, seedderivation, drand beacon verification, ruleset versioning, and the TS combat
engine moved out of
shared. Dependency-free, no clock reads, no ambientrandomness, no I/O. It is MIT on purpose, because the verifier depends on it.
verifier(@cryptopets/verifier, MIT): standalone receipt verifier with nobackend 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
CURRENT_ACCOUNT_VERSIONis now 7, which needsa redeploy plus
GlobalStatereinit and re-minted pets.indexer-go/internal/solana/idl/cryptopets.jsonis hand-edited to match becauseit drives positional Borsh decoding, and a stale IDL corrupts silently rather
than failing. Re-diff it against what
anchor buildgenerates.GameConfigis not behind a proxy, so its changes required a fresh deploymentplus
setGameConfigon bothGameLogicandPetCore.scripts/upgrade-game-config.tsreplays existing tunables onto the newinstance so live-tuned values are not reset to source defaults.
BATTLE_BACKEND_MODE_ENABLED. Both settle keepersare off by default.
Review risk
Rust and Anchor changes were written without a local toolchain, so
anchor buildand
anchor testhave not been run against them. That is the part of this branchwith the least mechanical verification behind it.