Splitting & Merging Polymarket CTF Positions Programmatically
A working guide to Polymarket conditional tokens split merge calls on the Gnosis CTF, and how bots trade the 1.00-sum invariant across binary outcomes.
Every Polymarket position is an ERC-1155 token minted by the Gnosis Conditional Token Framework, and the whole edge on that venue comes from one fact: a full set of outcome tokens for a condition always redeems for exactly 1 USDC. If you hold one YES and one NO for the same market, you hold collateral. The CTF lets you convert between the two forms — collateral and outcome tokens — at will, with splitPosition and mergePositions. Understanding those two calls, and the invariant that ties them together, is the difference between paying the CLOB spread twice and minting your inventory for free.
The three calls that matter
The CTF contract on Polygon (0x4D97DCd97eC945f40cF65F87097ACe5EA0476045) exposes a small surface. Three functions do almost all the work:
splitPosition— burn N units of collateral (USDC), mint N units of each outcome token in a partition. One USDC in, one YES + one NO out.mergePositions— the inverse. Burn one of each outcome token, get one USDC back.redeemPositions— after the UMA oracle reports, burn your held outcome tokens for their share of collateral. The winning outcome pays 1, the losing outcome pays 0.
The signature engineers trip over is splitPosition:
function splitPosition(
IERC20 collateralToken, // USDC.e on Polygon
bytes32 parentCollectionId, // 0x00..00 for top-level markets
bytes32 conditionId,
uint[] partition, // [1, 2] for a binary market
uint amount // in collateral base units (6 decimals)
) external;
The partition is the part people get wrong. For a binary market the outcome slot count is 2, and the partition [1, 2] means "index set 0b01 and index set 0b10" — YES and NO as disjoint bitmasks that cover the full set. Pass [1, 2] and you split into two ERC-1155 positions. Pass a partition that doesn't cover every slot exactly once and the call reverts, which is the contract enforcing the sum invariant for you.
The conditionId is derived, not looked up: keccak256(oracle, questionId, outcomeSlotCount). Polymarket's oracle here is the UMA adapter, and if you're building anything that touches settlement you want to understand how UMA's optimistic oracle actually resolves a market before you assume a redeem will pay what you expect.
Why split and merge exist as a trader's tool
On paper, splitting is just "deposit 1 USDC, get 1 YES + 1 NO." That sounds useless until you look at the order book. The CLOB quotes YES and NO independently, and their mid prices don't always sum to 1.00. That gap is the entire game.
Two directions to trade it:
Mint-and-sell (when YES + NO asks sum above 1.00). Call splitPosition for, say, 10,000 USDC. You now hold 10,000 YES and 10,000 NO that cost you exactly 1.00 each in aggregate. If the book will pay 0.62 for YES and 0.41 for NO, you sell both and collect 1.03 per set — a 3-cent risk-free spread minus fees and gas. This is the bread and butter of a Polymarket spread bot: watch both sides, and when the combined bid crosses 1.00, mint fresh sets straight into the book instead of buying paper that may not be there.
Buy-and-merge (when YES + NO asks sum below 1.00). The mirror trade. If you can buy YES at 0.48 and NO at 0.49, that's 0.97 for a set you can immediately mergePositions back into 1.00 USDC. You've bought a complete set at a discount and unwound it at par. A Polymarket arbitrage bot lives on exactly this loop, and the merge leg is what lets it recycle capital instead of sitting on a directional position it never wanted.
The invariant is the referee. Because a full set is always worth 1.00 at settlement regardless of which side wins, neither trade carries outcome risk. Your only exposures are execution (the far leg fills at a worse price than quoted), gas, and the taker fees Polymarket charges on the CLOB fills.
The gotchas nobody warns you about
Gas amortization. A split or merge is a single Polygon transaction, but you're also paying gas on every CLOB fill that unwinds the set. At 10,000 units the per-unit gas is noise; at 200 units it can eat the whole 2-cent edge. Bots batch: mint a large set once, then work it into the book over many small fills rather than round-tripping the CTF per trade.
Approvals and the two-token dance. splitPosition needs a USDC approval to the CTF; selling the minted ERC-1155s needs an approval to the CTF Exchange, which is a different contract. Forgetting the second one means your split lands and then your sell reverts, leaving you holding an unhedged full set. Set both approvals to type(uint256).max once at deploy time.
Neg-risk markets. Multi-outcome "one winner" markets (e.g. an election with five candidates) use Polymarket's neg-risk adapter, not a plain binary condition. The full-set invariant still holds across all outcomes, but you split against the adapter and the partition has more than two index sets. Treat these as a separate code path.
Rounding at 6 decimals. USDC is 6-decimal. If your position sizing math produces fractional base units, the truncation silently changes how many tokens you mint versus how much collateral you burned. Always size in whole base units and reconcile the ERC-1155 balances after the call, not before.
Settlement timing. redeemPositions only works after the condition is reported on-chain. Between market close and the oracle report there's a window where your tokens are frozen in value but not yet redeemable. If you're carrying inventory into resolution, that lag is real and your capital is locked through it.
Where this sits in a real stack
Split/merge is a primitive, not a strategy. The strategies wrap it. A Polymarket market maker uses minting to source inventory it then quotes on both sides, skewing prices as it accumulates one outcome. The mechanics of posting and cancelling those quotes — and the EIP-712 signing that authorizes them — are covered in the order-signing deep dive, and the broader quoting logic in the CLOB market-making guide.
The pattern to internalize: the CLOB gives you price, the CTF gives you a fixed 1.00 anchor, and every dollar of edge is the distance between them. A bot that can mint into a rich book and merge out of a cheap one is arbitraging its own venue's failure to keep two independent order books summing to par. That mispricing is small and it closes fast, which is why this is a latency-and-batching problem, not a signal problem.
If you want a system that watches the split/merge spread and mints straight into the book, our Polymarket arbitrage bot work is built around exactly this loop.
Need a bot like this built?
We design, build and run trading bots on Solana, Hyperliquid and Polymarket.
Start a projectMore from the blog
Public vs Paid RPC: How to Run a Fair Benchmark
A fair comparison of public and paid RPC endpoints must account for quotas, workload, freshness and support guarantees.
Read articleRPC Benchmark Percentiles Explained: p50, p95 and p99
Why averages hide the latency spikes that break trading bots, wallets and production blockchain applications.
Read articleHyperliquid REST vs WebSocket Benchmark: What to Measure
A benchmark plan for Hyperliquid market data that separates request latency from streaming freshness and recovery.
Read article