Skip to main content

Documentation Index

Fetch the complete documentation index at: https://polynode.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

A confirmed on-chain trade from Polymarket’s exchange contracts.
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:
ShapeWhen it firesSourceFormat
settlement.trades[]Pre-confirmation (3-5s before the block)Calldata (estimated for multi-maker)Array inside one event
trade events (this page)At block confirmationReceipt logs (exact)One separate WS message per fill
status_update.confirmed_fills[]At block confirmationReceipt 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.
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.
{
  "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

type
string
required
Always "trade".
timestamp
number
required
Unix milliseconds of the block.
data
object
required

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

SettlementTrade
SourcePre-chain detectionOn-chain confirmation
TimingPending (pre-chain) or confirmedConfirmed only
ContainsFull settlement with all maker fillsSingle maker-taker fill
Use caseEarly detection, copy tradingAccurate historical record
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).
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 identical to what Polymarket’s CLOB uses internally.

Tracking a specific wallet’s trades

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.
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.
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, which bundle all fills from a single matchOrders transaction into one message with a nested data.trades[] array.

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 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

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:
ws.send(JSON.stringify({
  action: "subscribe",
  type: "trades",
  filters: { wallets: ["0xYourWallet"] }
}));