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