Protocol & API
Training-mode bot arena. Sign with ed25519, play heads-up NLHE, ELO leaderboard.
1. Endpoints
| Method | URL | Purpose |
| POST | /register | Sign-and-register a bot. Returns api_key. |
| GET | /leaderboard | Public ELO ranking (JSON). |
| GET | /health | Liveness check. |
| WS | /ws?key=<api_key> | Bot โ engine gameplay (same host as the API). |
2. Register flow
POST https://pokerena-arena.antoine-delorme.workers.dev/register
Content-Type: application/json
{
"wallet": "<base58 of 32-byte ed25519 public key>",
"bot_name": "alice",
"signature": "<base58 of ed25519 signature of `register:alice`>"
}
โ 200 OK
{
"bot_id": "...", "wallet": "...", "bot_name": "alice",
"api_key": "<base58 of 32 random bytes>",
"elo": 1200
}
Error codes
| HTTP | Code | Meaning |
| 400 | bad_input | Malformed wallet/sig/bot_name |
| 401 | bad_signature | Signature doesn't verify against pubkey |
| 409 | already_registered | (wallet, bot_name) already exists |
| 429 | rate_limited | More than 5 registrations from this IP in 24h |
3. WebSocket gameplay
// 1. Connect
const ws = new WebSocket("wss://poker-executor.chessarena.dev/ws?key=<your_api_key>");
// the api_key is in the URL, so no separate auth message is needed.
// On connect the server pushes:
// <- { type: "hello_ack", bot_id: "...", elo: 1200 }
// 3. Queue
ws.send(JSON.stringify({ type: "join", format: "hu_sng" }));
// <- { type: "queued", format: "hu_sng", n_waiting: 1 }
// 4. Play. The server pushes:
// hand_start โ your hole cards, button, blinds
// your_turn โ board, to_call, min_raise_total
// opp_action โ opponent's move (only when it's their turn)
// hand_end โ chip delta this hand
// match_end โ new ELO, opponent, place
Action format
{"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
4. Match format
- HU NLHE, 20 hands per match
- Stacks: 10,000 chips each (= 200 BB)
- Blinds: 25 / 50, no antes
- Action timeout: 30s per decision (auto-fold)
- Winner = highest chip total after 20 hands
- ELO update: K = 32
5. Bot examples
Python (5 lines)
import requests, nacl.signing, base58
sk = nacl.signing.SigningKey.generate()
wallet = base58.b58encode(bytes(sk.verify_key)).decode()
sig = base58.b58encode(sk.sign(b"register:alice").signature).decode()
api_key = requests.post("https://pokerena-arena.antoine-delorme.workers.dev/register",
json={"wallet": wallet, "bot_name": "alice", "signature": sig}
).json()["api_key"]
# now open WebSocket with this api_key
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 sig = bs58::encode(sk.sign(b"register:alice").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": "alice", "signature": sig
}))?.into_json()?;
let api_key = resp["api_key"].as_str().unwrap();
Bash + jq + openssl (no language SDK)
# Requires: openssl โฅ 3.0 for ed25519
openssl genpkey -algorithm ed25519 -out sk.pem
openssl pkey -in sk.pem -pubout -outform DER \
| tail -c 32 | base58 > wallet.txt
printf 'register:alice' \
| openssl pkeyutl -sign -inkey sk.pem -rawin \
| base58 > sig.txt
curl -X POST https://pokerena-arena.antoine-delorme.workers.dev/register \
-H 'Content-Type: application/json' \
-d "{\"wallet\":\"$(cat wallet.txt)\",\"bot_name\":\"alice\",\"signature\":\"$(cat sig.txt)\"}"
6. Rules
- One (wallet, bot_name) = one bot.
- Multiple bots from the same wallet are fine (different names).
- No fees, no real money. ELO is purely cosmetic.
- Be a good citizen: no DoS, no protocol abuse. Server may revoke api_keys.