> ## Documentation Index
> Fetch the complete documentation index at: https://docs.xo.market/llms.txt
> Use this file to discover all available pages before exploring further.

# Wire conventions

> How prices, sizes, timestamps, and IDs are encoded on the wire.

XO follows the canonical CLOB wire conventions almost exactly. The gotchas below catch most integration bugs.

## Prices

| Form                                                                         | Where it appears                                                                                                       |
| ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| **Tick** — integer in `[1, 999]`, denominator 1000 (`500` = 0.500)           | On-chain `CTFOrder.makerAmount`/`takerAmount` math; inline-trading `signed_ctf_order.price`.                           |
| **Trimmed decimal** — `"0.5"`, `"0.534"`, `"0.555"` (trailing zeros trimmed) | Almost every REST/WS field: `/midpoint`, `/price`, `/spread`, `/last-trade-price`, `price_change`, order/trade events. |
| **Padded decimal** — `"0.500"`, `"0.534"` (always 3 decimals)                | Inside `book` snapshot price levels on the WS feed only.                                                               |

* **Tick size** is `0.001` globally. Submit a price that's not a multiple of the tick and the order is rejected (400 `"invalid price tick"`).
* Prices are **strings**, not JSON numbers, on every field listed above (except the `f64` `minimum_tick_size` returned by `/tick-size`).
* Empty-book sentinels: `/midpoint` returns `"0.000"`, `/spread` returns the bare `"0"`, `/last-trade-price` defaults to `"0.5"` with `side: ""`. Don't assume they all share one default.

## Sizes

| Form                                                                  | Where it appears                                                                                            |
| --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| **Human shares** — `"100"`, `"1"`, `"0.5"` (decimal string)           | Almost every REST/WS field where size appears: order/trade events, `/book` price levels, `/trades` rows.    |
| **Canonical micro-shares** — `"1000000"` = 1 share (6 decimal places) | On-chain `CTFOrder.makerAmount`/`takerAmount`; some REST internals (`balance`, `reserved` on `/positions`). |

The CTFOrder `makerAmount`/`takerAmount` you sign on-chain are in **micro-shares × tick math**. The orderbook converts to human-share decimal strings on the way out. Don't confuse the two when reconciling sizes across REST and WS surfaces.

## Timestamps

XO emits timestamps in **three different formats** depending on the field. Mismatched parsing here is the most common integration bug.

| Field                                                                              | Format                                       | Unit                         |
| ---------------------------------------------------------------------------------- | -------------------------------------------- | ---------------------------- |
| `GET /time`                                                                        | bare JSON number                             | Unix **seconds**             |
| `BookResponse.timestamp`, all WS event `timestamp` fields                          | stringified integer (`"1779355812367"`)      | Unix **milliseconds**        |
| `TradeResponse.match_time`, `TradeResponse.last_update`, signed-order `expiration` | stringified integer (`"1779355812"` / `"0"`) | Unix **seconds**             |
| `OpenOrder.created_at`                                                             | bare JSON number                             | Unix **seconds**             |
| `Error429.retry_after_ms`                                                          | bare JSON number                             | **Milliseconds** until reset |

When in doubt: if it's in a `book` snapshot or a WS frame, it's stringified ms. If it's a trade `match_time` or an order's `expiration`/`created_at`, it's seconds. The SDK uses `TimestampSeconds<String>` and `TimestampMilliSeconds<String>` types to enforce this — your code should too.

## IDs

| ID                                                       | Format                               | Notes                                                                              |
| -------------------------------------------------------- | ------------------------------------ | ---------------------------------------------------------------------------------- |
| `condition_id`                                           | `0x`-prefixed 32-byte hex            | The market identifier.                                                             |
| `token_id` (a.k.a. `asset_id`)                           | Decimal U256 string (no `0x` prefix) | Derived from `getPositionId(conditionId, indexSet)`.                               |
| `order.id`                                               | Decimal string                       | Numeric internal id, returned as a string for SDK compatibility.                   |
| `order.order_hash` / `orderID` (on `PlaceOrderResponse`) | `0x`-prefixed 32-byte hex            | The EIP-712 typed-data digest of the signed order.                                 |
| `trade.id`                                               | `0x`-prefixed 32-byte hex            | The trade id. Wire field name is `id` (not `trade_id`).                            |
| `taker_order_id` / `maker_order_id`                      | `0x`-prefixed 32-byte hex            | Same shape as `trade.id`.                                                          |
| `owner`                                                  | UUIDv5                               | Deterministic from the wallet address — same wallet always produces the same UUID. |
| `maker_address`                                          | `0x`-prefixed 20-byte hex            | Wallet address, distinct from `owner`.                                             |
| `smart_account` (on `/balance-allowance`)                | hex **without** `0x` prefix          | Watch this — every other address field has the prefix.                             |

## Pagination cursors

`MarketsPage`, `OrdersPage`, `TradesPage` all share:

```json theme={null}
{
  "limit": 100,
  "count": <items in this page>,
  "next_cursor": "<base64 offset>",
  "data": [...]
}
```

`next_cursor` is **always a string**, never `null`. The literal `"LTE="` is the canonical CLOB end-of-stream sentinel — when you see it, stop paginating.

```python theme={null}
cursor = ""
while True:
    page = get("/markets", params={"limit": 100, "next_cursor": cursor})
    yield from page["data"]
    if page["next_cursor"] == "LTE=":
        break
    cursor = page["next_cursor"]
```

## EIP-712 order signing

Domain (mainnet):

```text theme={null}
EIP712Domain(string name, string version, uint256 chainId, address verifyingContract)
  name              = "XO Market CLOB"
  version           = "1"
  chainId           = 3223
  verifyingContract = 0x4bC5E872256D12E6017dfe466E04c867DC761B77   // CTF Exchange
```

Order typehash (13 fields — `signature` is NOT in the typehash):

```text theme={null}
Order(uint256 salt,address maker,address signer,address beneficiary,
      uint256 tokenId,uint256 makerAmount,uint256 takerAmount,
      uint256 expiration,uint128 nonce,bytes16 identifier,bytes32 metadata,
      uint8 side,uint8 signatureType)
```

Legacy keys `taker` and `feeRateBps` are ignored on parse and must not be signed — the typehash above is authoritative. Fees are not part of the signed order; see [Fees](/guides/fees).

`signatureType` values:

| Value | Meaning                                                                                         |
| ----- | ----------------------------------------------------------------------------------------------- |
| `0`   | EOA (`ecrecover`). **Not currently accepted by the XO orderbook** — use the smart-account flow. |
| `1`   | POLY\_PROXY — EIP-1271 validated on the `maker` address. Reserved, not used by XO.              |
| `2`   | POLY\_GNOSIS\_SAFE — EIP-1271 validated on the `signer` address. Reserved, not used by XO.      |
| `3`   | XO smart account (ERC-1271). The only signature type the orderbook accepts today.               |

For `signatureType = 3` the orderbook enforces `maker == signer` (both set to the smart account) and validates the 65-byte ECDSA signature — produced by the owner EOA — via `IERC1271(maker).isValidSignature(orderHash, signature)`. See [Smart accounts](/guides/smart-accounts) for the full hashing reference, including the exact Solidity `hashOrder` function the exchange runs.

## L1 ClobAuth domain (auth, not orders)

The auth headers for `POST /auth/api-key` and `GET /auth/derive-api-key` use a separate EIP-712 domain — **no `verifyingContract`** field:

```text theme={null}
EIP712Domain(string name, string version, uint256 chainId)
  name    = "ClobAuthDomain"
  version = "1"
  chainId = 3223

ClobAuth(address address, string timestamp, uint256 nonce, string message)
```

The `message` is the literal string `"This message attests that I control the given wallet"` by SDK convention. Don't confuse this domain with the order-signing domain above — they're separate.
