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

# Trade Event

> Confirmed on-chain trade execution from Polymarket exchange contracts.

A confirmed on-chain trade from Polymarket's exchange contracts.

<Warning>
  **Don't confuse `trade` events with the `trades[]` array inside `settlement` events.** They are related but not the same thing.

  A `trade` event is one of three ways polynode exposes the same fill data, depending on what you need:

  | Shape                             | When it fires                            | Source                               | Format                           |
  | --------------------------------- | ---------------------------------------- | ------------------------------------ | -------------------------------- |
  | `settlement.trades[]`             | Pre-confirmation (3-5s before the block) | Calldata (estimated for multi-maker) | Array inside one event           |
  | `trade` events (this page)        | At block confirmation                    | Receipt logs (exact)                 | One separate WS message per fill |
  | `status_update.confirmed_fills[]` | At block confirmation                    | Receipt logs (exact)                 | Array inside one event           |

  `trade` events and `confirmed_fills[]` contain the **same exact data** — they're both decoded from the on-chain `OrderFilled` logs, just packaged differently. If you already subscribe to `settlement` events, you can read `confirmed_fills[]` from the `status_update` instead of also subscribing to `trade` events. If you only want the post-confirmation per-fill stream, subscribe to `trade` events directly.

  `settlement.trades[]` is the **pre-confirmation equivalent** — it has the same shape but the per-maker prices are estimated from the calldata's aggregate amounts. Use it for speed (3-5 second lead), use `trade` events or `confirmed_fills[]` for exactness.
</Warning>

<Info>
  **V2 compatible.** Trade events work identically for both V1 and V2 Polymarket exchanges. The `fee` field is decoded the same way for V2 trades. No changes to your subscription or parsing logic are needed.
</Info>

```json theme={null}
{
  "data": {
    "block_number": 84705489,
    "condition_id": "0x158fa5bd542d4982a5b09eee29c5d23f8f1304367adc1a8e4396b692eb550466",
    "event_slug": "eth-updown-5m-1774530900",
    "event_title": "Ethereum Up or Down - March 26, 9:15AM-9:20AM ET",
    "event_type": "trade",
    "exchange": "ctf_exchange",
    "fee": 0.122535,
    "log_index": 1996,
    "maker": "0x4b8cf80092c60b9b23d22133eb63eb4508fe4d31",
    "maker_amount": "2130000",
    "market_image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/ETH+fullsize.jpg",
    "market_slug": "eth-updown-5m-1774530900",
    "market_title": "Ethereum Up or Down - March 26, 9:15AM-9:20AM ET",
    "neg_risk": false,
    "order_hash": "0x916fa5c2728c93e19ea5d7c254b07234815ad01e59597573c09d852fe53ee564",
    "outcome": "Up",
    "outcomes": ["Up", "Down"],
    "price": 0.71,
    "side": "BUY",
    "size": 3.0,
    "taker": "0x536be02af900fe046fa708c8059c04f737a2cee3",
    "taker_amount": "3000000",
    "taker_base_fee": 1000.0,
    "tick_size": 0.01,
    "timestamp": 1774531013000,
    "token_id": "67838338591118719610402948028043822510398368770316608063235371949564584838291",
    "token_ids": [
      "67838338591118719610402948028043822510398368770316608063235371949564584838291",
      "106640979554519903529443899392736422684573887313790829687503639341656103167080"
    ],
    "tokens": {
      "106640979554519903529443899392736422684573887313790829687503639341656103167080": "Down",
      "67838338591118719610402948028043822510398368770316608063235371949564584838291": "Up"
    },
    "tx_hash": "0xaf3d097f3b2e799c0a14cca4e7709122b397c9b920c276c0fd3caa4848ea9808"
  },
  "timestamp": 1774531013000,
  "type": "trade"
}
```

## Fields

<ResponseField name="type" type="string" required>
  Always `"trade"`.
</ResponseField>

<ResponseField name="timestamp" type="number" required>
  Unix milliseconds of the block.
</ResponseField>

<ResponseField name="data" type="object" required>
  <Expandable title="data fields">
    <ResponseField name="tx_hash" type="string" required>
      Transaction hash.
    </ResponseField>

    <ResponseField name="block_number" type="number" required>
      Block number.
    </ResponseField>

    <ResponseField name="log_index" type="number" required>
      Log index within the transaction. `(tx_hash, log_index)` is globally unique.
    </ResponseField>

    <ResponseField name="timestamp" type="number" required>
      Block timestamp in Unix milliseconds.
    </ResponseField>

    <ResponseField name="exchange" type="string" required>
      `"ctf_exchange"` or `"neg_risk_ctf_exchange"`.
    </ResponseField>

    <ResponseField name="maker" type="string" required>
      Maker wallet address.
    </ResponseField>

    <ResponseField name="taker" type="string" required>
      Taker wallet address.
    </ResponseField>

    <ResponseField name="token_id" type="string" required>
      Conditional token ID.
    </ResponseField>

    <ResponseField name="side" type="string" required>
      `"BUY"` or `"SELL"`.
    </ResponseField>

    <ResponseField name="price" type="number" required>
      Fill price (0.0–1.0 for binary markets).
    </ResponseField>

    <ResponseField name="size" type="number" required>
      Fill size in shares.
    </ResponseField>

    <ResponseField name="maker_amount" type="string" required>
      Raw maker amount (6 decimal precision).
    </ResponseField>

    <ResponseField name="taker_amount" type="string" required>
      Raw taker amount (6 decimal precision).
    </ResponseField>

    <ResponseField name="fee" type="number">
      Fee charged on this trade in USDC. Decoded from the OrderFilled log. `null` when fee is zero or not present.
    </ResponseField>

    <ResponseField name="order_hash" type="string">
      EIP-712 order hash. Uniquely identifies the maker's limit order on Polymarket's CLOB. **Persistent across partial fills** — the same `order_hash` appears every time a slice of that limit order is matched.
    </ResponseField>

    <ResponseField name="exchange_version" type="string">
      Exchange contract version: `"v2"` for trades on Polymarket's V2 exchange contracts. Absent for V1 trades (current default).
    </ResponseField>

    <ResponseField name="market_title" type="string">
      Market question. Enriched from metadata.
    </ResponseField>

    <ResponseField name="outcome" type="string">
      Outcome name. Enriched from metadata.
    </ResponseField>

    <ResponseField name="market_slug" type="string">
      Market URL slug. Enriched from metadata.
    </ResponseField>

    <ResponseField name="market_image" type="string">
      Market image URL. Enriched from metadata.
    </ResponseField>

    <ResponseField name="event_title" type="string">
      Parent event title. Enriched from metadata.
    </ResponseField>

    <ResponseField name="neg_risk" type="boolean">
      Whether this market uses the negRisk contract type. Enriched from metadata.
    </ResponseField>

    <ResponseField name="tick_size" type="number">
      Minimum price increment (e.g. `0.01` or `0.001`). Enriched from metadata.
    </ResponseField>

    <ResponseField name="taker_base_fee" type="number">
      Taker fee rate in basis points. `200` = 2%, `1000` = 10%, `null` = no fee. Short-term crypto markets (5-minute ETH/BTC) typically have `1000` (10%), while standard markets have `200` (2%). Enriched from Polymarket metadata.
    </ResponseField>

    <ResponseField name="condition_id" type="string">
      Market condition ID (CTF contract identifier). Enriched from metadata.
    </ResponseField>

    <ResponseField name="event_slug" type="string">
      Parent event URL slug. Enriched from metadata.
    </ResponseField>

    <ResponseField name="outcomes" type="string[]">
      Ordered list of outcome names (e.g. `["Yes", "No"]`). Index-aligned with `token_ids`. Enriched from metadata.
    </ResponseField>

    <ResponseField name="token_ids" type="string[]">
      Ordered list of token IDs for this market. Index-aligned with `outcomes`. Enriched from metadata.
    </ResponseField>

    <ResponseField name="tokens" type="object">
      Map of token ID → outcome name for all outcomes in this market (e.g. `{"123...": "Yes", "456...": "No"}`). Enriched from metadata.
    </ResponseField>
  </Expandable>
</ResponseField>

## Ordering guarantees

Trade events are delivered in strict block order. All trades from block N arrive before any trades from block N+1. Within a block, trades are ordered by `log_index`. The tuple `(tx_hash, log_index)` is globally unique and can be used as a deduplication key.

Polygon uses Bor consensus with single-validator sprints, making chain reorganizations effectively nonexistent. You can safely assume that once you receive a trade from block N+1, you have received all trades from block N.

## Settlement vs trade

|          | Settlement                           | Trade                      |
| -------- | ------------------------------------ | -------------------------- |
| Source   | Pre-chain detection                  | On-chain confirmation      |
| Timing   | Pending (pre-chain) or confirmed     | Confirmed only             |
| Contains | Full settlement with all maker fills | Single maker-taker fill    |
| Use case | Early detection, copy trading        | Accurate historical record |

<Note>
  A single settlement can contain multiple trades (one per matched maker order). If you subscribe to both `settlement` and `trade`, you'll see the settlement first (pending), then individual trades (confirmed).
</Note>

<Tip>
  **Tracking limit orders across fills:** Use `order_hash` to group partial fills of the same limit order. When a maker places a 1,000-share order and it fills in three separate transactions, all three trades share the same `order_hash`. This is the EIP-712 hash of the signed order struct — it's deterministic from the order parameters and matches Polymarket's CLOB order hash.
</Tip>

## Tracking a specific wallet's trades

<Warning>
  **TL;DR — Always filter by `maker`, never by `taker`.** A single transaction emits multiple `trade` events as separate WebSocket messages. The wallet's actual fill is the event where `data.maker === your_wallet`. The other events show the counterparties' perspective.
</Warning>

This is the most common source of confusion when integrating with the trade stream. Once you understand the on-chain structure, the fix is one line.

<Note>
  **`trade` events have no sub-array.** Each fill arrives as its own WebSocket message with top-level `maker`, `taker`, `side`, `price`, `token_id`, etc. Inspect each incoming message individually. If you're looking for a `trades[]` array, you're thinking of [settlement events](/websocket/events/settlement), which bundle all fills from a single `matchOrders` transaction into one message with a nested `data.trades[]` array.
</Note>

### How `OrderFilled` events work on-chain

Every match on Polymarket emits one `OrderFilled` log per maker order, plus one additional log for the taker's own order. **All fields on each log — `side`, `token_id`, `price`, `size` — are recorded from the maker's perspective on that specific log.**

* `maker` = whoever placed the limit order that got filled (the resting order)
* `taker` = whoever's order matched into it
* `side` = whether the maker was buying or selling

When a user submits an order that matches against N existing maker orders, the on-chain settlement emits **N + 1 events**:

1. **N events with the user as `taker`** — one per counterparty maker order they matched against. These show the counterparties' tokens, prices, and sides, not the user's.
2. **One additional event with the user as `maker`** and the exchange contract as `taker` (`0x4bfb41d5b3570defd03c39a9a4d8de6bd8b8982e` for standard markets, `0xc5d563a36ae78145c45a50134d48a1215220f80a` for neg-risk markets). **This is the user's actual fill.**

Polymarket's activity API and Dome both follow this same convention, because they read directly from these `OrderFilled` logs. If you've used Dome before with `event.user === yourWallet`, the equivalent here is `event.maker === yourWallet`. See the [Dome migration guide](/dome-migration#order-perspective) for the same explanation in Dome's terminology.

### Real example

Live data from transaction `0x910466c40141da9784fe0dd6b8e7a81d0b46ed3667100b83c714ee70eaf39ab2` on April 9, 2026. The user `0xf5a77527f5154e4cd84ef32c73fab4ba58ec4be0` placed a market BUY for the Up token on an Ethereum 5-minute market. The transaction emitted **4 trade events**:

```
log_index=586  maker=0x0b9353ae taker=0xf5a77527  side=BUY price=0.21  size=5     token=Down
log_index=596  maker=0xfcdc071d taker=0xf5a77527  side=BUY price=0.20  size=12    token=Down
log_index=606  maker=0x2e9b93fa taker=0xf5a77527  side=BUY price=0.19  size=1.79  token=Down
log_index=610  maker=0xf5a77527 taker=0xExchange  side=BUY price=0.7983 size=18.79 token=Up    ← USER'S ACTUAL TRADE
```

The first three events show counterparties (other wallets) buying the **Down** token at 0.21, 0.20, 0.19. The user happens to appear as the `taker` on each of those because the contract matched their order against them, but those events represent the counterparties' trades, not the user's.

The user's actual trade is the fourth event: **`BUY Up at 0.7983 size=18.79`**, where they appear as `maker` and the CTF Exchange contract appears as `taker`. Polymarket's activity API confirms this exact trade: `side=BUY outcome=Up price=0.7983 size=18.51` (the slight size difference is the fee deduction).

If you filter by `taker == wallet`, you get the first three events (counterparty perspective on the Down token). If you filter by `maker == wallet`, you get the user's actual fourth event (the Up token at 0.7983).

### The right way to filter

```javascript theme={null}
ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  if (msg.type !== "trade") return;
  const t = msg.data;

  // CORRECT — match by maker. This is the user's actual fill.
  if (t.maker.toLowerCase() === MY_WALLET.toLowerCase()) {
    console.log("My trade:", t.side, t.size, t.outcome, "@", t.price);
  }

  // WRONG — match by taker. Returns counterparty fills with the OPPOSITE
  // token and the COMPLEMENT price (e.g. BUY Down 0.20 when you actually
  // bought Up at 0.80).
  // if (t.taker.toLowerCase() === MY_WALLET.toLowerCase()) { ... }
};
```

### Subscribing only to a specific wallet

If you only want events involving a specific wallet, use the `wallets` filter on the subscription. The server-side filter matches **both** `maker` and `taker` fields, so you receive every event the wallet appears in. Apply `maker === wallet` on the client side to get only the wallet's actual trades:

```javascript theme={null}
ws.send(JSON.stringify({
  action: "subscribe",
  type: "trades",
  filters: { wallets: ["0xYourWallet"] }
}));
```
