Skip to main content
Market makers need outcome tokens on both sides to quote a market. The three core inventory operations are splitting pUSD into YES/NO token pairs, merging pairs back into pUSD, and redeeming winning tokens after resolution — all executed gaslessly through the Relayer Client.
For a full breakdown of how the Conditional Token Framework works, see CTF Overview. This page focuses on the MM workflow using the Relayer Client.

Splitting pUSD into Tokens

Split converts pUSD into equal amounts of YES and NO tokens — creating the inventory you need to quote both sides of a market.
import { ethers } from "ethers";
import { Interface } from "ethers/lib/utils";
import { RelayClient, Transaction } from "@polymarket/builder-relayer-client";

const CTF_ADDRESS = "0x4D97DCd97eC945f40cF65F87097ACe5EA0476045";
const pUSD_ADDRESS = "0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB";

const ctfInterface = new Interface([
  "function splitPosition(address collateralToken, bytes32 parentCollectionId, bytes32 conditionId, uint[] partition, uint amount)",
]);

// Split $1000 pUSD into YES/NO tokens
const amount = ethers.utils.parseUnits("1000", 6); // pUSD has 6 decimals

const splitTx: Transaction = {
  to: CTF_ADDRESS,
  data: ctfInterface.encodeFunctionData("splitPosition", [
    pUSD_ADDRESS, // collateralToken
    ethers.constants.HashZero, // parentCollectionId (always zero for Polymarket)
    conditionId, // conditionId from market
    [1, 2], // partition: [YES, NO]
    amount,
  ]),
  value: "0",
};

const response = await client.execute([splitTx], "Split pUSD into tokens");
const result = await response.wait();
console.log("Split completed:", result?.transactionHash);
After splitting 1000 pUSD, you receive 1000 YES tokens and 1000 NO tokens. Your pUSD balance decreases by 1000.

Merging Tokens to pUSD

Merge converts equal amounts of YES and NO tokens back into pUSD — useful for reducing exposure, exiting a market, or freeing up capital.
const ctfInterface = new Interface([
  "function mergePositions(address collateralToken, bytes32 parentCollectionId, bytes32 conditionId, uint[] partition, uint amount)",
]);

// Merge 500 YES + 500 NO back to 500 pUSD
const amount = ethers.utils.parseUnits("500", 6);

const mergeTx: Transaction = {
  to: CTF_ADDRESS,
  data: ctfInterface.encodeFunctionData("mergePositions", [
    pUSD_ADDRESS,
    ethers.constants.HashZero,
    conditionId,
    [1, 2],
    amount,
  ]),
  value: "0",
};

const response = await client.execute([mergeTx], "Merge tokens to pUSD");
await response.wait();
After merging 500 of each, your YES and NO balances decrease by 500 and your pUSD balance increases by 500.

Redeeming After Resolution

Once a market resolves, redeem winning tokens for pUSD. Each winning token is worth 1losingtokensredeemfor1 — losing tokens redeem for 0.

Check Resolution Status

const market = await clobClient.getMarket(conditionId);
if (market.closed) {
  const winningToken = market.tokens.find((t) => t.winner);
  console.log("Winning outcome:", winningToken?.outcome);
}

Redeem Winning Tokens

const ctfInterface = new Interface([
  "function redeemPositions(address collateralToken, bytes32 parentCollectionId, bytes32 conditionId, uint[] indexSets)",
]);

const redeemTx: Transaction = {
  to: CTF_ADDRESS,
  data: ctfInterface.encodeFunctionData("redeemPositions", [
    pUSD_ADDRESS,
    ethers.constants.HashZero,
    conditionId,
    [1, 2], // Redeem both YES and NO (only winners pay out)
  ]),
  value: "0",
};

const response = await client.execute([redeemTx], "Redeem winning tokens");
await response.wait();

Negative Risk Markets

Multi-outcome markets use the Neg Risk CTF Exchange and Neg Risk Adapter. Split and merge work the same way, but use different contract addresses:
const NEG_RISK_CTF_EXCHANGE = "0xe2222d279d744050d28e00520010520000310F59";
const NEG_RISK_ADAPTER = "0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296";
See Negative Risk Markets for details on how multi-outcome token mechanics differ.

Inventory Strategies

Before Quoting

  1. Check market metadata via the Gamma API
  2. Split sufficient pUSD to cover your expected quoting size
  3. Set token approvals if not already done (see Getting Started)

During Trading

  • Skew quotes when inventory becomes imbalanced on one side
  • Merge excess tokens to free up capital for other markets
  • Split more when inventory on either side runs low

After Resolution

  1. Cancel all open orders in the market
  2. Wait for resolution to complete
  3. Redeem winning tokens
  4. Merge any remaining YES/NO pairs

Batch Operations

Execute multiple inventory operations in a single relayer call for efficiency:
const transactions: Transaction[] = [
  // Split on Market A
  {
    to: CTF_ADDRESS,
    data: ctfInterface.encodeFunctionData("splitPosition", [
      pUSD_ADDRESS,
      ethers.constants.HashZero,
      conditionIdA,
      [1, 2],
      ethers.utils.parseUnits("1000", 6),
    ]),
    value: "0",
  },
  // Split on Market B
  {
    to: CTF_ADDRESS,
    data: ctfInterface.encodeFunctionData("splitPosition", [
      pUSD_ADDRESS,
      ethers.constants.HashZero,
      conditionIdB,
      [1, 2],
      ethers.utils.parseUnits("1000", 6),
    ]),
    value: "0",
  },
];

const response = await client.execute(transactions, "Batch inventory setup");
await response.wait();

Next Steps

CTF Overview

How the Conditional Token Framework works under the hood

Split Tokens

Detailed split function parameters and prerequisites

Merge Tokens

Detailed merge function parameters

Gasless Transactions

Relayer Client setup and configuration