Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1,143 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Checkmate-Escrow — Competitive Chess Betting on Stellar

A trustless chess wagering platform built on Stellar Soroban smart contracts. Players stake tokens before a match, and the winner is automatically paid out the moment the game ends — no middleman, no delays, no trust required.

🎯 What is Checkmate-Escrow?

Checkmate-Escrow combines competitive chess with Stellar's fast settlement to create a fully on-chain betting platform for casual and high-stakes matches.

Players:

  • Stake tokens into a Soroban escrow contract before a match begins
  • Play their game on Lichess or Chess.com as normal
  • Receive automatic payouts the instant the match result is verified on-chain

A custom Oracle bridges the Chess.com / Lichess API to the smart contract, verifying match results and triggering payouts without any manual intervention.

This makes Checkmate-Escrow:

✅ Trustless (no platform can withhold or delay winnings)
✅ Transparent (all stakes and payouts are verifiable on-chain)
✅ Instant (Stellar's fast finality means payouts settle in seconds)
✅ Accessible (anyone with a Stellar wallet can participate)

🚀 Features

  • Create a Match: Set stake amount, token address, and link a Lichess/Chess.com game ID
  • Flexible Token Support: Any Stellar token address is accepted by default; once the admin adds at least one token via add_allowed_token, only allowlisted tokens are accepted for new matches
  • Escrow Stakes: Both players deposit funds into the contract before the game starts
  • Oracle Integration: Real-time result verification via Lichess/Chess.com APIs
  • Automatic Payouts: Winner receives the full pot the moment the result is confirmed
  • Draw Handling: Stakes are returned to both players in the event of a draw
  • Admin Controls: Pause/unpause, oracle rotation, admin transfer, and match timeout configuration
  • Transparent: All escrow balances and payout history are verifiable on-chain

🗺️ Match Lifecycle

Matches move through the following states:

Pending ──► Active ──► Completed
   │                       ▲
   └──► Cancelled ◄─────────
         (expire_match / cancel_match)
State Description
Pending Match created; awaiting deposits from both players
Active Both players have deposited; game is in progress
Completed Oracle submitted result; payout executed
Cancelled Cancelled before activation, or expired after timeout

Events Reference

Topic (namespace / name) Emitted by Payload
escrow / init initialize (oracle_address, admin_address)
admin / paused pause ()
admin / unpaused unpause ()
admin / oracle_up update_oracle (old_oracle, new_oracle)
admin / xfer transfer_admin (old_admin, new_admin)
match / created create_match (match_id, player1, player2, stake_amount)
match / completed submit_result (match_id, winner, payout_amount)
match / cancelled cancel_match match_id
match / expired expire_match match_id

🛠️ Quick Start

New to Checkmate-Escrow? Start with the Local Development Setup guide for step-by-step instructions on building, testing, and running the full stack locally — including running the oracle service locally against a mock Lichess/Chess.com server.

Prerequisites

  • Rust (1.70+)
  • Soroban CLI
  • Stellar CLI

Build

./scripts/build.sh

Test

./scripts/test.sh

Setup Environment

Copy the example environment file:

cp .env.example .env

Configure your environment variables in .env:

# Network configuration
STELLAR_NETWORK=testnet
STELLAR_RPC_URL=https://soroban-testnet.stellar.org

# Contract addresses (after deployment)
CONTRACT_ESCROW=<your-contract-id>
CONTRACT_ORACLE=<your-contract-id>

# Oracle configuration
LICHESS_API_TOKEN=<your-lichess-api-token>
CHESSDOTCOM_API_KEY=<your-chessdotcom-api-key>

# Frontend configuration
VITE_STELLAR_NETWORK=testnet
VITE_STELLAR_RPC_URL=https://soroban-testnet.stellar.org

Network configurations are defined in environments.toml — see the environments.toml reference in the local development guide for a full field-by-field breakdown of each network:

  • testnet — Stellar testnet (free funds via Friendbot, recommended for development)
  • mainnet — Stellar mainnet (real funds, production only)
  • futurenet — Stellar futurenet (preview of upcoming protocol features)
  • standalone — Local development node (fully isolated, no external connectivity)

Deploy to Testnet

# Configure your testnet identity first
stellar keys generate deployer --network testnet

# Deploy
./scripts/deploy_testnet.sh

Run Demo

Follow the step-by-step guide in demo/demo-script.md

🎓 New here? Start with the Interactive Tutorial

Brand new to Checkmate-Escrow? The Interactive Tutorial takes you from zero to a completed, paid-out match on testnet in under 15 minutes — no real funds at risk:

  1. Create a match — register a wager on-chain
  2. Deposit funds — fund the escrow
  3. Check the result — verify the outcome and watch the payout

Prefer to learn by watching or testing yourself?

  • 🎬 Video walkthroughs: the tutorial includes a Video walkthroughs section (links added as recordings are published)
  • 🧩 Interactive quiz & checklist: tutorial-quiz.md
  • 🧪 Testnet practice mode: the whole tutorial runs on free testnet funds — no real money at risk

📖 Documentation

🎓 Smart Contract API

Match Management

create_match(player1, player2, stake_amount, token, game_id, platform) -> u64
get_match(match_id) -> Match
cancel_match(match_id, caller) -> Result<(), Error>
expire_match(match_id) -> Result<(), Error>
get_player_matches(player) -> Vec<u64>
get_pending_matches() -> Vec<Match>
get_active_matches() -> Vec<Match>
  • create_match must be authorized by player1.
  • cancel_match may be called by either matched player.
  • expire_match may be called by anyone once the match timeout elapses.

Escrow

deposit(match_id, player) -> Result<(), Error>
is_funded(match_id) -> Result<bool, Error>
get_escrow_balance(match_id) -> Result<i128, Error>

is_funded vs get_escrow_balance

These two functions answer different questions and are easy to confuse:

  • is_funded(match_id) — returns true only when both players have deposited their stake (i.e. the match has transitioned to Active). It reflects deposit flags, not token balances. Use this to gate game-start logic.

  • get_escrow_balance(match_id) — returns the total token amount currently held in escrow for the match: 0, 1×stake, or 2×stake depending on how many players have deposited. Once a match is Completed or Cancelled (funds already paid out or refunded), this always returns 0 regardless of on-chain token balances.

Examples:

Scenario is_funded get_escrow_balance
Only player1 deposited false 1 × stake_amount
Both players deposited (Active) true 2 × stake_amount
Match completed (payout done) true 0
Match cancelled (refunds done) false 0

Contract Metadata

get_contract_version() -> String
  • Returns the crate version from Cargo.toml (e.g. "0.1.0") as a semver-formatted string. Clients can use this to detect version mismatches against a deployed contract.

Oracle & Payouts

get_oracle_address() -> Address
submit_result(match_id, winner, caller) -> Result<(), Error>
submit_result_with_oracle_record(match_id, winner, game_id) -> Result<(), Error>
submit_result_batch(results: Vec<(match_id, winner)>, caller) -> Result<Vec<Option<Error>>, Error>
  • get_oracle_address() returns the oracle address currently configured on the contract. This is a view function that requires no authentication and is intended for off-chain clients, frontends, and monitoring tools to verify oracle configuration without reading raw contract storage. Returns Error::Unauthorized if the contract has not been initialized.

  • submit_result_batch settles multiple matches in a single call, reducing oracle transaction overhead. caller must be the configured oracle. Each match is processed independently — a failure on one (e.g. NotFunded, InvalidState) does not stop the rest from being processed. The returned Vec has one entry per input entry, in the same order: None on success, Some(Error) on failure for that match. (Soroban's contract ABI has no Result element type, so Option<Error> is the on-chain equivalent of Result<(), Error> here.)

  • submit_result is called by the configured oracle address and requires oracle authorization.

  • submit_result_with_oracle_record is the canonical oracle integration path and stores the verified game_id for audit.

submit_result verifies the caller, records the winner, and immediately executes the payout (or refund on draw) in a single transaction. There are no separate verify_result or execute_payout functions.

Admin Controls

set_match_timeout(seconds) -> Result<(), Error>
get_match_timeout() -> Result<u64, Error>
set_maximum_stake(amount: Option<i128>) -> Result<(), Error>
set_protocol_config(config: ProtocolConfig) -> Result<(), Error>
get_protocol_config() -> Result<ProtocolConfig, Error>

All admin-control functions below require authorization from the configured admin address.

  • Match timeoutset_match_timeout sets how long (in seconds) a Pending match may wait for both deposits before anyone can call expire_match on it. Value must fall within [MIN_MATCH_TIMEOUT_SECONDS, MAX_MATCH_TIMEOUT_SECONDS] (1–90 days), enforced with Error::InvalidTimeout. get_match_timeout returns the current value in seconds. The timeout is stored in ProtocolConfig::match_timeout_seconds and defaults to DEFAULT_MATCH_TIMEOUT_SECONDS (30 days).
  • Maximum stakeset_maximum_stake caps the stake_amount accepted by create_match and its variants. Pass Some(amount) to set a cap (rejected with Error::InvalidAmount if amount <= 0), or None to remove the cap (the default). create_match rejects any stake_amount above the configured cap with Error::InvalidAmount. Read the current cap via get_protocol_config().maximum_stake.
  • Protocol feeProtocolConfig::protocol_fee_bps (basis points, default 0) and ProtocolConfig::fee_recipient control a platform fee taken out of the winner's payout: protocol_fee = stake_amount * 2 * protocol_fee_bps / 10_000, transferred to fee_recipient when submit_result resolves a winner. Draw refunds are never charged this fee. Set both fields via set_protocol_config; protocol_fee_bps above 10_000 (100%) is rejected with Error::InvalidAmount.
  • Full config readget_protocol_config returns the complete ProtocolConfig struct as currently stored on-chain (treasury, vesting duration, cancellation fee, stablecoin-only mode, maximum stake, match timeout, protocol fee, and fee recipient). Off-chain tools and the frontend can call this single function instead of making multiple separate admin queries to reconstruct contract state.

🧪 Testing

Comprehensive test suite covering:

✅ Match creation and configuration
✅ Deposit validation and escrow locking
✅ Oracle result submission and verification
✅ Winner payout and draw refund logic
✅ Cancellation and edge cases
✅ Error handling and security checks

Run tests:

cargo test

🌍 Why This Matters

The Problem: Current chess betting and tournament prize payouts are slow and rely entirely on the platform's honesty. Players have no guarantee their winnings will be paid out fairly or on time.

The Solution: By holding stakes in a Soroban smart contract and automating payouts via a verified Oracle, Checkmate-Escrow removes the need to trust any third party.

Blockchain Benefits:

  • No platform can withhold or manipulate payouts
  • Transparent stake and payout history for every match
  • Programmable rules enforced by smart contracts
  • Accessible to anyone with a Stellar wallet

Target Users:

  • Competitive chess players looking for trustless wagering
  • Chess clubs and tournament organizers
  • Casual players wanting skin-in-the-game matches
  • Developers building on Stellar/Soroban

🗺️ Roadmap

  • v1.0 (Current): Token-allowlist escrow, Lichess Oracle integration, basic match flow
  • v1.1: Chess.com Oracle, expanded token support
  • v2.0: Multi-game tournaments, bracket payouts
  • v3.0: Frontend UI with wallet integration
  • v4.0: Mobile app, ELO-based matchmaking, leaderboards

See docs/roadmap.md for details.

🤝 Contributing

We welcome contributions! Please:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

See our Code of Conduct and Contributing Guidelines.

🌊 Drips Wave Contributors

This project participates in Drips Wave — a contributor funding program! Check out:

Issues are categorized as:

  • trivial (100 points) — Documentation, simple tests, minor fixes
  • medium (150 points) — Oracle helpers, validation logic, moderate features
  • high (200 points) — Core escrow logic, Oracle integrations, security enhancements

📄 License

This project is licensed under the MIT License — see the LICENSE file for details.

🙏 Acknowledgments

About

A trustless chess wagering platform on Stellar. Securely escrow match stakes via Soroban smart contracts and automate payouts using a real-time Oracle integration with Lichess/Chess.com APIs.

Resources

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages