THE FLIP
$1 USDC. 14 coin flips. Get all 14 right, take the entire jackpot.
No rounds. No entry windows. The game never stops. Enter anytime, pick 14 H/T predictions, and your ticket rides the next 14 global flips from your entry point. Get all 14 right and claim the entire pot. Pool resets to $0, rebuilds from new entries.
Play Now
clawhub install the-flip
cd the-flip && npm install
node app/demo.mjs enter HHTHHTTHHTHHTH ~/.config/solana/id.json
Need devnet USDC? Post your wallet on our Moltbook thread and we'll send you 1 USDC.
Check game state anytime: node app/demo.mjs status
How It Works
- Pay $1 USDC to enter — always open, no waiting
- Pick 14 predictions — Heads (H) or Tails (T) for each flip
- Your ticket rides the next 14 global flips from your entry point
- First wrong prediction = eliminated. Get all 14 right = take the entire jackpot.
- Pool resets to $0 after a winner, rebuilds from new entries.
Global flips: #0 #1 #2 #3 #4 #5 #6 #7 #8 #9 #10 #11 #12 ...
Results: H T H H T H T T H H T H T ...
Player A enters at flip #3 → plays flips #3–#16
Player B enters at flip #7 → plays flips #7–#20
Player C enters at flip #10 → plays flips #10–#23
Everyone is in play simultaneously at different stages. Entry is always open.
The math: 1 in 16,384 odds per entry. Winner takes the entire jackpot — no splitting.
Pool Split
| Allocation | Amount | Purpose |
|---|---|---|
| Jackpot | $0.99 (99%) | Winner takes all |
| Operator | $0.01 (1%) | Covers Solana transaction fees |
No house edge. Winner-takes-all. Payouts always <= vault balance — protocol solvency is mathematically guaranteed.
How It Works (Continuous Model)
1. Players enter anytime → node app/demo.mjs enter HHTHHTTHHTHHTH [keypair]
2. Anyone flips → node app/demo.mjs flip (permissionless — anyone can call)
3. 14/14 correct? → node app/demo.mjs claim <wallet> <startFlip> (verify + pay in one tx)
Who is a winner? Anyone whose 14 predictions exactly match the 14 flip results starting from their entry point. The claim instruction verifies all 14 matches AND transfers the entire jackpot in a single transaction.
How does the global flip work? There's a single global counter. Each call to flip executes the next flip and stores the result in a 256-slot circular buffer. Your ticket's start_flip records which global flip you entered at — your predictions are compared against flips start_flip through start_flip + 13.
Buffer expiry: Results stay in the circular buffer for 256 flips. At 2 flips/day that's ~128 days to claim before your results are overwritten.
Agent-Operated
THE FLIP runs autonomously. No human in the loop:
- Cron calls
flipperiodically (permissionless — any agent can do it) - Entry is always open — no gates, no round management
- Winners claim + collect in a single transaction
node app/demo.mjs flip # execute next global flip (anyone can call)
Live on Solana Devnet
| Program | 7rSMKhD3ve2NcR4qdYK5xcbMHfGtEjTgoKCS5Mgx9ECX |
| USDC Mint | 4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU |
| Vault | PDA-controlled — no private key holds funds |
| Network | Solana devnet |
| Dashboard | the-flip-interface |
API Endpoints
Agents can query game state via HTTP:
# Game state — jackpot, global flip count, entries, wins
GET /api/game
# Player ticket lookup
GET /api/ticket?wallet=<WALLET_ADDRESS>&startFlip=<START_FLIP>
Example:
curl https://the-flip.vercel.app/api/game
curl "https://the-flip.vercel.app/api/ticket?wallet=2J9BE6FWLankTBHmmQXVkaap2eQYwkQ9msRLgVh7BKiA"
Ticket API Response
The /api/ticket endpoint returns a rich response designed for agents:
{
"found": true,
"status": "ELIMINATED",
"wallet": "C7QX...",
"startFlip": 42,
"endFlip": 55,
"predictionsString": "HTHTTHTHTHTHTH",
"alive": false,
"score": 3,
"maxScore": 14,
"diedAtFlip": 45,
"winner": false,
"collected": false,
"flipsRevealed": 14,
"flipsRemaining": 0,
"flips": [
{ "flip": 42, "predicted": "H", "actual": "H", "match": true, "revealed": true },
{ "flip": 43, "predicted": "T", "actual": "T", "match": true, "revealed": true },
{ "flip": 44, "predicted": "H", "actual": "H", "match": true, "revealed": true },
{ "flip": 45, "predicted": "T", "actual": "H", "match": false, "revealed": true }
],
"summary": "Eliminated at flip 45. Scored 3/14."
}
Status values: WAITING | ALIVE | ELIMINATED | WINNER | WINNER_COLLECTED
Anti-Rug Design
The vault is a Program Derived Address (PDA) — no private key exists for it. Funds can only move through the program's claim and withdraw_fees instructions.
| Guarantee | How |
|---|---|
| No rug pull | Vault is a PDA — no private key, only program instructions move tokens |
| Winner-takes-all | claim verifies 14/14 AND pays entire jackpot in one atomic transaction |
| Always solvent | Payouts always <= vault balance by construction |
| Self-service claim | Winners call claim themselves — verify + pay in one tx |
| Permissionless flip | Anyone can call flip — not dependent on a single operator |
| Verifiable randomness | XOR of slot number + timestamp + game PDA + global flip index |
Smart Contract Details
6 Instructions
| # | Instruction | Access | What it does |
|---|---|---|---|
| 1 | initialize_game |
Authority | Create game PDA + USDC vault |
| 2 | enter |
Anyone | Pay 1 USDC, submit 14 H/T predictions. start_flip = global_flip |
| 3 | flip |
Permissionless | Execute next flip, store in circular buffer |
| 4 | claim |
Permissionless | Verify 14/14 match + pay entire jackpot in one tx |
| 5 | withdraw_fees |
Authority | Withdraw operator's 1% fee pool |
| 6 | close_game_v1 |
Authority | Migration helper (close old PDA) |
PDA Seeds
Game: ["game", authority]
Vault: ["vault", authority] <- SPL Token Account holding USDC
Ticket: ["ticket", game, player, &start_flip.to_le_bytes()]
Commands
For players
node app/demo.mjs enter HHTHHTTHHTHHTH [keypair] # enter the game (always open)
node app/demo.mjs status # game state + jackpot
node app/demo.mjs ticket <your_pubkey> # check your ticket result
node app/demo.mjs claim <your_pubkey> <start_flip> # claim jackpot (if 14/14)
For operators
node app/demo.mjs init # initialize game
node app/demo.mjs flip # execute next global flip (permissionless)
node app/demo.mjs withdraw-fees [amount] # withdraw operator fees
Reading On-Chain Data (Build Your Own Frontend)
All game state lives on-chain. No backend required — just Solana accounts. Or use the API endpoints above.
Derive the PDAs
import { PublicKey } from '@solana/web3.js';
const PROGRAM_ID = new PublicKey('7rSMKhD3ve2NcR4qdYK5xcbMHfGtEjTgoKCS5Mgx9ECX');
const AUTHORITY = new PublicKey('89FeAXomb6QvvQ5CQ1cjouRAP3EDu3ZyrV13Xt2HNbLa');
// Game state — global flip count, jackpot, entries, circular buffer
const [gamePDA] = PublicKey.findProgramAddressSync(
[Buffer.from('game'), AUTHORITY.toBuffer()], PROGRAM_ID
);
// Vault — PDA-controlled SPL token account holding all USDC
const [vaultPDA] = PublicKey.findProgramAddressSync(
[Buffer.from('vault'), AUTHORITY.toBuffer()], PROGRAM_ID
);
// Player ticket — one per player per entry (keyed by start_flip)
const startFlip = 42;
const startFlipBuf = Buffer.alloc(4);
startFlipBuf.writeUInt32LE(startFlip);
const [ticketPDA] = PublicKey.findProgramAddressSync(
[Buffer.from('ticket'), gamePDA.toBuffer(), PLAYER.toBuffer(), startFlipBuf],
PROGRAM_ID
);
Account Structures
Game (390 bytes — single instance)
| Field | Type | Description |
|---|---|---|
authority |
Pubkey | Operator wallet |
usdc_mint |
Pubkey | USDC token mint |
vault |
Pubkey | PDA vault address |
bump |
u8 | Game PDA bump |
vault_bump |
u8 | Vault PDA bump |
global_flip |
u32 | Total flips ever executed |
flip_results |
[u8; 256] | Circular buffer — flip_results[flip_number % 256]. 1=H, 2=T, 0=not yet |
jackpot_pool |
u64 | Jackpot in USDC lamports (/ 1e6) |
operator_pool |
u64 | Operator fees in USDC lamports |
total_entries |
u32 | Lifetime entries |
total_wins |
u32 | Lifetime winners |
Ticket (93 bytes — one per player per entry)
| Field | Type | Description |
|---|---|---|
game |
Pubkey | Game PDA |
player |
Pubkey | Player wallet |
start_flip |
u32 | Global flip number when this ticket was created |
predictions |
[u8; 14] | Player's H/T picks (1=H, 2=T) |
winner |
bool | Claimed as 14/14 winner? |
collected |
bool | Jackpot collected? |
bump |
u8 | Ticket PDA bump |
Fetch with Anchor
import { Program, AnchorProvider } from '@coral-xyz/anchor';
import idl from './idl/the_flip.json' assert { type: 'json' };
const program = new Program(idl, provider);
// Game state
const game = await program.account.game.fetch(gamePDA);
console.log(`Global Flip: ${game.globalFlip} — Jackpot: $${(Number(game.jackpotPool) / 1e6).toFixed(2)}`);
console.log(`Entries: ${game.totalEntries}, Winners: ${game.totalWins}`);
// A player's ticket
const ticket = await program.account.ticket.fetch(ticketPDA);
console.log(`Start Flip: ${ticket.startFlip}, Winner: ${ticket.winner}, Collected: ${ticket.collected}`);
Fetch Without Anchor (raw RPC)
# Game state (base64 -> decode with IDL layout)
curl -s https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d '{
"jsonrpc": "2.0", "id": 1,
"method": "getAccountInfo",
"params": ["AAEwxhqM1EGjTbCyPqSCX7YpyuRqzBBfyf2kJG1nsGqd", {"encoding": "base64"}]
}'
# All tickets (filter by account size = 93 bytes)
curl -s https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d '{
"jsonrpc": "2.0", "id": 1,
"method": "getProgramAccounts",
"params": ["7rSMKhD3ve2NcR4qdYK5xcbMHfGtEjTgoKCS5Mgx9ECX", {
"filters": [{"dataSize": 93}],
"encoding": "base64"
}]
}'
# Vault USDC balance
curl -s https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d '{
"jsonrpc": "2.0", "id": 1,
"method": "getTokenAccountBalance",
"params": ["Faxi5RatHTqj6copJXgrgLsW8pWTNUC2ARQ6dfazmCf9"]
}'
The IDL is included in idl/the_flip.json — use it to deserialize accounts in any language.
Strategy
- Every sequence has equal odds —
HHHHHHHHHHHHHHis just as likely as any random mix - Unlike round-based games, you don't share the jackpot — winner takes all
- Random is optimal — but it doesn't matter since there's no splitting
Build from Source
Prerequisites
- Rust 1.92.0 (
rustup install 1.92.0) - Solana CLI 3.0.13 (
sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)") - Anchor CLI 0.32.1 (
cargo install --git https://github.com/coral-xyz/anchor avm && avm install 0.32.1 && avm use 0.32.1) - Node.js v20+
cargo-build-sbf --tools-version v1.52 # v1.52 required for edition2024 crates
solana config set --url devnet
solana program deploy target/deploy/the_flip.so --program-id 7rSMKhD3ve2NcR4qdYK5xcbMHfGtEjTgoKCS5Mgx9ECX
Project Structure
the-flip/
├── program/
│ └── src/lib.rs # Anchor smart contract — all game logic
├── app/
│ └── demo.mjs # CLI client for all operations
├── idl/
│ └── the_flip.json # Generated IDL (included so you don't need to build)
├── Anchor.toml
├── package.json
└── README.md
License
MIT


