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

# Order lifecycle

> The 8-state journey an order takes from client signature to terminal state, with balance and matcher effects at every step.

## State machine

```mermaid theme={null}
stateDiagram-v2
    [*] --> PENDING: client signs + submits
    PENDING --> OPEN: matcher accepts (no immediate fill)
    PENDING --> PARTIAL: partial fill at match time
    PENDING --> FILLED: full fill at match time
    PENDING --> REJECTED: signature / balance / market guard
    OPEN --> PARTIAL: counterparty fill
    OPEN --> FILLED: counterparty fill
    OPEN --> CANCELLED: user DELETE
    OPEN --> CANCELLED_BY_RESOLVE: market resolved
    OPEN --> EXPIRED: expiration reached
    PARTIAL --> FILLED: remaining filled
    PARTIAL --> CANCELLED: user DELETE (refunds remaining)
    PARTIAL --> CANCELLED_BY_RESOLVE: market resolved
    FILLED --> [*]
    CANCELLED --> [*]
    CANCELLED_BY_RESOLVE --> [*]
    REJECTED --> [*]
    EXPIRED --> [*]
```

## States

| State                  | Meaning                                                                             | Balance effect                                                                                                                |
| ---------------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `PENDING`              | Order signed and submitted; balance locked; matcher call in flight                  | `available → locked` for BUY notional                                                                                         |
| `OPEN`                 | Resting in orderbook                                                                | locked remains                                                                                                                |
| `PARTIAL`              | Some quantity filled, remainder resting                                             | locked reduced pro-rata at settlement                                                                                         |
| `FILLED`               | Terminal — fully executed                                                           | residual locked refunded                                                                                                      |
| `CANCELLED`            | Terminal — user-initiated cancel                                                    | locked → available (refund)                                                                                                   |
| `CANCELLED_BY_RESOLVE` | Terminal — market was resolved; all open orders cleaned                             | locked → available (refund)                                                                                                   |
| `REJECTED`             | Terminal — rejected before matching                                                 | locked → available (refund)                                                                                                   |
| `EXPIRED`              | Terminal — `expiration` passed                                                      | locked → available (refund)                                                                                                   |
| `SETTLEMENT_FAILED`    | Terminal — every fill on this order reverted on-chain (`MatchFailed` for every leg) | locked → available (refund). Rare; usually requires the maker book to have moved between off-chain match and on-chain submit. |

<Tip>
  `PARTIAL` is normalised to `OPEN` on `GET /api/orders/open` for
  client simplicity (the `filledQty` field carries the residual
  information). `GET /api/orders/history` returns the raw DB state,
  so a partially-filled-then-cancelled order surfaces there as
  `PARTIAL` (active leg) followed by `CANCELLED` (terminal). Code
  against both shapes when reconciling open vs historical views.
</Tip>

### Filtering `/api/orders/open`

Both `/open` and `/history` accept the same filter shape — useful
for MMs with thousands of resting orders who want to scope server-
side instead of streaming the full open-set every poll:

| Query param | Type                                      | Semantics                     |
| ----------- | ----------------------------------------- | ----------------------------- |
| `marketId`  | string                                    | Restrict to one market symbol |
| `side`      | `"buy"` / `"sell"` (case-insensitive)     | One side only                 |
| `outcome`   | integer ≥ 0                               | One outcome index             |
| `type`      | `"limit"` / `"market"` (case-insensitive) | One order type                |

```bash theme={null}
GET /api/orders/open?marketId=NCAA-FINAL-WINNER-93920000&side=buy&outcome=0
```

All four are optional; combine freely. Capped at 500 rows per
response (the open-set is also bounded by per-wallet caps, so
no cursor/pagination is exposed here — use `/api/orders/history`
for keyset pagination over the terminal universe).

## Trade-level settlement (after match)

Order state flips to `FILLED` / `PARTIAL` as soon as the matching
engine returns fills — but the underlying **trade rows** then go
through a separate on-chain settlement pipeline:

| Trade state          | Meaning                                                                                                        | Locked balance                  |
| -------------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------- |
| `matched`            | Recorded right after the match                                                                                 | Remains locked                  |
| `settlement_pending` | `batchMatchOrders` tx broadcast on-chain                                                                       | Remains locked                  |
| `settled`            | `OrderFilled` event confirmed on-chain                                                                         | `locked → 0` (released)         |
| `settlement_failed`  | Tx reverted fully, **or** `MatchFailed` event for this specific leg (other legs in the batch can still settle) | `locked → available` (refunded) |

The most common cause of `settlement_failed` is `MatchFailed(reason =
0xc56873ba)` = `OrderExpired()` — a resting maker order whose
`expiration` slipped between off-chain match and on-chain submit. Sign
new orders with `expiration = 0` to avoid this; see
[CTFExchange — MatchFailed reason codes](/concepts/contracts/ctf-exchange).

This is why PredictStreet uses a **strict settlement** model:
partner-visible balances reflect only on-chain-confirmed state.
Expect a few seconds (p99 \< 30s on testnet) between order `FILLED`
and the locked notional clearing. **If `settlement_pending` exceeds
5 minutes, contact support** — most likely chain congestion or a
known stuck-trade case.

### How partners observe settlement

The REST trade endpoints (`/api/orders/{id}/fills`, `/api/me/trades`)
do not currently surface `tx_hash` or settlement state on the trade
row — they always show the matched fill, not the on-chain status.
The partner-recommended channels:

* **WebSocket `/ws/user` typed channels** (recommended):
  * `user_orders` — `order_placed` / `order_cancelled`. Use for
    orderbook UI updates.
  * `user_fills` — `user_fill` fires when chain-watcher indexes the
    on-chain `OrderFilled` event. This is the **balance-commit**
    signal — `available` / `locked` totals from `/api/me/balances`
    reflect the trade after this event. Carries `txHash`,
    `blockNumber`, `fee`, vault addresses, matched amounts.
* **REST balance polling** — `available` + `locked` totals derive
  from on-chain-confirmed state, so polling `/api/me/balances` is a
  backstop if WS is unavailable.
* **Settlement-leg failures** are not surfaced on a WS channel today;
  to detect them, poll `GET /api/orders/{id}` and watch for
  `status: SETTLEMENT_FAILED` (locked balance is automatically
  refunded — the trade is treated as if it never settled).

See [WebSocket — User events](/api-reference/websocket/user) for
the full payload shapes of each push.

## What actually happens on `POST /api/orders/place`

1. **Authn** — `X-Api-Key` is verified (partner lookup → SHA-256 hash
   compare → lifecycle + IP-allowlist checks). The canonical wallet
   is your key's `associatedWallet`. `orders:write` scope is enforced
   before the handler runs — see [API keys](/auth/api-keys#scopes).
2. **Pre-verify enrichment** — the platform resolves:
   * your vault via `VaultFactory.vaultOf(signer)`
   * the on-chain `tokenId` for the requested `outcome`
   * `makerAmount` / `takerAmount` from `price × quantity` per side
3. **EIP-712 verify** — the full on-chain `Order` struct (11 fields)
   is reconstructed; the recovered signer must match the authenticated
   wallet, and `vaultOf(signer)` must equal `maker`. The body's
   `maker` is then overridden to the resolved vault before storage.
4. **Market guard** — the market's `status` must be `OPEN`; otherwise
   `409 market_not_open`.
5. **Balance / position lock** — atomic:
   * BUY: `available -= notional`, `locked += notional`
   * SELL: position lookup is keyed by **vault** address (not EOA);
     fails with `insufficient_position` if the vault doesn't hold
     enough of the outcome token
   * the order row is recorded as `PENDING`
6. **Match** — sent to the matching engine. Returns
   `{status, filledQty, trades[]}` synchronously.
7. **Trade persistence** — trade rows + fee ledger written, balance
   deltas applied for both sides of every trade — atomically.
8. **Settlement enqueue** — matched trades are broadcast on-chain via
   `batchMatchOrders` on `CTFExchange`. See the trade-state table
   above.
9. **Response** — shape documented below.

If step 6 fails after retries, step 5 is compensated: order status →
`REJECTED`, locked balance refunded, call returns 503
`exchange_unavailable`.

## Response shape

```typescript theme={null}
interface PlaceOrderResp {
  orderId: string;
  status:
    | 'PENDING'
    | 'OPEN'
    | 'FILLED'
    | 'CANCELLED'
    | 'REJECTED'
    | 'EXPIRED'
    | 'SETTLEMENT_FAILED';
  filledQty: string;
  remainingQty: string;
  trades: Trade[];
  code?: string;
  message?: string;
}

interface Trade {
  id: string;
  orderId: string;
  userWallet: string;        // EOA — the side's authenticated wallet
  marketId: string;
  price: string;             // decimal USDC
  quantity: string;          // outcome-token qty (decimal)
  fee: string;               // taker fee charged on this fill (decimal USDC)
  side: 'buy' | 'sell';      // from this wallet's perspective
  createdAt: string;
}
```

The synchronous `trades[]` carries the matched fill view — settlement
state and `txHash` are not on this row. To observe on-chain
confirmation, subscribe to the `user_fills` WebSocket channel and
watch for `user_fill` events (they include `txHash`, `blockNumber`,
and the matched amounts) — see
[How partners observe settlement](#how-partners-observe-settlement)
above.

## Idempotency

Include `clientOrderId` in the request body to make the call idempotent.
If a network retry hits the server with the same `(wallet, clientOrderId)`,
the original response is replayed.

## Cancellation

```http theme={null}
POST /api/orders/cancel
X-Api-Key: ps_live_<keyId>_<secret>  # integrators — needs `orders:write`
Content-Type: application/json

{ "orderId": "0x..." }
```

* **Matcher is called first** (best-effort — `NOT_FOUND` is treated as
  idempotent success).
* PG is the source of truth: on successful `matcher.cancelOrder`, we
  flip `OPEN | PARTIAL → CANCELLED` and refund residual locked balance
  in a single transaction.

## Next

<CardGroup cols={2}>
  <Card title="EIP-712 signing" icon="file-signature" href="/concepts/trading/eip712-signing">
    Struct reference + signing examples.
  </Card>

  <Card title="Placing orders" icon="plus" href="/concepts/trading/placing-orders">
    Full request schema + validation.
  </Card>

  <Card title="Fees" icon="coins" href="/concepts/trading/fees">
    Quadratic taker-fee curve explained.
  </Card>

  <Card title="Error codes" icon="triangle-exclamation" href="/errors/codes">
    Complete code reference.
  </Card>
</CardGroup>
