Polymarket.CTF (Polymarket v0.2.0)

Copy Markdown View Source

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

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

merge_positions(rpc, opts)

@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.

merge_positions_calldata(collateral_token, parent_collection_id, condition_id, partition, amount)

@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).

redeem_positions(rpc, opts)

@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.

redeem_positions_calldata(collateral_token, parent_collection_id, condition_id, index_sets)

@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].

split_position(rpc, opts)

@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.

split_position_calldata(collateral_token, parent_collection_id, condition_id, partition, amount)

@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).