In traditional online blackjack, the house holds your money in a database row and controls the shuffle behind a TLS connection. You trust the operator's server, or you don't play. A Kaspa UTXO game flips that model: every round is a chain of on-chain covenant states, each one a UTXO whose redemption script defines the only valid next move. No server holds funds. No participant can rewrite history. The contract *is* the rules.
This article walks through exactly how that covenant game state machine works for Arena Blackjack — from Room creation through JOIN, deal, play, and terminal settlement — using the actual architecture Kaspa Forge runs on mainnet.
A finite state machine where each state is an unspent transaction output (UTO). Transitions are transactions that consume the current UTXO and produce a successor whose covenant script encodes the next valid state. On Kaspa, this relies on Toccata covenant opcodes activated in mainnet.
The Problem: Gambling Without a Custodian
A provably fair card game on-chain needs to solve three problems simultaneously:
1. Funds must be locked, not held. Neither player nor dealer should be able to walk away with the pot mid-hand. 2. The shuffle must be verifiable. The deck cannot be known to either party before cards are dealt, yet both must be able to verify fairness after the fact. 3. Every outcome must be self-enforcing. The winning payout cannot depend on the loser's willingness to pay.
Kaspa's UTXO model is unusually well-suited to this. Each UTXO is a discrete, independently verifiable unit of state. Covenant scripts — enabled by Kaspa's Toccata upgrade — let you write redemption conditions that inspect the transaction spending the output: which outputs are produced, what scripts they carry, how much value they hold. A Kaspa Arena Room Game becomes a chain of UTXOs, each one a state in a finite state machine, where the covenant *is* the referee.
Anatomy of a Room UTXO
Before any cards are dealt, a game begins as a Room — a UTXO created by the dealer that locks funds and declares the rules.
A Room UTXO carries:
- Stake tier: one of
5 / 10 / 25 / 50 / 75 / 100 KAS, identical for both sides - Dealer bond:
2or5 KAS— the dealer's liveness guarantee - Fee reserve: calculated from the worst-case network fee path at the current inclusion cutoff, doubled for safety
- Entry policy:
OPEN(anyone who matches the stake can join) orINVITE(only a specific player public key) - Room mode:
LOCALorHOSTED— determines whether a ZK proof fee applies - Timeouts: room timeout (~24 hours at 10 BPS, encoded as
864,000 DAA), plus all game-phase deadlines pinned in atimeout_policy_hash
The total value locked in a Room before anyone joins is 1.5 × stake + dealer_bond + reserve. The extra half-stake is the dealer's pre-funding for a potential natural blackjack payout at 3:2 odds.
A Room covenant has exactly two branches:
WAITING_ROOM
├── JOIN → player signature + matching stake/bond → DECK_PROOF_PENDING (Game UTXO)
└── ROOM_TIMEOUT → anyone after DAA deadline → dealer refund (capped fee)
No third path. The dealer cannot reclaim funds while a valid JOIN is possible, and a player cannot extract funds without producing a valid signature with the correct stake.
Step 1: JOIN — From Room to Game
When a player broadcasts a JOIN transaction, the covenant verifies:
- The player's signature matches the declared public key (or, for
INVITErooms, a pinned key) - The player contributes exactly
stake + player_bondin matching inputs - The successor output carries the correct
covenant_id— binding it to the Game template
The single transaction consumes the Room UTXO and produces a Game UTXO in the DECK_PROOF_PENDING state. At this point, the full pot is locked: 2.5 × stake + both bonds + reserve. The Game UTXO inherits every pin from the Room — stake, fees, timeouts, mode — and writes the player's key, address, and commitment into its state region. These fields are write-once: no subsequent transition can alter them.
DECK_PROOF_PENDING
├── PROVE_AND_DEAL → dealer signature + ZK proof → PLAYER_TURN
├── DEALER_DECK_PROOF_TIMEOUT → anyone after deadline → player wins all principal + bonds
Step 2: PROVE_AND_DEAL — Shuffle and Initial Hand
This is where verifiability enters. The dealer must prove, via a zero-knowledge proof, that the deck was shuffled fairly — that neither party knew the card order before commitment.
The PROVE_AND_DEAL transaction carries:
- A ZK proof journal bound to
game_id, both commitments, the revealed player seed, and the rules schema - The
deck_root— a Merkle root committing to all 52 cards in the shuffled order - Merkle paths revealing cards at indices 0, 1, and 2 (player's two cards and the dealer's up-card)
- A
hole_card_commit— a blake3 commitment to the dealer's hidden card (index 3)
The successor Game UTXO enters PLAYER_TURN with next_card_index = 4, meaning the next HIT would draw card index 4 from the committed deck.
Card indices in the V1 protocol:
0 player card 1
1 dealer up-card
2 player card 2
3 dealer hole card (committed, not revealed)
4 first HIT card
The Groth16 proof variant used on mainnet produces a ~3.2 KB transaction at roughly 0.16 KAS in proof-related costs — paid by the house under current fee policy V3, not deducted from the player's stake.
Step 3: Player Actions — HIT, STAND, and the Clock
Once in PLAYER_TURN, the on-chain blackjack protocol branches into interactive play states:
PLAYER_TURN
├── PLAYER_HIT_CONTINUE → valid Merkle proof for next card, hand not bust → PLAYER_TURN
├── PLAYER_HIT_BUST → valid proof, total > 21 → terminal (dealer wins)
├── PLAYER_STAND → player signature → DEALER_TURN
├── PLAYER_ACTION_TIMEOUT → anyone after deadline → auto-stand (permissionless)
Each HIT is a covenant transition that:
1. Accepts a Merkle proof showing the next card (at next_card_index) belongs to deck_root 2. Appends the card to player_hand[] 3. Computes the hand total in-script from the raw card bytes — not from a witness-supplied number. The script recalculates the total, including ace promotion logic, directly from the cards the successor itself writes. A player cannot claim a false total; the math is the binding.
The PLAYER_ACTION_TIMEOUT branch is notable: it is permissionless (anyone can broadcast it) and uses a sequence lock to enforce the DAA deadline. It does not pay out the player's stake — it merely auto-stands, advancing the hand to the dealer's turn. The auto-stand target is a constant embedded in the branch, not chosen by the broadcaster.
Step 4: Dealer's Turn and Hole Card Reveal
When the player stands, the covenant transitions to DEALER_TURN. The dealer must now:
1. Reveal the hole card — the transaction must open the blake3 commitment *and* verify the card against deck_root simultaneously, reading from the same physical byte in the state region 2. Draw to 17 or bust — each dealer HIT follows the same Merkle proof discipline as the player's 3. Stand — once the dealer's total is ≥ 17
The dealer faces the same timeout discipline. DEALER_ACTION_TIMEOUT awards the entire pot to the player — no fees deducted, because the service was not completed.
Step 5: Terminal Settlement — The Covenant Decides
Terminal branches are where the covenant earns its name. The outcome is computed from both hands — player_hand[] and dealer_open_hand[] — directly in the script. The branch that builds the settlement transaction must prove:
- The correct outcome (win, loss, push, or natural)
- Exact output amounts for each participant
- Exact fee outputs (service fee, and prover fee in HOSTED mode)
- No foreign outputs — the covenant pins the number of outputs and rejects any addition
- Fee reserve is capped — spending one sompi over the cap is a consensus rejection
The payout table:
| Outcome | Player receives | Dealer receives | Fees |
|---|---|---|---|
PLAYER_WIN | net + player_bond | dealer_bond | exact outputs |
DEALER_WIN | player_bond | net + dealer_bond | exact outputs |
PUSH | stake − player_fees + player_bond | stake − dealer_fees + dealer_bond | exact outputs |
| Dealer timeout | entire bank + both bonds | nothing | zero |
Where net = 2 × stake − fees. Natural blackjack (exactly two cards totalling 21) pays 3:2 — the extra half-stake comes from the dealer's initial Room funding. The natural-win branch and the regular-win branch are mutually exclusive: the script checks player_natural && !dealer_natural as a derived fact from the card bytes, not as a witness claim. A three-card 21 is *not* a natural and does not trigger the 3:2 payout.
Bonds are always returned in full regardless of outcome. They exist to penalize absence, not to fund the pot. The covenant enforces this: any terminal branch that reduces a bond below its declared amount is rejected.
How Kaspa Forge Uses This State Machine
Arena Blackjack runs this exact FSM on Kaspa mainnet as a public mainnet beta. The Arena surface inside Desk lets players browse open Rooms, verify the covenant program, and join with locally signed transactions. Keys never leave the player's device.
In HOSTED mode, Kaspa Forge produces the ZK shuffle proof on the player's behalf; the proof fee is currently zero under fee policy V3 — the house absorbs it. In LOCAL mode, the dealer proves independently. The service fee (0.9% of the two-stake bank) appears as a separate exact output only after a decided outcome; timeouts produce zero fee outputs because no service was rendered.
The system enforces hard economic boundaries: a maximum of 6 active rooms, a 1,300 KAS total locked exposure cap on the Blackjack controller, and per-room fee reserves calculated from live network conditions. Every Room, Game, and terminal transaction is independently verifiable on-chain — the covenant program is open source, and the FSM has been tested through a negative matrix of 696 hand-state cases and 110 timeout cases with zero false accepts.
Arena Blackjack is live on Kaspa mainnet as a public beta. You can browse open Rooms, verify the covenant code, and join a game from the Arena tab in Desk. All game logic runs on-chain; your keys stay on your device.
Trade-offs and Honest Boundaries
This architecture has real constraints worth understanding:
Session secrets are local. Dealer seeds are encrypted with the device key and never leave the player's machine. If the device is lost without an encrypted export, the seed is unrecoverable. The dealer bond exists precisely to reflect this operational risk — it compensates the player if the dealer vanishes.
Reorgs require projection rollback. Kaspa's GHOSTDAG protocol allows small, frequent reorgs in the wide DAG. The game projection rolls back to the last canonical Game UTXO on reorg; conflicting transactions are preserved locally for rebroadcast but never rewrite on-chain state. The UI treats a transition as final only after a configured confirmation depth.
Timeouts are the safety net, not the happy path. Every dead-end in the FSM — a vanishing player, a withholding dealer, a failed proof — has a timeout branch that resolves the pot. But timeouts take time (player action timeout is ~2 minutes, room timeout ~24 hours), and during that window the funds are locked.
Capacity is deliberately limited. Six active rooms and a 1,300 KAS exposure cap are not engineering limitations — they are risk controls for a beta system. The capital policy tracks confirmed ordinary UTXO, reconciled collateral, and pending reservations to prevent double-counting funds across games.
What is not this product. Dice has completed significant engineering work but its permanent production arm is off pending independent review. Duel and baccarat are roadmap ideas, not products. Permissionless Tokens is planned and not live. None of these should be confused with the Blackjack FSM described above, which is running on mainnet today.
The core insight is simple: on Kaspa, a UTXO *is* a state, a transaction *is* a transition, and a covenant script *is* the referee. Blackjack is the first game built on that foundation. The state machine doesn't trust either party — it trusts the math.
FAQ
What is a Kaspa UTXO game?
A Kaspa UTXO game encodes every round of play as a series of UTXO state transitions enforced by covenant scripts. Each game state is a spendable output whose redemption rules define the only valid next move — no server, no custodian, no trust.
How does the Room UTXO protect both players?
The Room locks the dealer's stake, bond, and a fee reserve in a covenant with exactly two branches: a valid JOIN from a matching player, or a ROOM_TIMEOUT that returns funds to the dealer. Neither party can unilaterally drain the pot.
What happens if a player disappears mid-game?
The covenant includes a PLAYER_ACTION_TIMEOUT branch. After the DAA deadline expires, anyone can broadcast a permissionless transaction that auto-stands the player's hand, advancing the game to the dealer's turn without the absent player's signature.
How are blackjack payouts enforced on-chain?
Terminal branches compute the outcome from both hands directly in script, then pin exact output amounts and addresses. The covenant rejects any transaction that underpays a winner, overpays fees, or adds foreign outputs — the math is the contract.
Is Arena Blackjack live?
Arena Blackjack is a public mainnet beta. Dice engineering work has its permanent production arm off pending review. Duel and baccarat are roadmap ideas, not products.
Where are my keys during a game?
Keys remain on the player's device at all times. The player signs game transactions locally; Kaspa Forge never holds private keys or custodial funds.
Put your KAS where theft can be cancelled
A covenant vault on Kaspa mainnet: your keys, your rules, our tooling. Free on-chain, forever.
Create a vault