A single Polymarket transaction can involve 100+ blockchain events. To the untrained eye, it’s chaos. To those who understand the mechanics, it’s an elegant liquidity aggregation system.
Let’s decode transaction 0x0d45b2881dc1cb321ee942fec8cf9e523ce9f2a835005006ddbb1cdf36dfdec2 - a trade where someone spent 7,434 USDC to buy 13,276 shares of “Dallas Mavericks to win” - and learn how to interpret the 111 log entries it generated.
By the end of this article, you’ll understand:
- How Polymarket aggregates liquidity across both sides of a binary market
- Why “selling NO” is functionally equivalent to “buying YES”
- How to systematically interpret complex DeFi transactions
- The critical role of conditional token splits in creating market depth
Background: Binary Markets and Conditional Tokens
Polymarket uses binary outcome markets for events like sports games. In this case:
- Outcome A: Dallas Mavericks wins
- Outcome B: Denver Nuggets wins
Each outcome is represented by an ERC-1155 token. These aren’t ordinary tokens - they’re conditional tokens with a special property:
1 Mavericks token + 1 Nuggets token = 1 USDC
This relationship is enforced by the Conditional Tokens Framework (CTF), a smart contract system developed by Gnosis. The key operations are:
Split Operation
Convert USDC into both outcome tokens:
1 USDC → 1 Mavericks + 1 Nuggets
This operation is always available at a 1:1 ratio. Anyone can deposit USDC and receive equal amounts of both outcome tokens.
Merge Operation
Convert both outcome tokens back to USDC:
1 Mavericks + 1 Nuggets → 1 USDC
This is the reverse operation, also at a 1:1 ratio.
The Power of Symmetry
Because Mavericks + Nuggets = 1 USDC, the prices are locked together:
| Scenario | Mavericks Price | Nuggets Price | Sum |
|---|---|---|---|
| Even odds | 0.50 USDC | 0.50 USDC | 1.00 |
| Mavs favored | 0.56 USDC | 0.44 USDC | 1.00 |
| Nuggets favored | 0.30 USDC | 0.70 USDC | 1.00 |
This creates a powerful equivalence:
- Selling Nuggets at 0.44 = Buying Mavericks at 0.56
- Buying Nuggets at 0.44 = Selling Mavericks at 0.56
The CTF Exchange exploits this symmetry to aggregate liquidity from both sides of the market.
The Transaction at a Glance
Transaction: 0x0d45b2881dc1cb321ee942fec8cf9e523ce9f2a835005006ddbb1cdf36dfdec2
What happened: Buyer 0xb3a1AAD3D35AB7e87052b2AdDd5A11b3c7B63529 purchased 13,275.54 Mavericks shares for 7,434.3024 USDC.
Key mechanism: The CTF Exchange didn’t find a single seller with 13,275 Mavericks. Instead, it:
- Matched with 12 Nuggets buyers (indirectly acquiring Mavericks through splits)
- Bought Mavericks directly from 3 sellers
- Delivered all accumulated Mavericks to the buyer
Result: 14 different liquidity sources filled one large order.
Key Addresses
| Address | Role |
|---|---|
| 0xb3a1AAD3D35AB7e87052b2AdDd5A11b3c7B63529 | Buyer (order placer) |
| 0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E | CTF Exchange contract |
| 0x4D97DCd97eC945f40cF65F87097ACe5EA0476045 | Conditional Tokens contract |
| 0x2791bca1f2de4661ed88a30c99a7a9449aa84174 | USDC.e token (Polygon) |
Assets
| Asset ID (shortened) | Description |
|---|---|
| 0 | USDC (collateral) |
| 101476…318958 | Denver Nuggets token |
| 168573…843422 | Dallas Mavericks token |
Phase 1: Understanding the Transaction Structure
Let’s start at the end and work backwards. Log entry 863 shows the final OrderFilled event:
OrderFilled(
orderHash: 0x4acc54be1dce02f66c9e6e8805dcc47f7703318c45cd935e1a509f02e6df22b3
maker: 0xb3a1AAD3D35AB7e87052b2AdDd5A11b3c7B63529 ← The Mavericks buyer
taker: 0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E ← CTF Exchange
makerAssetId: 0 (USDC)
takerAssetId: 168573...843422 (Mavericks)
makerAmountFilled: 7434302400 (7,434.3024 USDC)
takerAmountFilled: 13275540000 (13,275.54 Mavericks)
fee: 0
)
Log 864 shows the OrdersMatched summary:
OrdersMatched(
takerOrderHash: 0x4acc54be...
takerOrderMaker: 0xb3a1...
makerAssetId: 0 (USDC)
takerAssetId: 168573... (Mavericks)
makerAmountFilled: 7434302400
takerAmountFilled: 13275540000
)
Understanding Maker vs Taker in CTF Exchange
Here’s the crucial insight about how to read these events:
0xb3a1… placed an “active order” (also called the “taker order” in the code) - an aggressive order that crossed the spread and initiated matching. Think of it as: “I want to buy Mavericks NOW at 0.56.”
The exchange then called matchOrders() with:
- takerOrder = 0xb3a1…‘s order (the active/aggressive order)
- makerOrders[] = Array of 14 resting orders on the book (passive liquidity)
The pattern in the logs:
-
Logs 755-862: Multiple OrderFilled events where:
maker= Someone with a resting order (e.g., 0xeAd… wanted to buy Nuggets)taker= 0xb3a1… (the active order that triggered the match)
-
Log 863: Final OrderFilled event where:
maker= 0xb3a1… (gets labeled as “maker” in the summary)taker= CTF Exchange (acted as the aggregator)
-
Log 864: OrdersMatched event (final summary)
Why the confusing labeling?
Even though 0xb3a1… was the taker (active order), they appear as “maker” in the final OrderFilled event. This is because:
- Polymarket gives the taker order maker treatment for fee purposes
- The user pays 0% fees as if they were a passive maker
- The exchange shows up as “taker” in the summary to reflect that it facilitated the match
Key takeaway: If you see many OrderFilled events with the same address as taker, that address placed the active order. All the different makers are resting orders that got matched against it.
Phase 2: Collecting Liquidity
Between log entries 755 and 864, the exchange executed 14 separate fills. Let’s break them down by type.
Type A: Indirect Mavericks via Nuggets Buyers (12 fills)
Here’s the brilliant part: If someone wants to buy Nuggets at 0.44, the exchange can use that to acquire Mavericks at 0.56.
The mechanism:
- Nuggets buyer wants to buy at 0.44
- Exchange deposits USDC into Conditional Tokens contract
- Contract splits USDC into equal Nuggets + Mavericks
- Exchange sends Nuggets to the buyer (they got their 0.44 price)
- Exchange keeps the Mavericks (effectively acquired at 0.56)
Let’s walk through a detailed example (we’ll skip the Approval logs as they’re not essential to understanding the flow):
Detailed Example: Acquiring 1,058 Mavericks
Nuggets Buyer: 0xeAd6EbDF379BDf92D0093EcdD5E346de95BE7f60
Step 1 (Log 757): Buyer sends USDC to exchange
Transfer(
from: 0xeAd... (Nuggets buyer)
to: 0x4bFb... (exchange)
value: 465537600 (465.5376 USDC)
)
Step 2 (Log 759): Exchange sends USDC to Conditional Tokens
Transfer(
from: 0x4bFb... (exchange)
to: 0x4D97... (Conditional Tokens)
value: 1058040000 (1,058.04 USDC)
)
Step 3 (Log 761): Conditional Tokens mints BOTH outcome tokens
TransferBatch(
operator: 0x4bFb... (exchange)
from: 0x0000...0000 (zero address = mint)
to: 0x4bFb... (exchange)
ids: [101476...318958, 168573...843422] ← Both Nuggets AND Mavericks
values: [1058040000, 1058040000] ← Equal amounts
)
Step 4 (Log 762): PositionSplit event confirms the operation
PositionSplit(
stakeholder: 0x4bFb... (exchange)
collateralToken: 0x2791... (USDC)
amount: 1058040000 (1,058.04 USDC)
)
Step 5 (Log 763): Exchange delivers Nuggets to buyer
TransferSingle(
operator: 0x4bFb... (exchange)
from: 0x4bFb... (exchange)
to: 0xeAd... (Nuggets buyer)
id: 101476...318958 (Nuggets)
value: 1058040000 (1,058.04 Nuggets)
)
Step 6 (Log 764): OrderFilled event records the trade
OrderFilled(
maker: 0xeAd... (Nuggets buyer)
taker: 0xb3a1... (Mavericks buyer)
makerAssetId: 0 (USDC) ← What Nuggets buyer is GIVING
takerAssetId: 101476...318958 (Nuggets) ← What Nuggets buyer is RECEIVING
makerAmountFilled: 465537600 (465.5376 USDC)
takerAmountFilled: 1058040000 (1,058.04 Nuggets)
)
The math:
- Nuggets buyer paid: 465.5376 USDC
- Nuggets buyer received: 1,058.04 Nuggets
- Price per Nuggets token: 465.5376 / 1,058.04 = 0.44 USDC ✓
- Exchange kept: 1,058.04 Mavericks
- Effective cost per Mavericks: (1,058.04 - 465.5376) / 1,058.04 = 0.56 USDC ✓
All 12 Nuggets Buyer Fills
The exchange repeated this pattern 12 times with different Nuggets buyers:
| # | Nuggets Buyer | Nuggets Delivered | USDC Received | Mavericks Kept |
|---|---|---|---|---|
| 1 | 0xeAd6… | 1,058.04 | 465.54 | 1,058.04 |
| 2 | 0x7eAb… | 9.94 | 4.37 | 9.94 |
| 3 | 0x9aa8… | 100.00 | 44.00 | 100.00 |
| 4 | 0x9a6C… | 600.00 | 264.00 | 600.00 |
| 5 | 0x5665… | 200.00 | 88.00 | 200.00 |
| 6 | 0xC1E6… | 100.00 | 44.00 | 100.00 |
| 7 | 0x9154… | 25.00 | 11.00 | 25.00 |
| 8 | 0x4707… | 200.00 | 88.00 | 200.00 |
| 9 | 0x8149… | 300.00 | 132.00 | 300.00 |
| 10 | 0xe35c… | 200.00 | 88.00 | 200.00 |
| 11 | 0xdddd… | 50.00 | 22.00 | 50.00 |
| 12 | 0xBA22… | 400.98 | 176.43 | 400.98 |
Total Mavericks accumulated via splits: 3,243.98
Type B: Direct Mavericks Purchases (3 fills)
Some sellers had Mavericks and wanted to sell them directly. This pattern is simpler:
Example: Direct Purchase (Logs 845-847)
Step 1 (Log 845): Seller sends Mavericks to exchange
TransferSingle(
operator: 0x4bFb... (exchange)
from: 0x64E5... (seller)
to: 0x4bFb... (exchange)
id: 168573...843422 (Mavericks)
value: 2000000000 (2,000.00 Mavericks)
)
Step 2 (Log 846): Exchange sends USDC to seller
Transfer(
from: 0x4bFb... (exchange)
to: 0x64E5... (seller)
value: 1120000000 (1,120.00 USDC)
)
Step 3 (Log 847): OrderFilled event
OrderFilled(
maker: 0x64E5... (seller)
taker: 0x4bFb... (exchange)
makerAssetId: 168573...843422 (Mavericks)
takerAssetId: 0 (USDC)
makerAmountFilled: 2000000000 (2,000.00 Mavericks)
takerAmountFilled: 1120000000 (1,120.00 USDC)
)
Price: 1,120 / 2,000 = 0.56 USDC per Mavericks ✓
All 3 Direct Mavericks Purchases
| # | Seller | Mavericks Purchased | USDC Paid |
|---|---|---|---|
| 1 | 0x64E5… | 2,000.00 | 1,120.00 |
| 2 | 0xcAD2… | 4,016.28 | 2,249.12 |
| 3 | 0xcAD2… | 4,016.28 | 2,249.12 |
Total Mavericks from direct purchases: 10,032.56
Phase 3: Final Settlement
The exchange has now accumulated Mavericks from 14 different sources:
- Via splits: 3,243.98 Mavericks
- Via direct purchases: 10,032.56 Mavericks
- Total: 13,276.54 Mavericks
Now it can fill the buyer’s order:
Log 862: Exchange transfers Mavericks to buyer
TransferSingle(
operator: 0x4bFb... (exchange)
from: 0x4bFb... (exchange)
to: 0xb3a1... (buyer)
id: 168573...843422 (Mavericks)
value: 13275540000 (13,275.54 Mavericks)
)
Log 863: OrderFilled event for the taker order (summary)
OrderFilled(
maker: 0xb3a1... (taker gets "maker" treatment for fees)
taker: 0x4bFb... (exchange)
makerAmountFilled: 7434302400 (7,434.3024 USDC)
takerAmountFilled: 13275540000 (13,275.54 Mavericks)
)
Log 864: OrdersMatched summary event
OrdersMatched(
takerOrderHash: 0x4acc54be... ← The active order (0xb3a1...)
takerOrderMaker: 0xb3a1... ← Owner of the active order
makerAssetId: 0 (USDC)
takerAssetId: 168573...843422 (Mavericks)
makerAmountFilled: 7434302400
takerAmountFilled: 13275540000
)
Understanding these final events:
- OrdersMatched contains the taker order hash and taker order maker
- This definitively identifies 0xb3a1… as the active/aggressive trader
- The final OrderFilled gives 0xb3a1… “maker” status to award 0% fees
- Note: OrdersMatched does NOT list individual maker orders - only totals
Final price check: 7,434.3024 / 13,275.54 = 0.5599 USDC per Mavericks ✓
The Math: Why This Works
Price Consistency
Every trade in this transaction occurred at consistent prices:
Nuggets side:
- All 12 buyers paid ~0.44 USDC per Nuggets
Mavericks side:
- All 3 direct sellers received ~0.56 USDC per Mavericks
- Final buyer paid ~0.56 USDC per Mavericks
Sum check: 0.44 + 0.56 = 1.00 USDC ✓
Split Economics
When the exchange splits USDC:
Split 100 USDC
→ 100 Nuggets + 100 Mavericks
Deliver 100 Nuggets to buyer at 0.44
→ Receive 44 USDC from Nuggets buyer
Keep 100 Mavericks
→ Worth 56 USDC (at market price)
Net cost to acquire 100 Mavericks:
100 USDC (deposited) - 44 USDC (recovered) = 56 USDC
This is exactly the same as buying Mavericks directly at 0.56.
Total Settlement Verification
USDC flow:
- Final buyer deposited: 7,434.3024 USDC
- Exchange spent on splits: ~3,794.38 USDC (to create both tokens)
- Exchange spent on direct Mavericks buys: ~5,618.24 USDC
- Exchange received from Nuggets buyers: ~1,426.34 USDC (buyers paying for Nuggets)
- Net exchange cost: 3,794.38 + 5,618.24 - 1,426.34 ≈ 7,434.3024 USDC ✓
Token flow:
- Buyer received: 13,275.54 Mavericks
- Exchange accumulated: 13,276.54 Mavericks (small rounding)
- Match: ✓
Transaction Flow Diagram
graph TD
A[Mavericks Buyer: 0xb3a1...] -->|Places limit order| B[CTF Exchange: 0x4bFb...]
subgraph "Nuggets Buyers (12)"
C1[Buyer 1: 0xeAd6...]
C2[Buyer 2: 0x7eAb...]
C3[Buyers 3-12: ...]
end
subgraph "Mavericks Sellers (3)"
D1[Seller 1: 0x64E5...]
D2[Sellers 2-3: 0xcAD2...]
end
C1 -->|Wants Nuggets @ 0.44| B
C2 -->|Wants Nuggets @ 0.44| B
C3 -->|Want Nuggets @ 0.44| B
D1 -->|Sells Mavericks @ 0.56| B
D2 -->|Sell Mavericks @ 0.56| B
B -->|Deposits USDC| E[Conditional Tokens: 0x4D97...]
E -->|Mints Nuggets + Mavericks| B
B -->|Delivers Nuggets| C1
B -->|Delivers Nuggets| C2
B -->|Delivers Nuggets| C3
C1 -->|Pays USDC for Nuggets| B
C2 -->|Pays USDC for Nuggets| B
C3 -->|Pay USDC for Nuggets| B
B -->|Accumulates 13,276 Mavericks| B
B -->|Delivers all Mavericks| A
A -->|Pays 7,434 USDC| B
The Split Mechanism
graph LR
A[USDC] -->|Deposit| B[Conditional Tokens Contract]
B -->|Split 1:1| C[Nuggets Token]
B -->|Split 1:1| D[Mavericks Token]
C -->|Deliver @ 0.44| E[Nuggets Buyer]
D -->|Keep @ 0.56| F[CTF Exchange]
E -->|Pays USDC| F
F -->|Net: Acquired Mavericks @ 0.56| G[Accumulation Pool]
Key Insights
Why the Exchange Acts as “Taker” in the Final Event
Traditional exchange matching:
- User A (maker): Places resting limit order
- User B (taker): Places aggressive order that crosses
- Direct match, taker pays fees
Polymarket with liquidity aggregation:
- User A (taker): Places aggressive order “I want Mavericks at 0.56 NOW”
- Exchange: Finds 14 resting maker orders and aggregates them
- Final event: Exchange appears as “taker”, User A appears as “maker”
- User A pays 0% fees (gets maker treatment)
The trick: Even though User A was the aggressive/active trader (true taker), they get labeled as “maker” in the final OrderFilled event for fee optimization. The exchange shows up as “taker” in that summary event.
Evidence User A was the taker:
- Their address appears as
takerin all 14 intermediate OrderFilled events - The OrdersMatched event has their order hash as
takerOrderHash - The code comment says
takerOrderis “the active order”
The exchange acts as an intermediary but never takes a position. It immediately passes through all tokens and USDC.
The Power of Splits
Without split operations:
- Market depth limited to direct Mavericks sellers
- Large orders would have high slippage
- Liquidity fragmented between YES and NO sides
With split operations:
- Every Nuggets buyer provides Mavericks liquidity indirectly
- Market depth effectively doubled
- Arbitrage keeps prices aligned
- Better execution for large orders
Gas Efficiency
This transaction bundled:
- 12 Nuggets buyer fills (6 key steps each)
- 3 Mavericks seller fills (3 steps each)
- 1 final settlement (3 logs)
- Various approvals and transfers
Total: 111 logs in a single atomic transaction
Benefits:
- User signs once
- All-or-nothing execution (no partial fills across multiple blocks)
- Exchange handles complexity
- Gas cost shared across all fills
How to Read OrderFilled Events
The OrderFilled event is the key to understanding Polymarket trades:
event OrderFilled(
bytes32 orderHash, // Unique identifier for the order
address indexed maker, // Who placed the resting order
address indexed taker, // Who executed against it
uint256 makerAssetId, // What the maker is SELLING
uint256 takerAssetId, // What the maker is BUYING
uint256 makerAmountFilled, // Amount of maker's asset sold
uint256 takerAmountFilled, // Amount received by maker
uint256 fee // Fee paid (usually 0 for makers)
);
How the Contract Emits These Events
Here’s the actual Solidity code from Polymarket’s CTF Exchange that proves how maker/taker labels are assigned:
The matchOrders function signature (from CTFExchange.sol):
/// @notice Matches a taker order against a list of maker orders
/// @param takerOrder - The active order to be matched
/// @param makerOrders - The array of maker orders to be matched against the active order
/// @param takerFillAmount - The amount to fill on the taker order
/// @param makerFillAmounts - The array of amounts to fill on the maker orders
function matchOrders(
Order memory takerOrder,
Order[] memory makerOrders,
uint256 takerFillAmount,
uint256[] memory makerFillAmounts
) external nonReentrant onlyOperator notPaused {
_matchOrders(takerOrder, makerOrders, takerFillAmount, makerFillAmounts);
}
Note the comment: “The active order to be matched” - this is the aggressive/crossing order.
Emitting intermediate fills (from Trading.sol _fillMakerOrder):
function _fillMakerOrder(Order memory takerOrder, Order memory makerOrder, uint256 fillAmount) internal {
// ... validation and calculation logic ...
emit OrderFilled(
orderHash,
makerOrder.maker, // ← Resting order's owner
takerOrder.maker, // ← Active order's owner
makerAssetId,
takerAssetId,
making,
taking,
fee
);
}
Key insight: takerOrder.maker is the person who placed the active/aggressive order. They appear as taker in the emitted event.
Emitting the final summary (from Trading.sol _matchOrders):
function _matchOrders(...) internal {
// ... execute all maker fills ...
// Final event where taker order gets "maker" treatment
emit OrderFilled(
orderHash,
takerOrder.maker, // ← Active order now labeled as "maker"
address(this), // ← Exchange is "taker"
makerAssetId,
takerAssetId,
making,
taking,
fee
);
emit OrdersMatched(
orderHash, // ← takerOrder's hash
takerOrder.maker, // ← The active trader
makerAssetId,
takerAssetId,
making,
taking
);
}
The OrdersMatched event definition:
event OrdersMatched(
bytes32 indexed takerOrderHash, // Hash of the active/taker order
address indexed takerOrderMaker, // Owner of the taker order
uint256 makerAssetId,
uint256 takerAssetId,
uint256 makerAmountFilled,
uint256 takerAmountFilled
);
This proves:
- In intermediate fills:
takerOrder.maker→ appears astakerin OrderFilled events - In final summary:
takerOrder.maker→ appears asmakerin OrderFilled (fee optimization) - OrdersMatched definitively identifies the active order via
takerOrderHash
Applying this to our transaction:
// Intermediate fill (one of 14)
OrderFilled(
maker: 0xeAd... (resting order)
taker: 0xb3a1... (takerOrder.maker - the active order)
...
)
// Final summary
OrderFilled(
maker: 0xb3a1... (takerOrder.maker - relabeled for fees)
taker: 0x4bFb... (address(this) - the Exchange)
...
)
// Confirmation
OrdersMatched(
takerOrderHash: 0x4acc54be... (0xb3a1...'s order hash)
takerOrderMaker: 0xb3a1...
...
)
This is why you see the pattern: repeated taker address in multiple OrderFilled events = active trader.
Interpretation Rules
Rule 1: makerAssetId is what the maker is giving up (selling)
Rule 2: takerAssetId is what the maker is receiving (buying)
Rule 3: When you see multiple OrderFilled events with the same taker address, that address placed the active/aggressive order
Rule 4: In those events, the different maker addresses are resting orders that were matched
Rule 5: The final OrderFilled with taker = CTF Exchange is the summary where the active order gets “maker treatment” for fee purposes
Critical Pattern: Identifying the Active Order
In our transaction example:
Logs 755-862 (intermediate fills):
OrderFilled(maker: 0xeAd..., taker: 0xb3a1..., ...)
OrderFilled(maker: 0x7eAb..., taker: 0xb3a1..., ...)
OrderFilled(maker: 0x9aa8..., taker: 0xb3a1..., ...)
... (14 total fills, all with taker = 0xb3a1...)
Log 863 (final summary):
OrderFilled(maker: 0xb3a1..., taker: 0x4bFb...(Exchange), ...)
Log 864 (OrdersMatched):
OrdersMatched(
takerOrderHash: 0x4acc54be... (0xb3a1...'s order hash)
takerOrderMaker: 0xb3a1...
makerAmountFilled: 7434302400
takerAmountFilled: 13275540000
...
)
What this tells us:
- 0xb3a1… appears as
takerin 14 OrderFilled events → They placed the active order - All the different
makeraddresses (0xeAd…, 0x7eAb…, etc.) → Resting orders on the book - Final event labels 0xb3a1… as “maker” → Fee optimization (they pay 0%)
- OrdersMatched definitively confirms 0xb3a1… was the taker via
takerOrderMakerfield
Example Interpretation
OrderFilled(
maker: 0xABC... (user)
taker: 0x4bFb... (exchange)
makerAssetId: 0 (USDC)
takerAssetId: 123456...789 (Some outcome token)
makerAmountFilled: 1000000000 (1,000 USDC with 6 decimals)
takerAmountFilled: 2000000000 (2,000 tokens with 6 decimals)
)
Translation: User 0xABC placed a limit order to buy 2,000 outcome tokens for 1,000 USDC (price: 0.50 per token). The exchange aggregated liquidity and filled it.
Practical Implications
For Traders
What you see:
- Place one limit order
- Get filled instantly
- Pay 0% maker fee
What’s happening behind the scenes:
- Your order might be filled by dozens of other traders
- The exchange is splitting tokens, aggregating from both sides
- You benefit from deeper liquidity than apparent from the order book
Pro tip: Large orders get better execution than expected because the exchange can tap into both outcome token markets.
For Developers
When parsing Polymarket data:
-
Identifying the active trader
- Look for repeated taker addresses across multiple OrderFilled events
- That address placed the aggressive/active order
- Don’t count the final OrderFilled where taker = Exchange - that’s the summary
-
One user trade ≠ one OrderFilled event
- A single active order generates N+1 OrderFilled events (N makers + 1 summary)
- The OrdersMatched event confirms which order was the taker
takerOrderHashandtakerOrderMakerin OrdersMatched = the active order and its owner- OrdersMatched shows totals but does NOT list individual maker orders
-
Track conditional token splits
- PositionSplit events indicate liquidity creation
- TransferBatch events show both outcome tokens minting
- These aren’t user trades - they’re liquidity operations
-
Calculate true P&L carefully
- Sum all USDC in/out for a wallet
- Sum all outcome token positions
- Account for token merges back to USDC
- Don’t double-count split operations
-
Reading OrdersMatched events
- This is the authoritative summary identifying the taker order
takerOrderHash= the active/aggressive order’s hashtakerOrderMaker= the active/aggressive order’s owner- Contains totals (
makerAmountFilled,takerAmountFilled) but NOT individual maker order details - One OrdersMatched per
matchOrders()call
For Analysts
Common mistakes:
❌ Wrong: Count every OrderFilled as a unique trade ✓ Right: Use OrdersMatched events to count unique matches (1 taker + N makers = 1 match event)
❌ Wrong: Treat the exchange as a counterparty ✓ Right: Exchange is a facilitator, not a position holder
❌ Wrong: Ignore PositionSplit events ✓ Right: These show when new liquidity enters the system
❌ Wrong: Can’t tell who was aggressive vs passive ✓ Right: Look for repeated taker addresses - that’s the aggressive order
Volume calculations:
- Use OrdersMatched events for accurate trade counting
- Or count unique taker addresses in OrderFilled events (but beware the final summary event)
- Don’t sum all OrderFilled events (massive double counting)
- Consider only one side (USDC or token, not both)
Taker vs Maker identification:
- Taker = appears as
takerin multiple OrderFilled events - Makers = unique
makeraddresses in those same events - OrdersMatched confirms the taker via
takerOrderHashandtakerOrderMakerfields - To find all makers in a match, look at the OrderFilled events with that takerOrderHash’s owner as taker
Conclusion
What initially looked like chaos - 111 log entries for a single trade - is actually a sophisticated liquidity aggregation system.
Key takeaways:
-
Conditional token splits double market depth - Every Nuggets buyer indirectly provides Mavericks liquidity through the split mechanism
-
The exchange never takes positions - It instantly aggregates and delivers, acting as a facilitator
-
OrdersMatched events identify the taker - The
takerOrderHashandtakerOrderMakerfields definitively identify the active/aggressive order -
Repeated taker addresses reveal the active trader - If you see the same address as
takerin many OrderFilled events, they placed the aggressive order -
“Maker” status is a fee optimization - The aggressive order gets labeled as “maker” in the final summary to award 0% fees
-
Reading DeFi transactions is systematic - Once you know the patterns, 100+ logs become interpretable
-
Polymarket’s architecture is elegant - By leveraging the CTF, they created a liquid market for binary outcomes with minimal friction
The next time you see a complex DeFi transaction, don’t be intimidated. Look for:
- Key contract addresses
- Token transfer patterns
- Event signatures
- Mathematical relationships
Every blockchain transaction tells a story. Now you know how to read this one.
Further Reading
- Gnosis Conditional Tokens Framework
- Polymarket Documentation
- CTF Exchange Contract
- Understanding Binary Market Pricing
All data in this article comes from on-chain transaction logs. The complete analysis code is available in our research repository. Always verify transactions independently when analyzing DeFi protocols.