All articles
Polymarket·May 14, 2026·6 min read

Gnosis Conditional Tokens Framework: The Engine Behind Polymarket

How Gnosis CTF's ERC-1155 outcome tokens, splitPosition and mergePositions power every Polymarket market under the hood.

Every Polymarket trade you've ever placed touched a contract called ConditionalTokens.sol, and most traders have never opened it. That's the Gnosis Conditional Tokens Framework (CTF) — a general-purpose contract for turning a yes/no (or 1-of-N) question into tradeable ERC-1155 tokens. Polymarket didn't write it from scratch; it forked Gnosis's 2020-era framework, wired it to USDC on Polygon, and built a CLOB on top. If you're building anything that touches Polymarket programmatically — a maker bot, an arb scanner, a settlement watcher — you eventually have to understand this layer, because it's where your PnL actually gets minted and burned.

The core object: a condition, not a market

CTF doesn't know what "market" means. It knows about a condition, identified by:

conditionId = keccak256(oracle, questionId, outcomeSlotCount)
  • oracle is the address allowed to report the outcome — for Polymarket this is the UMA-based adapter, not UMA's core contract directly. The resolution mechanics behind that adapter are their own rabbit hole, covered in how UMA's optimistic oracle resolves Polymarket markets.
  • questionId is a hash tied to the market question, set when the market is prepared.
  • outcomeSlotCount is 2 for a binary market (YES/NO), or N for a categorical one.

A condition on its own is inert. It becomes tradeable only when someone splits collateral against it.

Splitting: collateral becomes outcome tokens

splitPosition locks collateral (USDC) into the contract and mints one ERC-1155 token per outcome slot, each representing a claim on that slice of the payout. Mint 100 USDC worth of splits on a binary condition and you get 100 YES tokens plus 100 NO tokens — a complete set. This is the only way new outcome tokens enter existence; there's no separate minting function, no admin key. Collateral in, complete set out, always 1:1.

Each outcome token's identity — its positionId — is derived deterministically:

collectionId = keccak256(parentCollectionId, conditionId, indexSet)
positionId   = keccak256(collateralToken, collectionId)

indexSet is a bitmask over outcome slots. For a binary market, YES is index set 0b01, NO is 0b10. This bitmask machinery is overkill for a simple binary market but it's what lets CTF support combinatorial positions — splitting on a condition nested inside another condition's outcome. Polymarket mostly ignores that power; almost every market is a flat binary or categorical condition, not a tree. But the ERC-1155 positionId for even a plain YES token is a hash of an oracle address, a question hash, and a bitmask — which is why you can't guess a token ID by eyeballing it, you have to compute it or read it off the subgraph.

Worked example

Say a market resolves via oracle 0xAdapter, question hash 0xQ, 2 outcome slots. You want 50 USDC of exposure split evenly:

const conditionId = keccak256(
  ["address","bytes32","uint256"],
  [adapter, questionId, 2]
);

// parentCollectionId = 0x0 (root position, not nested)
await ctf.splitPosition(
  usdc.address,
  ethers.constants.HashZero,
  conditionId,
  [1, 2],        // index sets: YES = 1, NO = 2
  parseUnits("50", 6)
);

That call pulls 50 USDC from you and credits your address with 50 YES-position tokens and 50 NO-position tokens (ERC-1155, same contract, different positionIds). You now hold a complete set worth exactly 50 USDC at redemption regardless of outcome — you've paid nothing for directional exposure, you've just repackaged collateral.

Merging: the reverse, and where arbitrage lives

mergePositions does the opposite: burn one full set (equal amounts of every outcome token) and get the collateral back. This is the mechanism that keeps YES + NO prices honest. If the order book prices YES at 0.61 and NO at 0.36, their sum is 0.97 — buy both for 0.97 combined, merge them, redeem 1.00 USDC, pocket 0.03 per set minus gas and fees. Run that at size and it's a real, if thin, edge; it's also exactly the loop a Polymarket arbitrage bot is built to catch continuously across dozens of markets before anyone else's bot does.

The inverse trade works too: if YES + NO sum above 1.00, split fresh collateral into a complete set and sell both legs into the book above cost basis. Market makers use this constantly for inventory — rather than warehousing directional risk, a Polymarket market maker can split on demand to source the exact side it's short, quote both sides of the book, and merge back whenever inventory drifts toward a complete set again. That's cheaper than buying the missing leg off the book and it doesn't move the price against you.

Redemption: how the oracle payout becomes cash

Once the oracle calls reportPayouts with payout numerators (e.g., [1, 0] for YES winning), redeemPositions lets any holder burn their winning tokens for collateral at the reported ratio, and losing tokens redeem for zero. This is separate from Polymarket's UI-level "claim" flow, but it's the same call underneath. Worth remembering: nothing forces you to redeem immediately, and outcome tokens keep their ERC-1155 balance and technically remain transferable even after resolution — you just won't get a counterparty willing to pay more than the settled value for them.

Why the exchange layer still needs all this

Polymarket's CLOB matches orders off-chain and settles on-chain through a wrapper (CTFExchange) that calls into ConditionalTokens for the actual token movement. Understanding split/merge explains a detail that trips people up when comparing venues: Polymarket's book quotes and fills feel like a normal exchange, but liquidity for a fresh market can be sourced from thin air via splitPosition, not just from existing holders — a structural difference from how order flow behaves on the AMM-style venues discussed in our comparison of CLOB and AMM prediction market designs. It also explains capital efficiency differences worth weighing if you're benchmarking venues, which we get into in Polymarket vs. Kalshi for algorithmic traders.

The gotchas that bite bot builders

  • Approvals are per-collateral, not per-market. One USDC approval to the CTF contract covers splitting on every condition; don't re-approve per market, you're wasting gas and adding a tx to your critical path.
  • Complete sets must be exact. mergePositions reverts if your balances across the index sets aren't equal — a partial fill on one leg of your merge trade will strand the other leg until you rebalance.
  • positionId isn't the same across markets even for "YES". Every condition has its own token IDs; don't hardcode them, derive or fetch them per market.
  • Negative-risk / multi-outcome markets on Polymarket use a separate adapter contract to convert between several binary conditions and one N-way market, adding another indirection layer on top of vanilla CTF — don't assume categorical markets split like binary ones.

If you're building execution logic that needs to source liquidity, hedge inventory, or catch mispriced complements, the split/merge mechanics aren't optional background — they're the primitive your bot should be calling directly instead of routing everything through the order book. That's the layer we build into custody-aware Polymarket arbitrage bots so the merge-for-profit loop runs without a human watching the spread.

Need a bot like this built?

We design, build and run trading bots on Solana, Hyperliquid and Polymarket.

Start a project
#Polymarket#Gnosis CTF#ERC-1155#Smart Contracts#Prediction Markets