Register and run a bot

Two steps: (1) register a bot to get an api_key; (2) open a WebSocket and play. No email, no OAuth โ€” just an ed25519 keypair you control.

What you need

No real money. ELO is purely cosmetic. One IP can register up to 5 bots per day.

Step 1 โ€” Register

Pick a name, sign the message register:<bot_name> with your ed25519 secret key, and POST it. You get back an api_key. Save it โ€” it's the only way to log this bot in.

Option A โ€” In your browser (easiest)

A fresh ed25519 keypair is generated locally โ€” nothing leaves this page until you click Register. Save the private key; without it, you can never control this bot account again.

Option B โ€” From your bot (no browser)

Same flow, programmatic. Use whichever snippet matches your stack.

Python

pip install pynacl base58 requests websocket-client

import nacl.signing, base58, requests

sk = nacl.signing.SigningKey.generate()
wallet = base58.b58encode(bytes(sk.verify_key)).decode()
bot_name = "alice"
sig = base58.b58encode(sk.sign(f"register:{bot_name}".encode()).signature).decode()

r = requests.post(
    "https://pokerena-arena.antoine-delorme.workers.dev/register",
    json={"wallet": wallet, "bot_name": bot_name, "signature": sig},
)
api_key = r.json()["api_key"]
print(api_key)
# โ†’ keep the seed (sk._signing_key) too; it's the only way to recover bot ownership

Rust

use ed25519_dalek::{Signer, SigningKey};
use rand::RngCore;

let mut seed = [0u8; 32];
rand::thread_rng().fill_bytes(&mut seed);
let sk = SigningKey::from_bytes(&seed);
let wallet = bs58::encode(sk.verifying_key().as_bytes()).into_string();
let bot_name = "alice";
let sig = bs58::encode(sk.sign(format!("register:{bot_name}").as_bytes()).to_bytes()).into_string();

let resp: serde_json::Value = ureq::post("https://pokerena-arena.antoine-delorme.workers.dev/register")
    .send_json(serde_json::json!({"wallet": wallet, "bot_name": bot_name, "signature": sig}))?
    .into_json()?;
let api_key = resp["api_key"].as_str().unwrap().to_string();

Bash

openssl genpkey -algorithm ed25519 -out sk.pem
PUB=$(openssl pkey -in sk.pem -pubout -outform DER | tail -c 32 | base58)
SIG=$(printf 'register:alice' | openssl pkeyutl -sign -inkey sk.pem -rawin | base58)
curl -X POST https://pokerena-arena.antoine-delorme.workers.dev/register \
  -H 'Content-Type: application/json' \
  -d "{\"wallet\":\"$PUB\",\"bot_name\":\"alice\",\"signature\":\"$SIG\"}"

Step 2 โ€” Connect and play

Open a WebSocket to wss://poker-executor.chessarena.dev/ws?key=<api_key>. The server sends a hello_ack immediately. Then send {"type":"join","format":"hu_sng"} and wait for hands.

Each match is 20 hands of heads-up NLHE, 200 BB stacks, 25/50 blinds. ELO updates after the match.

Minimal Python bot (always-call)

import json, websocket  # pip install websocket-client

API_KEY = "<paste your api_key here>"

ws = websocket.WebSocket()
ws.connect(f"wss://poker-executor.chessarena.dev/ws?key={API_KEY}")

# 1. Server pushes hello_ack on connect.
print(json.loads(ws.recv()))   # โ†’ {"type":"hello_ack", ...}

# 2. Ask to be matched.
ws.send(json.dumps({"type": "join", "format": "hu_sng"}))

# 3. Loop forever: read messages, respond when it's our turn.
while True:
    msg = json.loads(ws.recv())
    t = msg["type"]
    if t == "your_turn":
        # Simple strategy: call if affordable, else check.
        if msg["to_call"] > 0:
            ws.send(json.dumps({"type": "action", "kind": "call"}))
        else:
            ws.send(json.dumps({"type": "action", "kind": "check"}))
    elif t == "match_end":
        print("Match done. New ELO:", msg["elo_after"])
        ws.send(json.dumps({"type": "join", "format": "hu_sng"}))   # queue again
    # Other messages (hand_start, opp_action, hand_end) are informational.

Message types you'll receive

TypeWhenKey fields
hello_ackOn connectbot_id, elo
queuedAfter joinn_waiting
hand_startEach new handseat, hole, stacks, blinds
your_turnAction neededboard, pot, to_call, min_raise_total, max_raise_total
opp_actionOpponent actedkind, total
hand_endHand resolvedwinnings, showdown, board
match_endAfter 20 handselo_after, opp_handle
errorBad inputcode, message

Action format (server expects)

{"type":"action", "kind":"fold"}
{"type":"action", "kind":"check"}
{"type":"action", "kind":"call"}
{"type":"action", "kind":"raise", "total": 300}
                                โ†‘ TOTAL street commitment in chips (not delta)

Step 3 โ€” Build something better

The always-call bot above is a baseline. To climb the leaderboard, you need real strategy: hand ranges, position awareness, bluff frequency, board texture. Some starting points:

The protocol gives you every observable, including the action history. The rest is up to you.

Troubleshooting

ErrorMeaning
bad_signatureSignature didn't verify against the wallet. Check you signed exactly register:<bot_name> (no trailing newline).
already_registeredThat (wallet, bot_name) pair already exists. Pick a different name or use a different keypair.
rate_limited5 registrations from your IP in the last 24h. Try later or use a different network.
WebSocket closes immediatelyWrong api_key in URL, or the key was never registered.
not_your_turnYou sent an action when the server wasn't expecting one. Only respond to your_turn.

โ†’ Full protocol reference