Skip to content

cryptoduke01/swindle

Repository files navigation

Swindle

Swindle

The Chess Arena For Agents.

AI agents play chess in real time. Pick a side and bet SOL on the winner, or deploy your own agent into the arena.

Live License Open Source

Live Site Β· $SWINDLE on Bags Β· Contributing


Table of contents


What Swindle is

Swindle is a competitive arena where AI chess agents fight each other in real time, and humans bet SOL on who wins. Think of it as the intersection of an old-school cockfighting circuit, an esports broadcast, and an on-chain prediction market, only with autonomous chess engines instead of horses or fighters.

Three things make it different from every other "AI agent" product you've seen:

  1. The agents actually fight. They aren't chatbots wearing tokens. Each agent is a chess engine, running server-side, with a documented strategy and a verifiable ELO rating. They play legal chess. They lose to better play. They draw when stuck. Every move is rendered to spectators in real time.

  2. The bets are real on-chain. When you place a wager, your SOL is locked in our Anchor program. When the match settles, payouts come from that same on-chain pool, math enforced by Rust code, not by a server promise. You can verify everything on Solana Explorer.

  3. The product is watchable. Most on-chain games are turn-based and async; you place a bet and check back in an hour. Swindle matches finish in 60 to 180 seconds. You watch them play. The eval bar moves. Captures trigger sound effects and commentary toasts. It feels like a sport.

You can also play the agents yourself at /play. Drag pieces, the server validates, the agent responds in roughly 200ms. Beat Toly if you can.


How a match actually plays out

What you see when you open /arena:

  1. You pick two agents (or accept the defaults). Each has an ELO rating and a play style: aggressive, defensive, chaotic, positional, sacrificial, endgame.
  2. Click Start Match. The browser calls POST /api/match/start. The server creates a match record in process memory and on Solana via our escrow program (create_match).
  3. The browser polls POST /api/match/[id]/move every 250-400 ms. The server runs minimax with alpha-beta pruning at the agent's depth, applies a personality bonus to the move ordering, and returns the next move in SAN plus the new FEN.
  4. The browser renders the board with react-chessboard, plays a sound effect (move, capture, check, or mate), and fires a commentary toast on impactful events ("Mert bags a queen", "Toly is taking control").
  5. Meanwhile, anyone connected can place a wager. The wager UI calls our Anchor program's place_wager instruction directly from the user's wallet. Their SOL transfers into the match's PDA and is held there until settlement.
  6. When the chess game ends (checkmate, threefold repetition, fifty-move rule, insufficient material, or the 72-ply move cap), the server calls the program's settle_match instruction with the result.
  7. The match is now settled on-chain. Anyone who wagered can go to /wallet, click Claim winnings / Claim refund, sign one transaction in their wallet, and pull their share of the pool out.

That's the whole loop. From "I want to bet on a chess match" to "I have new SOL in my wallet" takes about 90 seconds.


The pari-mutuel pool: who wins how much

This is the question everyone asks, so it gets its own section.

Swindle uses pari-mutuel betting. Same model as horse racing, polymarket, and most sportsbooks. Multiple people can bet on the same side and they all win if that side wins. Nobody takes the whole pool.

The math, enforced by payout_for_winner in the Anchor program:

your_payout = your_stake + (your_stake / total_winning_pool) * (losing_pool - fee)

Worked example. Match: Toly vs Mert.

Bettor Side Stake
Alice Toly 1 SOL
Bob Toly 2 SOL
Carol Toly 0.5 SOL
Dan Mert 5 SOL

Toly wins. The losing pool is 5 SOL. The protocol takes a 3% fee off the losing pool (0.15 SOL to treasury). The remaining 4.85 SOL gets split between Alice, Bob, and Carol pro-rata to their stake:

  • Alice gets 1 + (1 / 3.5 * 4.85) = 2.39 SOL
  • Bob gets 2 + (2 / 3.5 * 4.85) = 4.77 SOL
  • Carol gets 0.5 + (0.5 / 3.5 * 4.85) = 1.19 SOL

Total paid: 8.35 SOL. Fee: 0.15 SOL. Total in: 8.5 SOL. The numbers balance.

A few things to note:

  • Bigger bet on the winning side = bigger absolute payout. Bob staked twice as much as Alice and got roughly twice her winnings.
  • Bigger losing pool = better payout for winners. If the underdog wins, payouts can be 5-10x stake. If the heavy favorite wins, you might double your money.
  • Draws and cancellations refund every wager at face value. No fee taken on a draw. Same logic for a cancelled match.

The odds displayed in the wager panel are an estimate based on the ELO difference between the two agents. Actual payouts depend on what the pool looks like when the match settles, so the displayed odds are capped at 20x to avoid showing meaningless numbers like "1060x" when a 1200 ELO custom agent faces Toly.


The agents

Eight platform agents ship today. They split across six play styles.

Agent ELO Style Behavior
Toly 2410 Positional Solana co-founder. Long horizons, cold execution.
Harkl 2390 Positional Solana engineer. Reads the board like RPC logs.
Finn 2370 Endgame Backpack voice. Survives chaos, closes late.
Mert 2340 Aggressive Helius CEO. Pushes tempo, sacrifices for initiative.
Raj 2280 Defensive Solana co-founder. Fortress structure, patient punishes.
Alon 2190 Sacrificial pump.fun co-founder. Material for momentum.
Ansem 2150 Chaotic Solana CT legend. Timeline chaos that somehow wins.
Vibhu 2120 Aggressive Jupiter co-founder. Routes every attack through the best line.

All eight are the same minimax engine (src/lib/server/engine.ts) with a personality bonus baked into the move ordering function. Aggressive agents prioritize captures and checks. Defensive agents pile on king-side protection. Chaotic agents inject noise into the eval. Endgame agents simplify the position. The result is real chess with real personality, not eight different engines.

You can also deploy your own agent via /deploy. Custom agents start at ELO 1200 and climb (or sink) based on real match results. The agent registration goes into Upstash Redis (or a graceful in-memory fallback if Upstash isn't configured), and shows up in the swap menu inside the arena.


Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     POST /api/match/start    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Browser (Next.js)   β”‚ ───────────────────────────► β”‚  Next.js API routes  β”‚
β”‚   /arena, /play      β”‚                              β”‚  (serverless on      β”‚
β”‚   Phantom wallet     β”‚ ◄────────── FEN ──────────── β”‚   Vercel)            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                              β”‚  + Upstash Redis     β”‚
           β”‚                                          β”‚  + minimax engine    β”‚
           β”‚ signs place_wager                        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚                                                     β”‚
           β–Ό                                                     β”‚ oracle key
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ signs
β”‚  Solana devnet/mainnet                                       β”‚ β”‚ settle_match
β”‚                                                              β”‚β—„β”˜
β”‚  swindle_escrow program (FANtekM48...)                       β”‚
β”‚    create_match Β· place_wager Β· settle_match Β· claim_payout  β”‚
β”‚    cancel_match Β· transfer_match_oracle                      β”‚
β”‚                                                              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Stack

  • Frontend: Next.js 16 App Router, React 19, TypeScript, inline-styled components (no Tailwind classes for padding/margin/grid because TW v4 was unreliable on this project, only used for breakpoint utilities)
  • Fonts: Geist for body, Manrope for display headings, JetBrains Mono for numbers and code
  • Chess engine: chess.js for board state + a custom minimax with alpha-beta pruning, personality-driven move ordering, and a 72-ply move cap with adjudication
  • Wallet: @solana/wallet-adapter over the Wallet Standard. Works with Phantom, Solflare, Backpack, anything that registers itself.
  • Wagers: Real SystemProgram.transfer on devnet (treasury fallback) or PDA-locked SOL via the Anchor program (escrow mode, set via env var)
  • Persistence: Upstash Redis for agents, users, match archive, agent stats. Vercel Blob for agent portrait uploads. Both have graceful in-memory fallbacks for dev.

Why the chess engine runs server-side

It would be faster and cheaper to run minimax in the browser. We do it server-side anyway because:

  1. Oracle truth. The match outcome that settles funds on-chain has to come from one source. If the engine ran in the browser, two different browsers could disagree on the result and we'd need an arbitration mechanism. Running it once on the server eliminates that.
  2. Custom agents. Future versions will let users register a chess engine endpoint at agent registration time. The server can call any endpoint; the browser can't reasonably proxy arbitrary HTTP from inside a sandbox.
  3. Anti-cheat for human-vs-agent mode. At /play, the human's move is validated against the server's board state before the agent replies. This stops drag-the-piece-to-an-illegal-square clients.

Cold start latency on Vercel's edge is around 100ms for the move endpoint. Together with the artificial pacing delay (250-400ms), matches feel like a real broadcast.


The on-chain escrow program

Lives in anchor/programs/swindle_escrow/src/lib.rs. Deployed at:

FANtekM48DJzdmynD3q78sbWMimx7ECZZYR5agX1jpoL

(Devnet right now. Mainnet deploy planned alongside the $SWINDLE launch.)

Instructions

Instruction Signer What it does
create_match(match_id, fee_bps, treasury) Oracle Initializes a Match PDA. The match ID is a 16-byte string, used as the PDA seed. Fee is capped at 5%.
place_wager(amount, side) User Transfers SOL from user to the match PDA. Records a Wager PDA seeded by (match, user). Updates the match's total_white or total_black.
settle_match(winner) Oracle Records the winner on the match. Skims the 3% fee from the losing pool to treasury. Match transitions to Settled.
claim_payout() User Calculates the user's share of the pool (or refund), debits the match PDA, credits the user. Marks the wager claimed. Idempotent.
cancel_match() Oracle Marks a match Cancelled. Every wager becomes refundable via claim_payout at face value, no fee taken. Used when a match crashes mid-game.
transfer_match_oracle(new_oracle) Oracle Hands oracle authority for this match to a new pubkey. Used for server key rotation and migrating high-value matches to a multisig.

Trust model

The oracle (our server's signer) decides who won. This is the same trust model as Polymarket, Drift, or any centralized sportsbook. The funds themselves, the math that distributes them, and the immutability of the result post-settlement are all enforced on-chain.

The next big trust upgrade is replacing the centralized oracle with one of:

  • A verifiable game-state oracle (a ZK proof that a sequence of moves leads to a given outcome under chess.js rules)
  • A multisig oracle for high-value matches
  • A staked validator set that votes on outcomes

transfer_match_oracle is the road to all three. The interface for adopting a new oracle doesn't require a program upgrade, just a key rotation per match.

Math audit

The pari-mutuel payout function uses u128 for all intermediate calculation, so overflow is impossible at any realistic SOL amount. Integer division does cost a few lamports of dust per claim (worst case roughly 3 lamports), which accumulates in the match PDA. This is benign for the current use case; a future sweep_dust instruction will drain it to treasury.

The fee is computed once in settle_match and physically transferred to treasury at that point. claim_payout re-derives the fee for math purposes but doesn't transfer it again. Multi-winner pools split the post-fee distributable amount strictly by stake share.


Routes

Route Description
/ Landing: agents grid, how it works, deploy CTA
/arena Live AI vs AI matches with wagering
/play Human vs agent: pick agent, pick color, play
/tournament 8-agent single-elimination bracket
/spectate Browse live matches
/leaderboard Podium top 3 + ranked table
/deploy Form-based custom agent deployment
/wallet Balance, send/receive, pending claims, recent transactions
/match/[id] Permalink for a finished match
/docs User-facing documentation
/api/agents GET roster Β· POST register custom agent
/api/match/start POST create a new match
/api/match/[id]/move POST advance one move (engine call)
/api/match/[id] GET match state
/api/match/[id]/human-move POST submit a human move + get the agent's reply
/api/match/start-human POST initialize a human-vs-agent match
/api/match/list GET active matches
/api/escrow/ensure-match POST oracle creates the on-chain match if missing
/api/escrow/settle-match POST oracle settles a match on-chain
/api/escrow/cancel-match POST oracle cancels a stuck match (refund mode)
/api/escrow/transfer-oracle POST oracle rotates the match's oracle key
/api/escrow/match-state GET on-chain match + user wager status
/api/escrow/pending-claims GET wallet's claimable + history wagers
/api/escrow/recover-wager POST settle a stuck match as a draw + refund
/api/status GET storage + blob + escrow status (health check)
/api/health GET basic health check
/api/banner GET 1500x500 PNG, X/Twitter profile banner

Local development

git clone https://github.com/cryptoduke01/swindle.git
cd swindle
pnpm install
pnpm dev

That's enough for everything except on-chain wagering. The site falls back to "treasury mode" (a direct SystemProgram.transfer to the treasury wallet) when the escrow program ID isn't set in env. Matches still run, you can still place wagers, settlement is just off-chain.

To run with the Anchor program too, you'll need the deployed program ID and an oracle keypair (see env vars below).


Environment variables

# Solana network: devnet (default) or mainnet-beta
NEXT_PUBLIC_SOLANA_NETWORK=devnet

# Optional: dedicated RPC URL (Helius, QuickNode, etc.)
# Default is the public Solana RPC for the chosen network.
NEXT_PUBLIC_SOLANA_RPC_URL=https://api.devnet.solana.com

# Site URL for metadata (set in Vercel for production)
NEXT_PUBLIC_SITE_URL=https://playswindle.fun

# --- Persistent storage (optional) -----------------------------------
# Without these, custom agents and match archive live in process memory
# and disappear on every Vercel cold start.
# Get free credentials at https://upstash.com β†’ Redis β†’ REST API tab.
UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=

# --- Agent portrait uploads (optional) -------------------------------
# Vercel Blob token from your Vercel project. Required if you want to
# accept image uploads on /deploy.
BLOB_READ_WRITE_TOKEN=

# --- On-chain escrow (optional) --------------------------------------
# When set, wagers go through the Anchor program. When unset, wagers
# fall back to a direct treasury transfer.
NEXT_PUBLIC_ESCROW_PROGRAM_ID=FANtekM48DJzdmynD3q78sbWMimx7ECZZYR5agX1jpoL

# Server-side oracle keypair. Either:
#   - JSON array of bytes:  [1,2,3,...]
#   - Base58 string:        4xX...
# Required for /api/escrow/settle-match, /api/escrow/cancel-match,
# /api/escrow/recover-wager, /api/escrow/transfer-oracle.
ESCROW_ORACLE_SECRET_KEY=

# Optional shared secret. When set, the admin escrow endpoints require
# an x-admin-token header matching this value.
ESCROW_ADMIN_TOKEN=

Deploying your own copy

The whole stack is built around being a single git push away from production.

  1. Fork the repo or clone it locally
  2. Push to your own GitHub
  3. Import the repo into Vercel (root directory, pnpm build)
  4. Set the env vars listed above in Vercel's project settings
  5. Done

To wire on-chain escrow:

  1. Generate a program keypair: solana-keygen new -o target/deploy/swindle_escrow-keypair.json
  2. Sync into the Anchor source: cd anchor && anchor keys sync
  3. Build: anchor build (or solana program deploy target/deploy/swindle_escrow.so if the IDL step fails)
  4. Deploy to devnet: anchor deploy --provider.cluster devnet
  5. Copy the program ID into NEXT_PUBLIC_ESCROW_PROGRAM_ID on Vercel
  6. Generate an oracle keypair (separate from your deploy key) and put its secret key into ESCROW_ORACLE_SECRET_KEY
  7. Fund the oracle with a small amount of SOL so it can pay fees for create_match and settle_match
  8. Redeploy

After redeploy, hit /api/status to confirm everything is wired:

{
  "status": "ok",
  "storage": "upstash",
  "persistent": true,
  "blob": { "configured": true },
  "agents": { "platform": 8, "custom": 0, "total": 8 },
  "network": "devnet"
}

Comparison with similar projects

Uphive Drift / Polymarket Swindle
Domain Generic AI task marketplace Generic prediction market Chess arena
Token USDC (Base) USDC SOL ($SWINDLE on Bags)
Settlement On-chain escrow (Base) On-chain On-chain Anchor escrow (Solana)
Agents Bring-your-own autonomous N/A Built-in + custom
Reputation On-chain reputation score Trader history ELO that updates per match
Watchable No, async tasks Markets resolve later Yes, every move live

The closest comparison is probably Drift, which has its perp markets settle quickly and has a strong "live trading" feel. Swindle's positioning is "Drift, but the market is an agent fight you can watch."


Roadmap

Done:

  • Server-side chess engine with personality
  • Real SOL transfers (treasury fallback)
  • Custom agent deployment via form
  • Persistent storage via Upstash Redis
  • Custom agents in arena swap modal
  • Wallet page with balance, send/receive, recent transactions
  • OG / Twitter card with logo + branding
  • Anchor escrow program live on devnet with pari-mutuel payouts
  • Human vs Agent mode at /play
  • Tournament mode at /tournament
  • Live commentary toasts + sound effects
  • On-chain match cancellation + oracle rotation
  • One-click claim flow on /wallet
  • 3D rotatable agent inspect modal

Next:

  • @swindle/cli npm package so the CLI deploy tab on /deploy is real, not aspirational
  • Agent leaderboard PnL (which agents have made the most money for backers)
  • Spectator pool stats on /spectate ("12 SOL pooled on this match")
  • Match replay player on /match/[id] (scrub through moves)
  • Mainnet deploy alongside $SWINDLE launch
  • Verifiable game-state oracle (ZK proof of move sequence)
  • Tournament prize pools

Contributing

Open source under MIT. See CONTRIBUTING.md for project layout, dev tips, and good-first-issues. Pull requests welcome.

Links

License

MIT Β© 2026 cryptoduke01

About

The Open Source Chess Arena For Agents. AI agents play chess in real time. Pick a side and bet SOL on the winner or deploy your own agent into the arena.

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors