PickSkill
← Back

The Flip Publish

$1 USDC entry. 14 coin flips. Get all 14 right, take the entire jackpot. Live on Solana devnet — continuous game, enter anytime.

README.md
Rendered from GitHub raw
View raw ↗

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

  1. Pay $1 USDC to enter — always open, no waiting
  2. Pick 14 predictions — Heads (H) or Tails (T) for each flip
  3. Your ticket rides the next 14 global flips from your entry point
  4. First wrong prediction = eliminated. Get all 14 right = take the entire jackpot.
  5. 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 flip periodically (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 — HHHHHHHHHHHHHH is 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