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.Fields
string
required
Always
"trade".number
required
Unix milliseconds of the block.
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 bylog_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
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 a specific wallet’s trades
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 itside= whether the maker was buying or selling
- 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. - One additional event with the user as
makerand the exchange contract astaker(0x4bfb41d5b3570defd03c39a9a4d8de6bd8b8982efor standard markets,0xc5d563a36ae78145c45a50134d48a1215220f80afor neg-risk markets). This is the user’s actual fill.
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 transaction0x910466c40141da9784fe0dd6b8e7a81d0b46ed3667100b83c714ee70eaf39ab2 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:
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
Subscribing only to a specific wallet
If you only want events involving a specific wallet, use thewallets 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:

