flip.betflip.bet
Developer API

Build your own flip.bet front-end

The Anchor program is open source and the SDK is published on npm. Anyone can talk to the same on-chain program — host their own UI, run their own indexer, or build novel game variants on top of the same fairness primitives.

Program ID

F1pb…E5Wd

mainnet-beta

SDK version

0.1.0

MIT

Bundle size

9.2 KB

gzipped, tree-shaken

Runtime

any

Node · Deno · Bun · browser

Install

bash
npm install @flipbet/sdk @solana/web3.js# orpnpm add @flipbet/sdk @solana/web3.js# orbun add @flipbet/sdk @solana/web3.js

Quick start

Build a transaction that creates a 0.1 SOL match in three lines:

create-match.ts
typescript
import { Connection, Keypair, Transaction } from "@solana/web3.js";import { createMatchSolIx, BN } from "@flipbet/sdk";import { randomBytes } from "node:crypto"; const conn = new Connection("https://api.mainnet-beta.solana.com");const me = Keypair.generate(); // your wallet adapter signer const { ix, matchAccount } = createMatchSolIx({  creator: me.publicKey,  nonce: new BN(Date.now()),                  // any unique value  wagerLamports: new BN(0.1 * 1e9),  pick: "heads",  creatorSeed: randomBytes(32),                // commit; revealed at settle}); const tx = new Transaction().add(ix);tx.feePayer = me.publicKey;tx.recentBlockhash = (await conn.getLatestBlockhash()).blockhash;const sig = await conn.sendTransaction(tx, [me]);console.log("match created:", matchAccount.toBase58(), sig);
The matchAccount returned is a PDA derived from [b"match", creator.key(), nonce.to_le_bytes()]. Anyone can fetch the same address knowing the creator + nonce — no IDs to coordinate.

Instruction builders

Every program instruction has a typed builder that returns a ready-to-sign TransactionInstruction. Builders never throw on missing accounts; they fill PDAs deterministically.

BuilderCaller
createMatchSolIxPlayer A
joinMatchSolIxPlayer B
requestRandomnessIxEither
settleIxAnyone
claimWinningsIxWinner / anyone
offerDorIxWinner
acceptDorIxLoser
declineDorIxLoser / anyone
cancelOpenIxCreator

Account decoders

On-chain Match data is decoded with strict types — no runtime casts. Use these from any environment that can read account data:

typescript
import { fetchMatch, listOpenMatches, deriveOutcome } from "@flipbet/sdk"; const match = await fetchMatch(conn, matchPubkey);if (!match) throw new Error("match not found"); console.log(match.status);     // "open" | "joined" | ...console.log(match.pot.toString()); // Recompute the outcome client-side and compare to on-chain result.const { side } = deriveOutcome({  vrfValue:    match.lastEntropy,        // already SHA-256'd by the program  creatorSeed: match.creatorSeed,  opponentSeed: match.opponentSeed,});console.log("verified", side === match.lastResult); // Fetch all open matches for a lobby render. Heavy on RPC — prefer the// indexer WebSocket (below) for live updates.const open = await listOpenMatches(conn);

Helius webhook → indexer

Subscribe to every program-touching transaction by pointing a Helius Enhanced Transactions webhook at our flipbet-indexer Worker. The Worker decodes Anchor events and writes into a Lobby Durable Object that web clients subscribe to.

helius-webhook.json
json
{  "webhookURL": "https://flipbet-indexer.your-workers.dev/webhook",  "transactionTypes": ["ANY"],  "accountAddresses": ["F1pbet4Hjk9zqsZTPKqNPQ8YEfeZCJdrvZB56dbqE5Wd"],  "webhookType": "enhanced",  "authHeader": "shared-secret-here"}

Decoded event payloads

Each event carries the same shape across the SDK and indexer Worker. All fields are bytes/numbers, never trusted strings.

MatchCreatedlobby
MatchJoinedlobby
RandomnessRequestedlifecycle
MatchSettledlifecycle
DorOffereddor
DorAccepteddor
DorDeclineddor
WinningsClaimedlifecycle
MatchCancelledlifecycle
CommunitySwepttreasury

Realtime WebSocket

For live lobby updates, connect to the indexer Worker's WebSocket. It's a single global connection — one stream per origin — and the Cloudflare hibernation API means idle DOs cost nothing.

lobby-stream.ts
typescript
const ws = new WebSocket("wss://flipbet-indexer.your-workers.dev/lobby/connect"); ws.onmessage = (e) => {  const msg = JSON.parse(e.data);  switch (msg.kind) {    case "snapshot": {      // Full initial state: open[], recent[]      hydrateLobby(msg.open);      break;    }    case "match_added":   addToLobby(msg.match);    break;    case "match_removed": removeFromLobby(msg.matchPda); break;    case "settled":       prependRecentFlip(msg.flip); break;  }};

Auth-less by design

The lobby WebSocket is read-only and unauthenticated. Anyone can consume it. Rate-limited at the Cloudflare edge to ~50 msg/s per connection, which is more than the program will ever produce.

Direct RPC (no SDK)

If you're building in a language without the SDK, talk to the program directly via JSON-RPC. The instruction layout matches Anchor conventions — discriminator + Borsh args. The Rust source is the canonical reference; an IDL JSON is published on every release.

curl
bash
curl https://api.mainnet-beta.solana.com -X POST \  -H "Content-Type: application/json" \  -d '{    "jsonrpc":"2.0","id":1,"method":"getProgramAccounts",    "params":[      "F1pbet4Hjk9zqsZTPKqNPQ8YEfeZCJdrvZB56dbqE5Wd",      {"encoding":"base64","filters":[{"memcmp":{"offset":0,"bytes":"<MATCH_DISC>"}}]}    ]  }'

Rate limits & costs

12