Gnosis Conditional Tokens Framework (CTF) operations Polymarket relies on.
Three functions are exposed in two flavors each — a pure calldata builder and a submit wrapper. The pair always shares the same function-specific arguments; only the wrapper layers signing and broadcasting on top.
Two surfaces
- Calldata builders —
redeem_positions_calldata/4,merge_positions_calldata/5,split_position_calldata/5. Produce the raw function-call bytes. Use when you want the bytes for inspection, off-line signing, or submission through your own Polygon RPC client. - Submit wrappers —
redeem_positions/2,merge_positions/2,split_position/2. Build, sign, and broadcast in one call. Compose the calldata builder +Polymarket.Tx+Polymarket.RPC. Caller provides every signing field (:nonce,:gas_price,:gas_limit,:chain_id,:private_key); the wrappers do not auto-fetch from RPC.
What each function does
redeem_positions[_calldata]— claim payouts for resolved outcome tokens. Per-outcome amounts are derived from the reported payout numerators on-chain; the caller only specifies the partition.merge_positions[_calldata]— combine outcome tokens back into collateral. Useful for wallet hygiene or when liquidating an entire spread.split_position[_calldata]— mint outcome tokens from collateral. Rare in normal trading; included for completeness.
CTF function ABI (from the Gnosis spec):
redeemPositions(address collateralToken, bytes32 parentCollectionId, bytes32 conditionId, uint256[] indexSets)mergePositions(address collateralToken, bytes32 parentCollectionId, bytes32 conditionId, uint256[] partition, uint256 amount)splitPosition(address collateralToken, bytes32 parentCollectionId, bytes32 conditionId, uint256[] partition, uint256 amount)
Partition / index sets
For Polymarket binary YES/NO markets, pass [1, 2]. For an N-outcome
market, pass [1, 2, 4, ..., 2^(N-1)] — each integer is a bitmask
selecting one outcome slot.
Static-vs-dynamic ABI layout
All three functions have exactly one dynamic parameter (uint256[]).
Per the Solidity ABI, every argument occupies one 32-byte word in
the head — static args inline their value, dynamic args inline an
offset pointer to their tail.
redeemPositions (address, bytes32, bytes32, uint256[]) has
4 head words = 4 * 32 = 128 bytes:
1 address + 2 bytes32 + 1 dynamic-array offset.
mergePositions and splitPosition
(address, bytes32, bytes32, uint256[], uint256) have 5 head words
= 5 * 32 = 160 bytes:
1 address + 2 bytes32 + 1 dynamic-array offset + 1 uint256.
The dynamic-array offset placed in the head equals the head size (i.e. the byte position of the array tail relative to the start of the args, post-selector).
Calldata bytes are validated against the canonical ethers.js v6
encoder in test/fixtures/calldata.json. The submit-wrapper
composition (calldata → tx → signed bytes) is verified against a
manually-composed reference in
test/polymarket/submit_test.exs.
Summary
Functions
Builds, signs, and broadcasts a mergePositions(...) transaction to
the CTF contract at :contract.
Builds calldata for mergePositions(address, bytes32, bytes32, uint256[], uint256).
Builds, signs, and broadcasts a redeemPositions(...) transaction to
the CTF contract at :contract. Returns the on-chain transaction
hash on success.
Builds calldata for redeemPositions(address, bytes32, bytes32, uint256[]).
Builds, signs, and broadcasts a splitPosition(...) transaction to
the CTF contract at :contract.
Builds calldata for splitPosition(address, bytes32, bytes32, uint256[], uint256).
Functions
@spec merge_positions( Polymarket.RPC.t(), keyword() ) :: {:ok, String.t()} | {:error, term()}
Builds, signs, and broadcasts a mergePositions(...) transaction to
the CTF contract at :contract.
Required opts: :contract, :collateral_token,
:parent_collection_id, :condition_id, :partition, :amount,
plus :nonce, :gas_price, :gas_limit, :chain_id,
:private_key.
@spec merge_positions_calldata( String.t(), String.t(), String.t(), [non_neg_integer()], non_neg_integer() ) :: binary()
Builds calldata for mergePositions(address, bytes32, bytes32, uint256[], uint256).
@spec redeem_positions( Polymarket.RPC.t(), keyword() ) :: {:ok, String.t()} | {:error, term()}
Builds, signs, and broadcasts a redeemPositions(...) transaction to
the CTF contract at :contract. Returns the on-chain transaction
hash on success.
Required opts: :contract, :collateral_token,
:parent_collection_id, :condition_id, :index_sets, plus the
common signing fields :nonce, :gas_price, :gas_limit,
:chain_id, :private_key. See redeem_positions_calldata/4 for
argument shapes.
@spec redeem_positions_calldata( String.t(), String.t(), String.t(), [non_neg_integer()] ) :: binary()
Builds calldata for redeemPositions(address, bytes32, bytes32, uint256[]).
index_sets is the partition over outcome slots, expressed as
bitmasks. For Polymarket binary YES/NO markets pass [1, 2].
@spec split_position( Polymarket.RPC.t(), keyword() ) :: {:ok, String.t()} | {:error, term()}
Builds, signs, and broadcasts a splitPosition(...) transaction to
the CTF contract at :contract.
Required opts: same as merge_positions/2.
@spec split_position_calldata( String.t(), String.t(), String.t(), [non_neg_integer()], non_neg_integer() ) :: binary()
Builds calldata for splitPosition(address, bytes32, bytes32, uint256[], uint256).