> For the complete documentation index, see [llms.txt](https://dev.strixlab.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://dev.strixlab.io/get-started/concepts.md).

# Core Concepts

Events, markets, outcomes and tokenIds — the five ideas you need before writing any trading code.

### The hierarchy

Everything in the API hangs off three nested objects:

```
Event            "2026 World Cup winner"          ← what people are betting on
 └─ Market       "Will Brazil win?"               ← one binary question
     └─ Outcome  YES / NO                         ← what you actually trade
         └─ tokenId  0x1a2b…                      ← the identifier you pass everywhere
```

* An **event** is the container. A simple yes/no event holds exactly one market. A multi-outcome event (an election, a tournament) holds one binary market per candidate.
* A **market** is a single binary question with exactly two outcomes.
* An **outcome** is one side of that question, and it carries the `tokenId`.

{% hint style="info" %}
**`tokenId` is the only identifier that matters at trade time.** Orders, order-book queries, WebSocket subscriptions and positions are all keyed by it. `eventId` and `marketId` are for filtering and reporting.
{% endhint %}

{% code title="Walking the hierarchy" %}

```ts
const event  = await client.events.get('will-brazil-win-the-2026-world-cup');
const market = event.markets[0];
const yes    = market.outcomes.find((o) => o.outcomeIndex === 1)!;

console.log(yes.tokenId);   // ← trade with this
```

{% endcode %}

### YES is index 1, NO is index 0

Outcome order in the array is not guaranteed. The reliable mapping is `outcomeIndex`: **`1` is YES, `0` is NO**. Rather than re-deriving that everywhere, use the helper — it is the single source of truth the SDK, the reference bot and the frontend all share:

```ts
import { getTokenIds } from 'strix-sdk';

const { yesTokenId, noTokenId, yesOutcome, noOutcome } = getTokenIds(market);
```

{% hint style="warning" %}
On multi-outcome (`NEG_RISK`) events the displayed `outcomeName` can read the opposite way round from these labels. That is cosmetic — **trust `outcomeIndex`, not the display name**. The `tokenId` returned by `getTokenIds()` is always the correct one to trade.
{% endhint %}

### A share is a dollar-if-right

Each outcome token pays **$1.00** if that outcome happens and **$0.00** if it doesn't. So a price of `0.65` means 65¢ per share — the market's collective estimate of a 65% chance.

* Prices are decimals in `[0.0001, 0.9999]` — never exactly 0 or 1, which would break the YES↔NO complement math.
* `tickSize` is `0.01` or `0.001` depending on the market, and it lives on the **event** (`event.tickSize`). Every order price must be an exact multiple of it, on top of the platform-wide 0.0001 precision floor.
* Quantities are share counts, carried as decimal strings with up to 6 fractional digits.
* Minimum order value is **$1.00**. For a resting order that is `price × quantity`; for a market order it is the value actually swept.

### YES and NO are the same book, mirrored

Buying YES at 40¢ and selling NO at 60¢ are economically identical, so the two books are complements:

```
bid YES @ P   ≡   ask NO @ (1 − P)
```

The SDK exposes this directly. Subscribe to the YES token and you get both prices:

```ts
client.ws.orderbook.subscribe([yesTokenId], {
  onMid: ({ yesMid, noMid }) => console.log(`YES ${yesMid} · NO ${noMid}`),
});
```

This mirroring is also why a **market buy behaves the way it does**: it only sweeps resting *asks on the same token*. It will not cross into the sibling outcome's bids. On a book with bids but no asks, a market buy fails with `no_liquidity` — to take a resting bid on the other leg, place a crossing limit buy on the sibling token at `1 − bidPrice` instead. See [Orders](/typescript-sdk/orders.md#market-orders).

### Orders settle asynchronously

This is the single biggest difference from a conventional exchange API, and the most common source of bugs:

{% stepper %}
{% step %}

#### You place an order

`POST /orders` validates it, reserves your balance, and returns `status: "OPEN"` with `filledQuantity: "0"` — **even if it crosses the spread and will fill immediately.**
{% endstep %}

{% step %}

#### The engine matches it

Milliseconds later, the matching engine locks the matched size. The order-book delta goes out to every subscriber, and your order's matched quantity moves into `holdQuantity`.
{% endstep %}

{% step %}

#### The trade settles on-chain

Roughly 15–20 seconds later the settlement transaction confirms. Now `status` becomes `FILLED` or `PARTIALLY_FILLED`, and `trade.settled` fires on your user channel with the transaction hash.
{% endstep %}
{% endstepper %}

Two practical consequences:

1. **Don't treat the placement response as a fill report.** Observe fills via `order.update` / `trade.settled` on the [user channel](/typescript-sdk/websockets.md#your-orders-and-fills), or by polling `portfolio.positions()`.
2. **Your resting size is a three-term subtraction.** While a settlement is in flight, part of your order is neither filled nor on the book:

   ```ts
   const resting = Number(order.quantity) - Number(order.filledQuantity) - Number(order.holdQuantity);
   ```

   The two-term version looks right and quietly overstates your exposure.

### Order statuses

<table><thead><tr><th width="220">Status</th><th>Meaning</th></tr></thead><tbody><tr><td><code>OPEN</code></td><td>Resting on the book, nothing filled.</td></tr><tr><td><code>PARTIALLY_FILLED</code></td><td>Some quantity settled; the remainder is still working.</td></tr><tr><td><code>HOLD</code> / <code>PARTIALLY_HOLD</code></td><td>Matched and locked while settlement is in flight. <strong>Not cancellable.</strong></td></tr><tr><td><code>FILLED</code></td><td>Fully settled.</td></tr><tr><td><code>CANCELLED</code></td><td>Cancelled by you, by expiry sweep, or by an unfilled FOK/FAK remainder.</td></tr><tr><td><code>EXPIRED</code></td><td>A GTD order passed its <code>expiredAt</code>.</td></tr></tbody></table>

The WebSocket user channel adds one transient status you'll never see over REST: **`MATCHED`**, emitted between the engine match and on-chain confirmation.

{% hint style="info" %}
**Cancels are restricted to `OPEN` orders.** Once any part of an order is matched or settling, cancelling it would race the settlement, so the venue refuses. A cancel that fails means **the order is still live** — never assume otherwise and quote against it.
{% endhint %}

### Splitting and merging

Because a full outcome set is worth exactly $1, you can convert between USDC and shares without touching the book at all:

* **Split** — $100 USDC becomes 100 YES + 100 NO.
* **Merge** — 100 YES + 100 NO becomes $100 USDC.
* **Redeem** — after resolution, winning shares become $1 each.

These are on-chain operations exposed as [`client.positions`](/typescript-sdk/portfolio.md#conditional-token-operations). Market makers use split to manufacture two-sided inventory instead of buying it.
