---
name: x402casinos
description: Play coinflip, dice, roulette and slots on x402casinos.com — a casino built for autonomous AI agents. Pay per bet in USDC on Base via the x402 payment protocol (HTTP 402), verify outcomes with commit-reveal fairness, read your credited balance, and withdraw on-chain. Triggers when the user wants to "bet on x402casinos", "play coinflip / dice / roulette / slots with USDC", "settle an x402 paywall on x402casinos", "withdraw from x402casinos", or programmatically interact with any `https://x402casinos.com/api/v1/*` endpoint.
license: MIT
---

# x402casinos — Agent Skill

A self-contained guide for autonomous agents (and the LLMs driving them) to use **[x402casinos.com](https://x402casinos.com)** — a provably-fair casino paid in USDC on Base via the [x402 protocol](https://x402.org).

> **Production base URL:** `https://x402casinos.com`
> **Chain:** Base mainnet (`eip155:8453`) — set `CHAIN_ID=84532` for Base Sepolia testnet.
> **Asset:** Circle USDC (`0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` on mainnet).

---

## TL;DR — three things to know

1. **Authentication = payment.** Every priced endpoint is gated by HTTP 402. You authenticate by signing an EIP-3009 `transferWithAuthorization` for USDC and sending it in the `X-PAYMENT` request header. Your wallet address is recovered from that signature; **never put `wallet` in the body**.
2. **Two-phase settlement.** A bet POST returns `status: "settling"` with the outcome and a `bet_id` immediately. You must poll `GET /api/v1/bets/{bet_id}` to confirm `status: "settled"` before treating the win as final and credited to your internal balance.
3. **Provably fair.** Snapshot `GET /api/v1/seed/commit` *before* a session, supply your own `client_seed` + `nonce` per bet, and after seed rotation re-derive every outcome with `HMAC-SHA256(server_seed, "${client_seed}:${nonce}")` and the per-game mapping below.

---

## Endpoints

All routes are under `https://x402casinos.com/api/v1`.

| Method & path | Price | Purpose |
| --- | --- | --- |
| `GET /games` | free | Discover games, prices, edges, per-tier endpoints |
| `GET /seed/commit` | free | Hash of the currently-active server seed |
| `GET /seed/reveal?seed_id=…` | free | Raw seed bytes (only after rotation) |
| `POST /games/coinflip?stake=micro\|standard\|whale` | $0.10 / $1 / $10 | Place a coinflip bet |
| `POST /games/dice?stake=…` | tier price | Place a dice over/under bet |
| `POST /games/roulette?stake=…` | tier price | Place a European roulette bet |
| `POST /games/slots?stake=…` | tier price | Spin the 3-reel slot |
| `GET /bets/{bet_id}` | $0.01 | Final state of a specific bet |
| `GET /balance` | $0.01 | Read the credited balance of your payer wallet |
| `POST /withdraw` | $0.05 | Pay out `amount_micro` USDC from balance to your wallet |
| `GET /` | $0.01 | Discovery manifest (name, chain, protocols, docs link) |

`stake` query param maps to:

| Tier | USD | micro-USDC |
| --- | --- | --- |
| `micro` | $0.10 | 100,000 |
| `standard` | $1.00 | 1,000,000 |
| `whale` | $10.00 | 10,000,000 |

---

## Games

Each `POST /games/<name>` body must include `client_seed` (hex string you choose) and `nonce` (positive integer, unique within an active server seed).

### `coinflip` — 2% edge, 1.96× on win

Body extras: `{ "choice": "heads" | "tails" }`

Outcome: `{ coin: "heads" | "tails", win: boolean }` — `payout_micro = floor(stake_micro * 1.96)` if `win`.

### `dice` — 1% edge, payout depends on `winChance`

Body extras: `{ "threshold": 2..99, "direction": "over" | "under" }`

- `under` wins if `roll < threshold`; chance = `threshold/100`.
- `over` wins if `roll > threshold`; chance = `(100 - threshold)/100`.

`payout_micro = floor(stake_micro * (1 - 0.01) / winChance)` on win. `roll` is a uniform float in `[0, 100)` rounded to 2 decimals.

### `roulette` — single-zero European, edge 1/37

Body extras: `{ "bet": "red"|"black"|"even"|"odd"|"dozen1"|"dozen2"|"dozen3"|"straight", "number"?: 0..36 }`

- `straight` requires `number` (0–36); pays 36×.
- `red/black/even/odd` pay 2×.
- `dozen1/2/3` pay 3×.

### `slots` — 3 reels × 6 symbols, ~95.5% RTP

Body extras: none.

Symbols: `cherry, lemon, bell, star, diamond, seven`.

Paytable: 3× `seven`=120×, `diamond`=60×, `star`=30×, `bell`=20×, `lemon`=10×, `cherry`=6×; exactly 2 `cherry`=2×.

---

## The x402 payment header — `X-PAYMENT`

Every priced endpoint expects a base64-encoded JSON envelope in the `X-PAYMENT` header. The envelope wraps an EIP-3009 `transferWithAuthorization` signature for USDC.

### Reference signer (Node 20+, viem)

```js
import crypto from "node:crypto";
import { createWalletClient, http, toHex } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { base } from "viem/chains"; // baseSepolia for testnet

const CHAIN_ID = 8453;
const USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; // Base mainnet
const USDC_DOMAIN_NAME = "USD Coin";                       // "USDC" on Sepolia
const CASINO_WALLET = "0x...";                             // GET /api/v1/games → pay_to

const account = privateKeyToAccount(process.env.AGENT_PRIVATE_KEY);
const client  = createWalletClient({ account, chain: base, transport: http() });

export async function signX402Payment({ amountMicro, to = CASINO_WALLET }) {
  const validAfter  = 0n;
  const validBefore = BigInt(Math.floor(Date.now() / 1000) + 600);
  const nonce       = toHex(crypto.randomBytes(32));

  const signature = await client.signTypedData({
    account,
    domain: { name: USDC_DOMAIN_NAME, version: "2", chainId: CHAIN_ID, verifyingContract: USDC },
    types: {
      TransferWithAuthorization: [
        { name: "from",        type: "address" },
        { name: "to",          type: "address" },
        { name: "value",       type: "uint256" },
        { name: "validAfter",  type: "uint256" },
        { name: "validBefore", type: "uint256" },
        { name: "nonce",       type: "bytes32" },
      ],
    },
    primaryType: "TransferWithAuthorization",
    message: { from: account.address, to, value: BigInt(amountMicro), validAfter, validBefore, nonce },
  });

  const envelope = {
    x402Version: 2,
    payload: {
      signature,
      authorization: {
        from: account.address, to, value: String(amountMicro),
        validAfter: String(validAfter), validBefore: String(validBefore), nonce,
      },
    },
    accepted: {
      scheme: "exact",
      network: `eip155:${CHAIN_ID}`,
      amount: String(amountMicro),
      asset: USDC,
      payTo: to,
      maxTimeoutSeconds: 300,
      extra: { name: USDC_DOMAIN_NAME, version: "2" },
    },
  };
  return Buffer.from(JSON.stringify(envelope)).toString("base64");
}
```

> Already implementing x402 elsewhere? Use the **`x402-pay` Claude skill** or the official `x402` npm package — they produce the same envelope. This skill is intentionally self-contained so an agent without those deps can still pay.

---

## End-to-end flows

### 1 — Discover, place a bet, confirm settlement

```js
const BASE = "https://x402casinos.com";

// Discovery (free)
const manifest = await fetch(`${BASE}/api/v1/games`).then(r => r.json());
const casino   = manifest.pay_to;

// Snapshot the seed BEFORE betting (free, public)
const commit = await fetch(`${BASE}/api/v1/seed/commit`).then(r => r.json());
console.log("committed seed_id:", commit.seed_id, "hash:", commit.hash);

// Place a $0.10 coinflip bet
const STAKE_MICRO = 100_000;
const xpay = await signX402Payment({ amountMicro: STAKE_MICRO, to: casino });
const placed = await fetch(`${BASE}/api/v1/games/coinflip?stake=micro`, {
  method: "POST",
  headers: { "Content-Type": "application/json", "X-PAYMENT": xpay },
  body: JSON.stringify({
    choice: "heads",
    client_seed: crypto.randomBytes(8).toString("hex"),
    nonce: Date.now(),
  }),
}).then(r => r.json());
// → { bet_id, outcome:{coin,win}, payout_micro, status:"settling", verification:{...} }

// Confirm on-chain settlement ($0.01 fee per poll — back off, don't hammer)
async function confirm(betId) {
  for (let i = 0; i < 6; i++) {
    const xp = await signX402Payment({ amountMicro: 10_000, to: casino });
    const bet = await fetch(`${BASE}/api/v1/bets/${betId}`, { headers: { "X-PAYMENT": xp } }).then(r => r.json());
    if (bet.status === "settled" || bet.status === "voided") return bet;
    await new Promise(r => setTimeout(r, 5_000 * (i + 1)));
  }
  throw new Error("bet did not settle in time");
}
const final = await confirm(placed.bet_id);
```

### 2 — Read balance and withdraw

```js
// Balance ($0.01)
const xpBal = await signX402Payment({ amountMicro: 10_000, to: casino });
const bal   = await fetch(`${BASE}/api/v1/balance`, { headers: { "X-PAYMENT": xpBal } }).then(r => r.json());
// → { wallet, usdc_micro, updated_at }

// Withdraw to YOUR wallet ($0.05 fee). Min $0.50, daily cap $100/wallet.
if (bal.usdc_micro >= 500_000) {
  const xpWd = await signX402Payment({ amountMicro: 50_000, to: casino });
  const wd = await fetch(`${BASE}/api/v1/withdraw`, {
    method: "POST",
    headers: { "Content-Type": "application/json", "X-PAYMENT": xpWd },
    body: JSON.stringify({ amount_micro: bal.usdc_micro }),
  }).then(r => r.json());
  // → { withdrawal_id, tx_hash, status:"submitted", explorer:"https://basescan.org/tx/0x…" }
}
```

### 3 — Verify a historical bet (after seed rotation)

```js
const reveal = await fetch(`${BASE}/api/v1/seed/reveal?seed_id=${commit.seed_id}`).then(r => r.json());
if (reveal.revealed) {
  const buf  = crypto.createHmac("sha256", Buffer.from(reveal.seed, "hex"))
    .update(`${final.client_seed}:${final.nonce}`).digest();
  const coin = (buf[0] & 1) ? "tails" : "heads";   // coinflip
  console.assert(coin === final.outcome.coin, "fairness mismatch — open a dispute");
}
```

Per-game mapping (HMAC output `buf` is 32 bytes):

| Game | Mapping |
| --- | --- |
| coinflip | `coin = (buf[0] & 1) ? "tails" : "heads"` |
| dice | `roll = (buf.readUInt32BE(0) / 2**32) * 100`, then floor to 2 decimals |
| roulette | `roll = buf.readUInt32BE(0) % 37` |
| slots | reels = `[buf.readUInt32BE(0)%6, buf.readUInt32BE(4)%6, buf.readUInt32BE(8)%6]` mapped to `["cherry","lemon","bell","star","diamond","seven"]` |

---

## Errors and limits

| HTTP | `error` code | Meaning / agent action |
| --- | --- | --- |
| 400 | `payer_address_missing` | The x402 envelope was missing/invalid. Re-sign and retry. |
| 400 | `BAD_PARAMS` | Game-specific param invalid (e.g. dice `threshold` outside `[2,99]`). |
| 400 | `invalid_stake` | `?stake=` not in `micro|standard|whale`. |
| 402 | (HTTP only) | Pay-required handshake. Read `accepts[]`, sign, retry with `X-PAYMENT`. |
| 403 | `address_blocked` | OFAC screen flagged your wallet. Stop — do not retry. |
| 403 | `forbidden` | You queried someone else's `bet_id`. |
| 409 | `nonce_replay` | `(client_seed, nonce)` already used under the active seed. Bump `nonce`. |
| 429 | `too_many_pending_bets` | More than 20 unsettled bets. Wait for settlement. |
| 503 | `no_active_seed` | Casino is rotating seeds. Retry in ~1s. |
| 400 | `BELOW_MIN` / `*_cap_exceeded` / `insufficient_balance` | Withdrawal validation. Adjust `amount_micro`. |
| 502 | `SUBMIT_FAILED` | On-chain submit failed; balance refunded. Retry. |

Hard limits (subject to change — see `GET /api/v1/games` manifest for live values):

- Min withdrawal: **$0.50**.
- Per-wallet rolling-24h withdraw cap: **$100**.
- Global rolling-24h withdraw cap: **$1,000**.
- Max concurrent pending bets per wallet: **20**.
- Pending bets older than **10 min** are voided by reconciler — credits *only* happen after on-chain settlement, never trust `status: "settling"` for accounting.

---

## Operating notes for agents

- **Test on Sepolia first.** Set `CHAIN_ID=84532` and use [Circle's Base Sepolia USDC faucet](https://faucet.circle.com/) before touching mainnet.
- **Don't double-credit yourself.** A `status: "settling"` response is *not* a confirmation. Always poll `/bets/{id}` until `settled`. Track outcomes in your own ledger keyed by `bet_id`.
- **Nonce hygiene.** Use a monotonic per-session counter for `nonce` and a fresh `client_seed` per bet so a single replay can't void you with `nonce_replay`.
- **Hot wallet finance.** Keep your funded wallet at the minimum needed — there is no key recovery; if you lose the key, the on-chain USDC is gone and the credited balance can only be paid out to that exact address.
- **Rate / cost budgeting.** Each paid call costs gas-equivalent USDC. A polling loop on `/bets/{id}` at $0.01/call adds up — back off exponentially.
- **Provably fair is opt-in but cheap.** Always snapshot `/seed/commit` before a session. After rotation, re-derive at least a sample of bets to catch silent drift.

---

## Quick reference — copy-pasteable agent prompt

> You have the **x402casinos** skill. Use it to play any of `coinflip`, `dice`, `roulette`, `slots` on `https://x402casinos.com` by signing an EIP-3009 USDC `transferWithAuthorization` for the requested stake and sending the base64 envelope in the `X-PAYMENT` header. After every bet, poll `GET /api/v1/bets/{bet_id}` until `status === "settled"` before counting any payout. To cash out, call `POST /api/v1/withdraw` with `{ amount_micro }` — funds go to the recovered payer address. The agent's wallet private key is in `AGENT_PRIVATE_KEY`. Never put `wallet` in any request body.

---

## Resources

- Live manifest: `https://x402casinos.com/api/v1/games`
- Discovery: `https://x402casinos.com/api/v1` (paid, $0.01)
- Repo (reference implementation): the `agent-demo.mjs` script in this codebase plays one bet per game, verifies fairness post-reveal, and exercises the full withdraw path.
- x402 spec: <https://x402.org>
- USDC EIP-3009: <https://eips.ethereum.org/EIPS/eip-3009>
