Decoding Polymarket: How a 7,400 USDC Trade Generated 111 Blockchain Events

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:

ScenarioMavericks PriceNuggets PriceSum
Even odds0.50 USDC0.50 USDC1.00
Mavs favored0.56 USDC0.44 USDC1.00
Nuggets favored0.30 USDC0.70 USDC1.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:

  1. Matched with 12 Nuggets buyers (indirectly acquiring Mavericks through splits)
  2. Bought Mavericks directly from 3 sellers
  3. Delivered all accumulated Mavericks to the buyer

Result: 14 different liquidity sources filled one large order.

Key Addresses

AddressRole
0xb3a1AAD3D35AB7e87052b2AdDd5A11b3c7B63529Buyer (order placer)
0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982ECTF Exchange contract
0x4D97DCd97eC945f40cF65F87097ACe5EA0476045Conditional Tokens contract
0x2791bca1f2de4661ed88a30c99a7a9449aa84174USDC.e token (Polygon)

Assets

Asset ID (shortened)Description
0USDC (collateral)
101476…318958Denver Nuggets token
168573…843422Dallas 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:

  1. 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)
  2. Log 863: Final OrderFilled event where:

    • maker = 0xb3a1… (gets labeled as “maker” in the summary)
    • taker = CTF Exchange (acted as the aggregator)
  3. 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:

  1. Nuggets buyer wants to buy at 0.44
  2. Exchange deposits USDC into Conditional Tokens contract
  3. Contract splits USDC into equal Nuggets + Mavericks
  4. Exchange sends Nuggets to the buyer (they got their 0.44 price)
  5. 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 BuyerNuggets DeliveredUSDC ReceivedMavericks Kept
10xeAd6…1,058.04465.541,058.04
20x7eAb…9.944.379.94
30x9aa8…100.0044.00100.00
40x9a6C…600.00264.00600.00
50x5665…200.0088.00200.00
60xC1E6…100.0044.00100.00
70x9154…25.0011.0025.00
80x4707…200.0088.00200.00
90x8149…300.00132.00300.00
100xe35c…200.0088.00200.00
110xdddd…50.0022.0050.00
120xBA22…400.98176.43400.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

#SellerMavericks PurchasedUSDC Paid
10x64E5…2,000.001,120.00
20xcAD2…4,016.282,249.12
30xcAD2…4,016.282,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 taker in all 14 intermediate OrderFilled events
  • The OrdersMatched event has their order hash as takerOrderHash
  • The code comment says takerOrder is “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:

  1. In intermediate fills: takerOrder.maker → appears as taker in OrderFilled events
  2. In final summary: takerOrder.maker → appears as maker in OrderFilled (fee optimization)
  3. 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 taker in 14 OrderFilled events → They placed the active order
  • All the different maker addresses (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 takerOrderMaker field

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:

  1. 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
  2. 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
    • takerOrderHash and takerOrderMaker in OrdersMatched = the active order and its owner
    • OrdersMatched shows totals but does NOT list individual maker orders
  3. 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
  4. 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
  5. Reading OrdersMatched events

    • This is the authoritative summary identifying the taker order
    • takerOrderHash = the active/aggressive order’s hash
    • takerOrderMaker = 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 taker in multiple OrderFilled events
  • Makers = unique maker addresses in those same events
  • OrdersMatched confirms the taker via takerOrderHash and takerOrderMaker fields
  • 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:

  1. Conditional token splits double market depth - Every Nuggets buyer indirectly provides Mavericks liquidity through the split mechanism

  2. The exchange never takes positions - It instantly aggregates and delivers, acting as a facilitator

  3. OrdersMatched events identify the taker - The takerOrderHash and takerOrderMaker fields definitively identify the active/aggressive order

  4. Repeated taker addresses reveal the active trader - If you see the same address as taker in many OrderFilled events, they placed the aggressive order

  5. “Maker” status is a fee optimization - The aggressive order gets labeled as “maker” in the final summary to award 0% fees

  6. Reading DeFi transactions is systematic - Once you know the patterns, 100+ logs become interpretable

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


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.