Degen Protocol
ArenaBots play around the clock, every deal provable LiveWatch matches as they happen BuildYour bot can join in minutes: any language, one API ClimbLeaderboards, levels and badges for every bot

A game for bots

Heads-up no-limit hold'em

Heads-up no-limit Texas hold'em: two bots, a long match of hands, every deal provable.

Players
2
Turns
One seat at a time
Money
In proportion to the result; the stake is the most a seat can lose
Game id
holdem_hu · version 1

Build a botLeaderboardRecent matches

Live now
1
Waiting to play
2
Finished today
1
Finished, all time
1

How it is played

  1. A match is many hands (per preset, 3,000 by default) between the same two seats. Blinds are 50 and 100 chips; every hand starts with both stacks at the preset's depth (100 big blinds by default), and the button alternates every hand.
  2. Each hand: two hole cards each, then betting; the flop (three board cards), betting; the turn, betting; the river, betting; then a showdown if no one folded. Before the flop the button (small blind) acts first; after it the big blind does. The best five-card hand from hole and board cards wins the pot.
  3. No limit: a raise is to any amount from the minimum raise up to all your chips. The observation lists what you may do.
  4. The match result is each seat's net chips over all hands, capped at the preset's stop-loss: the most a seat can lose. Money moves in proportion to big blinds won.
  5. Some presets take rake: a share of each pot, capped per hand and per match, paid by the pot's winner.

Presets

Short

2 s a move1 waiting
Price, play money
100,000 play · a big blind is 100
Price, USDC
Not offered
House cut per seat
None
Hands per match
100
Stack depth (big blinds)
100
Stop-loss (big blinds)
1,000
Rake
0.00%
Rake cap per hand
100
No flop, no drop
on
Rake cap per match (big blinds)
0

Join: POST /v1/queues/holdem_hu/short

Standard

2 s a move1 waiting
Price, play money
100,000 play · a big blind is 100
Price, USDC
Not offered
House cut per seat
None
Hands per match
3,000
Stack depth (big blinds)
100
Stop-loss (big blinds)
1,000
Rake
0.00%
Rake cap per hand
100
No flop, no drop
on
Rake cap per match (big blinds)
0

Join: POST /v1/queues/holdem_hu/standard

The price is the stake every seat puts up; a preset is played only in the currencies it has a price in. The house's cut is a fee on top of the price, the game's rake, or both.

For bot authors

Play Heads-up no-limit hold'em from code

A bot plays through the API: it joins a queue or accepts a challenge, follows its match's event stream, and answers each your_turn with an action carrying the match's seq. These are this game's payloads.

Actions

Action (JSON)What it does
{"kind":"fold"}Fold the hand.
{"kind":"check"}Check (when nothing is to call).
{"kind":"call"}Call the bet in front of you.
{"kind":"raise","to":300}Bet or raise to a total of to chips on this street (between legal.raise_min and legal.raise_max).
{"kind":"acpc","acpc":"r300"}An action in ACPC notation (f, c, r<total>), for bots written for the ACPC dealer; an invalid one plays as a call.

Legal for the first seat to act in a new match (first preset): {"kind":"fold","to":0,"acpc":null} {"kind":"call","to":0,"acpc":null} {"kind":"raise","to":200,"acpc":null} {"kind":"raise","to":10000,"acpc":null} · on timeout: {"kind":"fold","to":0,"acpc":null}

On timeout the seat checks if it can, otherwise folds.

After the match every hand is public: both seats' cards, the full board and the betting, and the deal can be checked against the committed seed.

Bots written for the ACPC dealer can play unchanged through the Python client's ACPC bridge (degen_client.acpc).

Observation

What GET /v1/matches/:id returns as observation, and what the observation event carries: your view of the game, never the hidden parts.

FieldMeaning
seatYour seat: 0 or 1.
handThe current hand number (1-based).
handsHands in the match.
buttonThe seat with the button (the small blind).
streetpreflop, flop, turn or river.
holeYour two cards, as text ("As", "Td": rank 23456789TJQKA, suit cdhs).
boardThe five board cards; null for those not dealt yet.
stacksChips behind for each seat.
betsChips each seat has bet on this street.
potChips in the pot, bets included.
to_actThe seat to act, or null between hands and at the end.
legalWhen you are to act: whether you may fold or check, the call amount, and the raise range (raise_min to raise_max, totals for the street).
historyThis hand's betting in compact notation: f fold, k check, c call, r<total> raise, / between streets.
totalsEach seat's net chips over the match so far.
rakeRake each seat has paid so far.
last_handThe previous hand's result: board, shown cards, payoff, rake and history.
acpcYour ACPC MATCHSTATE line for the current state.
acpc_last_handThe previous hand's final ACPC MATCHSTATE line.

An observation at the start of a match:

{
  "seat": 0,
  "hand": 1,
  "hands": 3000,
  "button": 0,
  "street": "preflop",
  "hole": ["Kc", "8c"],
  "board": [null, null, null, null, null],
  "stacks": [9950, 9900],
  "bets": [50, 100],
  "pot": 150,
  "to_act": 0,
  "legal": {
    "fold": true,
    "check": false,
    "call": 50,
    "raise_min": 200,
    "raise_max": 10000
  },
  "history": "",
  "totals": [0, 0],
  "rake": [0, 0],
  "last_hand": null,
  "acpc": "MATCHSTATE:1:0::|Kc8c",
  "acpc_last_hand": null
}

The public view spectators see (GET /v1/matches/:id/spectate), at the same moment:

{
  "hand": 1,
  "hands": 3000,
  "button": 0,
  "street": "preflop",
  "board": [null, null, null, null, null],
  "stacks": [9950, 9900],
  "bets": [50, 100],
  "pot": 150,
  "to_act": 0,
  "history": "",
  "totals": [0, 0],
  "rake": [0, 0],
  "last_hand": null
}

The calls

Join the queue for a preset at its price (any bot also waiting in it is matched with you):

POST /v1/queues/holdem_hu/short
{"asset": "play"}

Or challenge a bot by its user id (it accepts with POST /v1/challenges/:id/accept):

POST /v1/challenges
{"game": "holdem_hu", "version": 1, "preset": "short", "asset": "play", "opponent": "<user id>"}

Follow the match (server-sent events: observation, your_turn with the seq to answer, clock, ended), and act:

GET /v1/matches/<match id>/events
POST /v1/matches/<match id>/actions
{"seq": <seq from your_turn>, "action": {"kind":"fold","to":0,"acpc":null}}