MyChess.onchain is a decentralized chess platform with on-chain game state, hybrid ETH + CHESS bonding, commit-reveal dispute resolution, ratings, rewards, and token-governed protocol controls. The repository and some historical documents still use the original name, Solidity Chess.
This repo contains:
ethereum/: canonical Solidity sources, Truffle migrations, deployment scripts, and contract testsfrontend/: SvelteKit client, built as a static app for IPFS-style deploymentdocs/: historical protocol, UX, and mitigation notes; some documents predate the current Base-focused architecturelegacy/: unsupported historical deployment helpers, flattened contracts, and standalone scripts
At a high level, the system does four things:
- stores games and validates piece movement and endgame conditions on-chain
- locks player collateral through a hybrid bond model
- allows post-game cheating disputes through a commit-reveal DAO flow
- tracks token rewards, Elo-style ratings, and governance on-chain
- The contract suite defines
360test cases across16Truffle test files.npm run test:ciexecutes them in four isolated Ganache batches. ChessCoreuses EIP-1167 clones. Heavy rule evaluation lives in oneChessRulesEnginedeployed by the implementation and shared by the clones through the implementation's immutable reference.- The frontend is configured for static/IPFS deployment and now lazy-loads ABI-only artifacts.
- Solidity compilation is pinned to
solc 0.8.24, generates validated Truffle-compatible artifacts, and enforces the EIP-170 bytecode limit. - GitHub Actions recompiles, checks bytecode size, runs all contract tests, audits high-severity frontend dependencies, builds the static app, and checks generated ABI drift.
- The supported deployment path is
ethereum/migrations/2_deploy_chess_system.js; public Base deployments can transfer protocol control toChessTimelockand remove deployer privileges. - The system is still not formally audited.
The repository is suitable for source publication, local use, and a clearly labelled Base Sepolia beta. It is not approved for a permissionless production launch with material user funds.
Important limitations:
- arbitrator selection is still pseudo-random on-chain, not VRF-backed
- ratings use a bounded linear approximation of the Elo expected-score curve;
PlayerRating.getTopPlayers()is a pagination helper, not a sorted on-chain leaderboard - faucet eligibility and the CHESS/ETH price feed still depend on trusted, separately rotatable off-chain signers
- Truffle 5 and
@truffle/hdwallet-providerretain known npm advisories in their deprecated dependency trees; replace or isolate this deployment toolchain before handling a production mnemonic - game state, moves, stakes, disputes, and ratings are public blockchain data; the protocol does not provide player privacy
- timeout presets use timestamp deadlines:
Finneyis 1 hour,Buterinis 7 hours, andNakamotois 7 days legacy/is retained for historical reference only and must not be used to build or deploy the protocol
Frontend (SvelteKit SPA, ABI-only contract access)
|
v
ChessFactory ----------------------------------> ChessNFT
| (one token per game)
|
+-- EIP-1167 clone --> ChessCore
|
+--> shared ChessRulesEngine
+--> BondingManager
+--> DisputeDAO --> ArbitratorRegistry
+--> PlayerRating
+--> RewardPool
ChessToken --> ChessGovernor --> ChessTimelock --> protocol administration
ChessFactory registers every new clone with the bonding, dispute, rating, and reward components. With the canonical dispute wiring enabled, a game finalizes only after its challenge window has expired or its dispute has been resolved; prize allocation uses a pull-payment flow.
- on-chain piece movement, king-safety, check, mate, and stalemate evaluation through
ChessCore+ChessRulesEngine - special moves: castling, en passant, promotion
- check, checkmate, stalemate, threefold repetition, 50-move rule, 75-move automatic draw
- three chain-independent timeout presets:
Finney(1 hour),Buterin(7 hours),Nakamoto(7 days) Friendlymode rejects moves that leave the mover's king in check and allows custom pre-game positionsTournamentmode treats a self-checking move as an immediate loss instead of reverting the transaction- unjoined game cancellation after timeout
- dispute-aware settlement for prizes, rewards, and ratings
- hybrid bonding in ETH + CHESS
- commit-reveal arbitrator voting
- dynamic effective quorum based on the selected panel
- up to 3 escalation levels
- slashing and challenger compensation on
Cheatverdicts - arbitrator reputation tracking
- per-dispute deposit escrow and assignment-based stake locks for selected arbitrators
- capped
CHESSERC20Votes token with permit, delegation, controlled minting, and two-year team vesting - governor + timelock governance flow
- automatic deployer-role removal on the canonical
baseproduction deployment - configurable dispute and bonding parameters
- on-chain Elo-style rating updates using fixed-point arithmetic and a linear expected-score approximation
- player stats and provisional status
- separately funded reward and faucet pools; deployment alone does not fund either pool
- frontend leaderboard view built from on-chain data, with client-side ordering
| Contract | Responsibility |
|---|---|
ChessBoard |
Board storage, initial position, draw-state bookkeeping, SVG support |
ChessCore |
Match lifecycle, moves, settlement, draw flows |
ChessRulesEngine |
Shared move legality and check/checkmate/stalemate evaluation |
ChessMediaLibrary |
On-chain SVG board rendering used by games and NFT metadata |
ChessFactory |
Game creation through clone deployment |
ChessNFT |
ERC721 minted to the creator for every game; tokenURI() delegates to the live game-board SVG |
ChessToken |
ERC20 governance / ecosystem token |
BondingManager |
ETH + CHESS bond accounting and locking |
RewardPool |
Distribution from pre-funded reward/faucet balances and signer-authorized faucet claims |
ArbitratorRegistry |
Arbitrator staking, tiering, reputation, selection |
DisputeDAO |
Challenge window, commit-reveal voting, escalation, final decisions |
PlayerRating |
Elo-style ratings and player stats |
ChessGovernor |
Governance proposals and voting |
ChessTimelock |
Delayed governance execution |
Canonical Truffle profiles:
development: local RPC, default127.0.0.1:7545, configurable throughLOCAL_RPC_HOSTandLOCAL_RPC_PORTbase_sepolia: Base Sepolia (84532)base: Base mainnet (8453)
The frontend recognizes:
1337/5777: local Ganache84532: Base Sepolia8453: Base mainnet
No Goerli, Ethereum Mainnet, Arbitrum, or Optimism Truffle profile is shipped. Historical documents that mention Goerli, Sepolia, Holesky, Linea, or other networks are not authoritative for the current Base-focused deployment.
- Node.js 20.19+ or 22.12+ (below Node 23)
- npm
- Ganache or another local EVM RPC for local development
- MetaMask or another injected EVM wallet for frontend testing
git clone https://github.com/jacmos3/ChessGameSolidity.git
cd ChessGameSolidity
cd ethereum
npm ci
cd ../frontend
npm ci
cd ..From the repo root:
npm run dev:localThis does five things in order:
- starts or reuses a local Ganache RPC on
127.0.0.1:8545 - compiles deterministic Truffle-compatible artifacts with the pinned compiler
- runs
truffle migrate --reset - writes
frontend/.env.localfromethereum/deployments/latest-development.json - starts the frontend dev server
You can override the ports with:
DEV_LOCAL_RPC_PORTDEV_LOCAL_WEB_PORT
Once the local stack is up, you can run a contract-level smoke test against the same RPC:
LOCAL_RPC_PORT=8545 npm run smoke:localThe smoke flow covers a real end-to-end path on a fresh deployment:
- mint and stake arbitrators
- deposit player bonds
- create and join a game
- play opening moves and resign
- open a dispute and run commit/reveal voting
- resolve the dispute and verify prize settlement
If you want to run each step manually, start Ganache and pass the same port to every Truffle command:
cd ethereum
npx ganache --server.host 127.0.0.1 --server.port 8545 --wallet.totalAccounts 20cd ethereum
npm run compile
LOCAL_RPC_PORT=8545 npx truffle migrate --resetThe migration writes the latest addresses to ethereum/deployments/latest-development.json.
Truffle includes base_sepolia and base network profiles. Configure these variables before a public migration:
MNEMONIC=
BASE_SEPOLIA_RPC_URL=
BASE_RPC_URL=
TEAM_WALLET=
TREASURY_WALLET=
FAUCET_SIGNER=
ORACLE_UPDATER=cd ethereum
npm run compile
npx truffle migrate --network base_sepolia --resetFAUCET_SIGNER authorizes eligible faucet beneficiaries. ORACLE_UPDATER receives only ORACLE_ROLE and must submit a fresh CHESS/ETH price at least once every seven days; stale prices block bond calculation and new bonded games.
The base profile automatically transfers ownership and admin roles to ChessTimelock, then removes deployer privileges. Base Sepolia keeps the deployer as admin unless GOVERNANCE_HANDOFF=true is set. When handoff is enabled, FAUCET_SIGNER and ORACLE_UPDATER must both differ from the deployer.
Verify a handoff against the selected network:
cd ethereum
npm run verify:handoff -- --network basePost-deployment operations are required:
- approve and deposit CHESS into
RewardPool.depositRewardPool()before play-to-earn payouts can occur - approve and deposit CHESS into
RewardPool.depositFaucetPool()before faucet claims can occur - keep the oracle price fresh through the dedicated updater
- optionally pre-fund
DisputeDAOif governance wants challenger bonuses beyond return of the reserved challenge deposit
Deployment address files under ethereum/deployments/ are local operational output and are intentionally not committed.
Deployment warning: everything under
legacy/predates the latest role wiring, signer model, governance handoff, timeout semantics, and contract interfaces. It is unsupported. The pinned compiler and Truffle migration above are the only canonical path.
npm run dev:local creates frontend/.env.local automatically. For a manual setup, copy frontend/.env.example to frontend/.env.local and fill the local addresses from the latest deployment file.
These are the variables the frontend actually reads today:
VITE_CONTRACT_ADDRESS_LOCAL=
VITE_BONDING_MANAGER_LOCAL=
VITE_CHESS_TOKEN_LOCAL=
VITE_DISPUTE_DAO_LOCAL=
VITE_ARBITRATOR_REGISTRY_LOCAL=
VITE_CHESS_GOVERNOR_LOCAL=
VITE_CHESS_TIMELOCK_LOCAL=
VITE_PLAYER_RATING_LOCAL=For Base Sepolia / Base, use the corresponding ..._BASE_SEPOLIA and ..._BASE variables.
The current frontend does not directly load RewardPool; rewards are triggered by finalized games and faucet administration remains an external operational flow. Consequently there is no VITE_REWARD_POOL_* variable today.
cd frontend
npm run devnpm run dev automatically runs npm run sync:abis, so the frontend ABI-only artifacts stay aligned with the latest Solidity build output.
sync:abis reads from ethereum/build/contracts. On a clean checkout, compile or migrate the contracts before starting the frontend:
cd ethereum
npm run compileOpen the URL shown by Vite, typically http://127.0.0.1:3000/.
The suite currently contains 360 test cases in 16 files. The canonical command compiles, checks every deployed bytecode against EIP-170, and runs four test batches against fresh Ganache instances:
cd ethereum
npm run test:ciFor an ad hoc monolithic run against an already running Ganache instance:
LOCAL_RPC_PORT=8545 npm test -- --compile-noneWith gas reporting, set REPORT_GAS=true on that monolithic command. The isolated test:ci runner is preferred because the full suite creates enough chain state to destabilize a single long-lived provider.
cd frontend
npm run buildThe prebuild hook refreshes ABI-only artifacts from the latest pinned Solidity compilation.
.
├── README.md
├── LICENSE.md
├── package.json
├── .github/workflows/ci.yml # reproducible contract and frontend checks
├── scripts/ # local orchestration and env generation
├── legacy/ # unsupported historical snapshots
├── docs/ # historical/reference documents
│ ├── ANTI_CHEATING_TOKENOMICS.md
│ ├── USER_GUIDE.md
│ ├── UX_UI_AUDIT_REPORT.md
│ └── VULNERABILITIES_MITIGATIONS.md
├── ethereum/
│ ├── contracts/
│ │ ├── Chess/
│ │ │ ├── ChessBoard.sol
│ │ │ ├── ChessCore.sol
│ │ │ ├── ChessFactory.sol
│ │ │ ├── ChessMediaLibrary.sol
│ │ │ ├── ChessNFT.sol
│ │ │ └── ChessRulesEngine.sol
│ │ ├── DAO/
│ │ ├── Governance/
│ │ ├── Rating/
│ │ └── Token/
│ ├── deployments/ # ignored local deployment outputs
│ ├── migrations/ # canonical deployment flow
│ ├── scripts/ # compiler, size check, test runner, smoke and handoff verification
│ └── test/ # 16 Truffle test files
└── frontend/
├── scripts/
│ └── extract-abis.mjs
├── src/
│ ├── lib/
│ │ ├── components/
│ │ ├── contracts/
│ │ │ ├── abi/
│ │ │ └── loadAbi.js
│ │ └── stores/
│ └── routes/
└── static/
When documentation conflicts, treat the Solidity sources and Truffle migration as authoritative, followed by this README. In particular, docs/USER_GUIDE.md and everything under legacy/ may contain older branding, networks, interfaces, or role wiring.
function createChessGame(
uint8 _timeoutPreset,
uint8 _gameMode
) external payable returns (address);
function getDeployedChessGames() external view returns (address[] memory);function joinGameAsBlack() external payable;
function makeMove(uint8 startX, uint8 startY, uint8 endX, uint8 endY) external;
function makeMoveWithPromotion(
uint8 startX,
uint8 startY,
uint8 endX,
uint8 endY,
int8 promotionPiece
) external;
function resign() external;
function canClaimPrize() external view returns (bool);
function claimPrize() external;
function finalizePrizes() external;
function withdrawPrize() external;
function cancelUnjoinedGame() external;
function offerDraw() external;
function acceptDraw() external;
function claimDrawByRepetition() external;
function claimDrawByFiftyMoveRule() external;Settlement guidance:
finalizePrizes()followed bywithdrawPrize()is the canonical pull-payment path and is required for drawsclaimPrize()is a backward-compatible combined path for a single rightful recipient- neither path can settle while a challenge window or dispute is still active
function challenge(uint256 gameId, address accusedPlayer) external;
function commitVote(uint256 disputeId, bytes32 commitHash) external;
function revealVote(uint256 disputeId, Vote vote, bytes32 salt) external;
function resolveDispute(uint256 disputeId) external;
function getChallengeWindowRemaining(uint256 gameId) external view returns (uint256);
function getEffectiveQuorum(uint256 disputeId) external view returns (uint256);
function getSelectedArbitrators(uint256 disputeId) external view returns (address[] memory);The default challenge window is 48 hours, followed by 24-hour commit and reveal periods. A vote commitment is keccak256(abi.encodePacked(vote, salt, arbitratorAddress)). Insufficient quorum or supermajority triggers automatic escalation when resolveDispute() is called, up to the configured limit.
Faucet claims require an off-chain authorization bound to the pool address, chain ID, and beneficiary:
const digest = ethers.utils.solidityKeccak256(
["address", "uint256", "address"],
[rewardPool.address, chainId, beneficiary]
);
const authorization = await faucetSigner.signMessage(ethers.utils.arrayify(digest));
await rewardPool.connect(beneficiary).claimFaucet(authorization);An authorization cannot be replayed for another beneficiary, chain, or RewardPool. Eligibility remains a trusted off-chain policy enforced by FAUCET_SIGNER.
- SvelteKit
2.70.1 - Svelte
5.56.7in legacy component mode - Vite
8.1.5 - Tailwind CSS
3.4.x - ethers.js
5.8.0 - chess.js
1.0.0-beta.8 @sveltejs/adapter-staticfor IPFS-compatible static builds
Implemented protections include:
- reentrancy protection on fund-moving flows
- role-based access control
- challenge windows and commit / reveal deadlines
- dispute max duration cap
- bond locking and slashing
- stale-price rejection and a dedicated, revocable oracle role
- signer-bound faucet authorizations without
tx.originor EOA-only assumptions - production governance handoff with deployer privilege removal
- custom errors for lower revert overhead
Known limitations:
- no formal external audit yet
- the Truffle deployment stack contains deprecated transitive dependencies with unresolved npm advisories; do not treat a clean contract test run as a supply-chain audit
- arbitrator selection is not VRF-backed
- the price updater and faucet signer remain trusted operational roles
- all gameplay and dispute data is public on-chain
- public frontend addresses are build-time environment configuration, not discovered from an on-chain registry
- reward and faucet balances require explicit operational funding
- historical material under
legacy/is intentionally unsupported and not synchronized
- Create a short-lived branch from
main - Run the contract suite before pushing
- Run the frontend production build
- Regenerate ABI-only frontend artifacts after contract changes
- Update this README when protocol behavior or deployment requirements change
The repository ships the MIT license in LICENSE.md, Solidity sources use SPDX-License-Identifier: MIT, and the contract package declares the same license.