> ## Documentation Index > Fetch the complete documentation index at: https://docs.polymarket.com/llms.txt > Use this file to discover all available pages before exploring further. # Inventory Management > Managing outcome token inventory for market making 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](/trading/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. ```typescript TypeScript theme={null} 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); ``` ```python Python theme={null} from web3 import Web3 CTF_ADDRESS = "0x4D97DCd97eC945f40cF65F87097ACe5EA0476045" pUSD_ADDRESS = "0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB" ctf_abi = [{ "name": "splitPosition", "type": "function", "inputs": [ {"name": "collateralToken", "type": "address"}, {"name": "parentCollectionId", "type": "bytes32"}, {"name": "conditionId", "type": "bytes32"}, {"name": "partition", "type": "uint256[]"}, {"name": "amount", "type": "uint256"} ], "outputs": [] }] # Split $1000 pUSD into YES/NO tokens amount = 1000 * 10**6 # pUSD has 6 decimals split_tx = { "to": CTF_ADDRESS, "data": Web3().eth.contract( address=CTF_ADDRESS, abi=ctf_abi ).encode_abi( abi_element_identifier="splitPosition", args=[ pUSD_ADDRESS, bytes(32), # parentCollectionId (always zero) condition_id, # conditionId from market [1, 2], # partition: [YES, NO] amount, ] ), "value": "0" } response = client.execute([split_tx], "Split pUSD into tokens") response.wait() ``` ```rust Rust theme={null} use polymarket_client_sdk_v2::ctf::Client as CtfClient; use polymarket_client_sdk_v2::ctf::types::SplitPositionRequest; use polymarket_client_sdk_v2::types::{U256, address}; let ctf_client = CtfClient::new(provider, 137)?; // Split $1000 pUSD into YES/NO tokens let request = SplitPositionRequest::builder() .collateral_token(address!("0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB")) .condition_id(condition_id) .partition(vec![U256::from(1), U256::from(2)]) .amount(U256::from(1000_000_000u64)) // 1000 pUSD (6 decimals) .build(); let result = ctf_client.split_position(&request).await?; println!("Split tx: {:?}", result.transaction_hash); ``` 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. ```typescript TypeScript theme={null} 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(); ``` ```python Python theme={null} merge_abi = [{ "name": "mergePositions", "type": "function", "inputs": [ {"name": "collateralToken", "type": "address"}, {"name": "parentCollectionId", "type": "bytes32"}, {"name": "conditionId", "type": "bytes32"}, {"name": "partition", "type": "uint256[]"}, {"name": "amount", "type": "uint256"} ], "outputs": [] }] # Merge 500 YES + 500 NO back to 500 pUSD amount = 500 * 10**6 merge_tx = { "to": CTF_ADDRESS, "data": Web3().eth.contract( address=CTF_ADDRESS, abi=merge_abi ).encode_abi( abi_element_identifier="mergePositions", args=[pUSD_ADDRESS, bytes(32), condition_id, [1, 2], amount] ), "value": "0" } response = client.execute([merge_tx], "Merge tokens to pUSD") response.wait() ``` ```rust Rust theme={null} use polymarket_client_sdk_v2::ctf::types::MergePositionsRequest; // Merge 500 YES + 500 NO back to 500 pUSD let request = MergePositionsRequest::builder() .collateral_token(address!("0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB")) .condition_id(condition_id) .partition(vec![U256::from(1), U256::from(2)]) .amount(U256::from(500_000_000u64)) // 500 pUSD (6 decimals) .build(); let result = ctf_client.merge_positions(&request).await?; ``` 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 $1 — losing tokens redeem for $0. ### Check Resolution Status ```typescript TypeScript theme={null} const market = await clobClient.getMarket(conditionId); if (market.closed) { const winningToken = market.tokens.find((t) => t.winner); console.log("Winning outcome:", winningToken?.outcome); } ``` ```python Python theme={null} market = clob_client.get_market(condition_id) if market.get("closed"): winning = next(t for t in market["tokens"] if t.get("winner")) print("Winning outcome:", winning["outcome"]) ``` ```rust Rust theme={null} let market = clob_client.market(condition_id).await?; if market.closed { if let Some(winner) = market.tokens.iter().find(|t| t.winner) { println!("Winning outcome: {}", winner.outcome); } } ``` ### Redeem Winning Tokens ```typescript TypeScript theme={null} 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(); ``` ```python Python theme={null} redeem_abi = [{ "name": "redeemPositions", "type": "function", "inputs": [ {"name": "collateralToken", "type": "address"}, {"name": "parentCollectionId", "type": "bytes32"}, {"name": "conditionId", "type": "bytes32"}, {"name": "indexSets", "type": "uint256[]"} ], "outputs": [] }] redeem_tx = { "to": CTF_ADDRESS, "data": Web3().eth.contract( address=CTF_ADDRESS, abi=redeem_abi ).encode_abi( abi_element_identifier="redeemPositions", args=[pUSD_ADDRESS, bytes(32), condition_id, [1, 2]] ), "value": "0" } response = client.execute([redeem_tx], "Redeem winning tokens") response.wait() ``` ```rust Rust theme={null} use polymarket_client_sdk_v2::ctf::types::RedeemPositionsRequest; let request = RedeemPositionsRequest::builder() .collateral_token(address!("0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB")) .condition_id(condition_id) .index_sets(vec![U256::from(1), U256::from(2)]) // Redeem both (only winners pay) .build(); let result = ctf_client.redeem_positions(&request).await?; ``` *** ## 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: ```typescript theme={null} const NEG_RISK_CTF_EXCHANGE = "0xe2222d279d744050d28e00520010520000310F59"; const NEG_RISK_ADAPTER = "0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296"; ``` See [Negative Risk Markets](/advanced/neg-risk) for details on how multi-outcome token mechanics differ. *** ## Inventory Strategies ### Before Quoting 1. Check market metadata via the [Gamma API](/market-data/fetching-markets) 2. Split sufficient pUSD to cover your expected quoting size 3. Set token approvals if not already done (see [Getting Started](/market-makers/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: ```typescript theme={null} 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 How the Conditional Token Framework works under the hood Detailed split function parameters and prerequisites Detailed merge function parameters Relayer Client setup and configuration