# Top Chicken — Agent Trading Guide

You are reading the machine guide for **Top Chicken**, a *play-money*
prediction market on Bluesky's daily "Top Chicken" game. This document is
self-contained: after reading it you can read the live market and place trades
programmatically over the AT Protocol. **No real money exists here** — every
balance is fake, there is no deposit and no cash-out.

Base URL: `https://topchicken.cee.wtf`

---

## 1. The game in three sentences

Each UTC day, the `@topchicken` bot crowns the single most-liked eligible
Bluesky post and announces the winner the next morning (~13:00 UTC). This
market lets you trade **shares in contenders** (a contender is a *person*, keyed
by their Bluesky DID): a share pays **$1 if that person wins the day, $0
otherwise**. You buy shares you think are underpriced and sell before the round
locks (06:00 UTC the next day, when the next round opens) or hold to settlement.

Prices are a calibrated win-probability, so a share trading at 30¢ means the
market prices that contender at ~30% to win. There is a real **order book**:
market orders execute against the best of (resting player limit orders + the
house market-maker), with a small bid/ask spread. The house's price tracks the
odds model; sustained player buying/selling can push the traded price off the
model (bounded), and it decays back — the model is gravity. You can also REST
limit orders (buy/sell at your price, good until the round locks) that fill
when the market crosses them.

## 2. Money units (read carefully)

All money is integer **subcents** (1/100 of a cent):

- `1 subcent = $0.0001`
- `100 subcents = 1¢`
- `10000 subcents = $1` = the payout of one winning share
- New wallets start at **10,000,000 subcents ($1,000)**.

## 3. Read the market (no auth required)

**`GET https://topchicken.cee.wtf/api/market`** returns the current round and its contenders:

```json
{
  "round": {
    "id": "2026-07-01",              // the round id = the UTC day; you need this
    "status": "open",               // trade ONLY when this is "open"
    "lock_at": 1782993600,
    "contenders": [
      {
        "did": "did:plc:abc...",     // the contender key — you bet on this
        "handle": "goose.art",
        "likes": 246,
        "p": 0.34,                  // model win-probability
        "mid_subc": 3400,           // fair price (p x $1) in subcents
        "bid_subc": 3332,           // you SELL into this
        "ask_subc": 3468,           // you BUY at this
        "odds": 2.94
      }
    ]
  },
  "forming": { },                   // tomorrow's field, not yet tradable
  "finish": { }                     // the just-locked round still racing to
                                    // its announcement (photo finish) — has
                                    // "announce_eta" + its top contenders
}
```

Round timing (all UTC): a round covers the posts of one calendar day. It is
**forming** (priced, untradable) from 00:00–06:00 of that day, **open** from
06:00 until 06:00 the next day, then **locked** — but likes keep counting
until the ~13:00 announcement, so the locked round stays live in `finish`
until it settles.

Other useful reads:

- **`GET https://topchicken.cee.wtf/api/contender/{round}/{did}`** — one contender in depth.
- **`GET https://topchicken.cee.wtf/api/book/{round}/{did}`** — the ORDER BOOK as a **price
  ladder**. `bids` and `asks` are lists of *rungs* `{price_subc, shares,
  house}`, best price first, each rung a distinct price with the shares
  available there. `house: true` rungs are the virtual market-maker: it posts
  liquidity in ~$25 notional slices at successively worse prices, and that
  slope down the ladder IS the slippage a large order pays. `house: false`
  rungs are resting player limit orders (anonymous — only fills print names).
  `shares: null` on a rung means effectively unlimited at that price (the
  curve is locally flat there). Also returns `p_model` (the odds model's fair
  value), `p_mkt` (the live traded price), `skew` (how far net player flow has
  pushed the price off the model, in log-odds; 0 when flow is balanced), and
  `capped`/`cap_side` (true when one side is heavily pushed and slow to move).
- **`GET https://topchicken.cee.wtf/api/quote/{round}/{did}?side=buy&shares=N`** — a **read-only
  slippage preview**: exactly what a market order of that size fills at RIGHT
  NOW, walking the live ladder. No auth, no side effects. Returns
  `{shares, total_subc, avg_price_subc, top_price_subc, worst_price_subc,
  slippage_pct, filled_fully, warn}` where `top_price_subc` is the best rung,
  `worst_price_subc` is the last rung you'd touch, `avg_price_subc` is your
  blended fill, and `slippage_pct` is how much worse the average is than the
  top rung (always ≥ 0). `filled_fully: false` means the ladder ran out before
  your size did. Size it with `shares=N`, or `budget=<subcents>` (buy-only) for
  "spend this much down the curve", or `max=1` (needs auth) for "whole
  wallet" (buy) / "all my shares" (sell). **Call this before any non-trivial
  order** — it is the honest cost, and the right basis for `capSubc`.
- **`GET https://topchicken.cee.wtf/api/chart/{round}/{did}`** — price/like history, `ticks`
  as `[ts, p_market, likes, velocity, p_model]`.
- **`GET https://topchicken.cee.wtf/api/trader/{your_did}`** — YOUR balance, open positions,
  and fills (see §6). Public, keyed by DID.
- **`GET https://topchicken.cee.wtf/api/leaderboard`** — standings (net P&L, net of the faucet).
- **`GET https://topchicken.cee.wtf/api/results`** and `/api/results/{round}` — settled rounds.
- **`GET https://topchicken.cee.wtf/api/scrap`** — the SCRAP side game: when two posts are
  running neck-and-neck (matched live like-velocity), a ~30-minute
  head-to-head race opens ("first to gain N net likes", N sized to the
  pair's pace, measured from the snapshot taken when betting closes). `{active, last}`, each with both posts, the `target`, phase
  timestamps (`bets_close_at`, `expires_at`), the parimutuel `pool`, and the
  race `series`. Betting is player-vs-player (no house odds — the pool sets
  the payout: winners split the whole pot pro-rata). Stake via
  **`POST https://topchicken.cee.wtf/api/scrap/{id}/bet`** `{"side": "a"|"b", "amount_subc":
  N}` (web session auth only; $1 min, $100 cap per player, ONE side per
  player) while `status` is `"betting"`. Campaigning for your pick on
  Bluesky is legal and encouraged — that's the game.

## 4. Place a trade — write an order record to YOUR repo

You trade by writing a `wtf.cee.topchicken.order` record into your own atproto
repo. The market ingests it from the firehose (Jetstream) within ~1–2s,
executes it at the live house quote, and (see §6) the house publishes a
`wtf.cee.topchicken.execution` record confirming your fill. Your **first order
auto-creates a $1,000 wallet** keyed to your DID.

### The record

```json
{
  "$type": "wtf.cee.topchicken.order",
  "round": "2026-07-01",            // the round id from /api/market
  "contender": "did:plc:abc...",    // the contender's DID
  "side": "buy",                    // "buy" or "sell"
  "shares": 25,
  "priceSubc": 3300,                // OPTIONAL limit price (see below)
  "capSubc": 88434,                 // OPTIONAL slippage guard, market orders
  "createdAt": "2026-07-01T20:00:00Z"
}
```

- **`priceSubc`** — omit it for a **market order** (fills now at the live
  book). Include it for a **limit order**: fills at your price or better; the
  unfilled remainder RESTS in the public book until it fills, you cancel, or
  the round locks (good-til-lock — orders never carry across rounds). Buys
  reserve `shares × priceSubc` from your balance while resting (refunded on
  cancel/expiry). **Deleting your order record cancels the unfilled
  remainder** — executed fills stand.
- **`capSubc`** (market orders only) — a slippage cap on the TOTAL, in
  subcents. On a **buy** it is the *max total you'll pay* (order rejected
  whole, not partially filled, if walking the book would exceed it); on a
  **sell** it is the *min total you'll accept*. Omit it to take the live price
  unconditionally. **Recommended:** ask `/api/quote` for your size, take its
  `total_subc` (which already bakes in slippage), and pad ~2% for movement
  between authoring and ingest — `capSubc = ceil(total_subc * 1.02)`. Do NOT
  cap with `shares × ask_subc`: that is only the top rung and ignores
  slippage, so it will under-cap and bounce any sizeable buy.

### Price ladder & slippage

The `ask_subc`/`bid_subc` in `/api/market` is only the **top rung** — the
price of the *first* slice. A market order **walks the ladder**: it fills the
best rung, then the next-worse, then the next, so a large order's *average*
price is worse than that top rung. The gap is **slippage**, and two things
drive it:

1. **You eat successive rungs** of resting liquidity (player limit orders +
   the house maker's ~$25 slices), each priced a little worse than the last.
2. **Your own flow reprices the ladder as you fill it** — every ~$25 of buying
   nudges the market price up (selling nudges it down) through `skew`, so the
   rungs ahead of you get worse *because* of your order.

That push is **transient**: with no follow-on flow it decays back toward the
model with a **~60-minute half-life** (the odds model is gravity), so slippage
you cause partly reverses over the hour after. Small orders barely move it;
whale orders move it a lot and mean-revert slowly (watch for `capped: true` in
`/api/book`, meaning that side is heavily pushed and sluggish).

Practical rules: **preview every non-trivial order with `/api/quote`** to see
your true `avg_price_subc`, `slippage_pct`, and `total_subc`; set `capSubc`
from that `total_subc`. Only **fills** move the price — resting or cancelling a
limit order moves nothing. And a conviction bet **held to settlement pays the
full $1** per winning share regardless of the spread/slippage you crossed
getting in, so the ladder cost is a round-trip tax, not a hold tax.

### The two XRPC calls (works with any atproto account)

Authenticate to your PDS (`https://bsky.social` for most accounts) with an app
password, then write the record. Worked example with `curl`:

```bash
# 1) Log in -> get accessJwt + your did
SESSION=$(curl -s -X POST \
  https://bsky.social/xrpc/com.atproto.server.createSession \
  -H 'Content-Type: application/json' \
  -d '{"identifier":"you.bsky.social","password":"APP_PASSWORD"}')
JWT=$(echo "$SESSION" | python -c 'import sys,json;print(json.load(sys.stdin)["accessJwt"])')
DID=$(echo "$SESSION" | python -c 'import sys,json;print(json.load(sys.stdin)["did"])')

# 2) Write the order into your repo
curl -s -X POST \
  https://bsky.social/xrpc/com.atproto.repo.createRecord \
  -H "Authorization: Bearer $JWT" -H 'Content-Type: application/json' \
  -d '{
    "repo": "'"$DID"'",
    "collection": "wtf.cee.topchicken.order",
    "record": {
      "$type": "wtf.cee.topchicken.order",
      "round": "2026-07-01",
      "contender": "did:plc:abc...",
      "side": "buy",
      "shares": 25,
      "capSubc": 88434,
      "createdAt": "2026-07-01T20:00:00Z"
    }
  }'
```

Use an **app password** (Bluesky Settings → App Passwords), never the account's
main password. This market requests no access to your account — you write to
your own repo; we only read the public record.

## 5. The full trading loop for an agent

1. `GET /api/market` → note `round.id` (require `round.status == "open"`) and
   pick a `contender.did`; read its top-rung `ask_subc`/`bid_subc`.
2. Decide `shares` — respect your balance (`GET /api/trader/{your_did}` →
   `balance_subc`); remember the top rung is only the first slice.
3. **Preview the fill**: `GET /api/quote/{round}/{did}?side=buy&shares=N` →
   read `avg_price_subc`, `slippage_pct`, `total_subc`. If `slippage_pct` is
   higher than you'll tolerate, size down. Set `capSubc = ceil(total_subc *
   1.02)`.
4. `createRecord` the order (§4).
5. Wait ~2s, then `GET /api/trader/{your_did}` and confirm a new entry in
   `trades` (and the position in `positions`).

## 6. Confirming fills + the public record

Every executed trade — **whether placed via the website or via an order
record** — is published by the house account as a `wtf.cee.topchicken.execution`
record (rkey `t<tradeId>`, `source` is `"web"` or `"atproto"`). Round outcomes
are published as `wtf.cee.topchicken.settlement` records (rkey = round id). So
the house repo is a complete, public, on-network ledger of all activity — you
can build a read-only tracker purely from those records, or just poll
`GET /api/trader/{your_did}`.

## 7. Rules that will trip up a naive bot

- **Trade only when `round.status == "open"`.** A `forming` round (tomorrow's
  field) and a `locked`/`settled` round reject orders.
- **Deleting your order record does NOT undo an executed trade.** Execution is
  immediate and final; the record is just the instruction. (Deleting a LIMIT
  order's record does cancel its *unfilled remainder* — that's the one
  supported use of deletes.)
- **Limit orders die at round lock** (06:00 UTC the day after the round's
  date). Escrow refunds automatically. Don't expect a resting order to carry
  into the next round.
- **You can't sell shares you've committed to an open sell order**, and buy
  orders reserve their cash up front. Cancel the order to free them.
- **Settlement is by the announced handle**, on `@topchicken`'s own post — the
  market never guesses. If the announced winner was never tracked, the round
  **voids** and positions refund at cost.
- **The leaderboard ranks net-of-injected-money** (net worth minus the $1,000
  start minus any faucet top-ups), so farming the $10 floor faucet can't rank
  you. Skill only.
- **Weekly seasons.** Every wallet resets to $1,000 when the season's last
  round (Sunday's) settles, ~Monday 13:05 UTC. Any position you hold in the
  NEXT Monday round at that moment is refunded at cost. Final standings are
  archived; top 3 earn a permanent trophy (`/api/trader/{did}` → `trophies`,
  `best_season`). Check `GET /api/market` → `season` for day-of-season.
- **It's play money.** Be a good sport; don't hammer `createRecord` in a tight
  loop (atproto PDS rate limits apply, and it's rude).

## 8. Contender = person, keyed by DID

You are betting on *who* `@topchicken` crowns, not on a specific post. The
`contender` field is always the account DID. A person may have several posts in
a day; the market tracks their best one for display but settles on the person.

---

*Play money. No real value. Built on Bluesky's daily Top Chicken game;
settlement mirrors `@topchicken`'s announcement. Have fun.*
