Skip to main content
CTFExchange is a Polymarket-compatible order-matching contract that settles trades against ConditionalTokens. PredictStreet deploys it with the quadratic taker-fee formula fee = k × P × (1−P) × outcomeTokens. See Fees for the math.

Key entry points

FunctionCallerPurpose
fillOrder(Order, fillAmount)onlyOperatorDirect fill against a resting order
matchOrders(takerOrder, makerOrders[], takerFillAmount, makerFillAmounts[])onlyOperatorSingle-taker batch
batchMatchOrders(takerOrders[], makerOrders[][], takerFillAmounts[], makerFillAmounts[][])onlyOperatorMulti-taker batch — per-taker reverts caught and emitted as MatchFailed so the rest of the batch still settles
cancelOrders(Order[])signer of each orderCancel on-chain
registerToken(uint256 token0, uint256 token1, bytes32 conditionId)onlyAdminRegister a new market
pauseTrading() / unpauseTrading()onlyAdminEmergency halt
pauseUser(address, uint256 blocks)onlyAdminBlock a specific user
pauseMarket(bytes32 conditionId)onlyAdminBlock a specific market
setVaultFactory(address)onlyAdminWire the VaultFactory
addOperator(address)onlyAdminWhitelist an operator key

Fee formula

// BUY: fee charged in outcome tokens
fee_tokens = feeRateBps × (1 − price) × outcomeTokens / BPS_DIVISOR;

// SELL: fee charged in collateral
fee_collateral = feeRateBps × price × (1 − price) × outcomeTokens / (BPS_DIVISOR × ONE);
Cap: feeRateBps ≤ MAX_FEE_RATE_BIPS (1000).

Events

event OrderFilled(
    uint64 seq,
    bytes32 indexed orderHash,
    address indexed maker,
    address taker,
    uint256 makerAssetId,
    uint256 takerAssetId,
    uint256 makerAmountFilled,
    uint256 takerAmountFilled,
    uint256 fee
);

event OrdersMatched(...);
event OrderCancelled(uint64 seq, bytes32 indexed orderHash);
event FeesWithdrawn(uint64 seq, address indexed token, address to, uint256 amount);

Security invariants

  • Signature replay impossible — every filled order’s hash stored in orderStatus[hash].isFilledOrCancelled.
  • Maker authority cryptographicmaker field verified against signer via vaultFactory.vaultOf(signer) (VAULT) or equality (EOA).
  • Admin can pause but not seize — no admin function transfers user funds.
  • Operator rotatableremoveOperator(address) disables compromised key.

MatchFailed reason codes

When batchMatchOrders catches a per-leg revert, the reason bytes are the 4-byte selector of the original custom error. Common ones:
SelectorCustom errorCause
0xc56873baOrderExpired()A maker or taker order’s expiration is in the past at execution time. Most common with stale resting LIMITs whose TTL elapsed between match (off-chain) and submit (on-chain).
0xb52e0fecOrderFilledOrCancelled()The order’s hash is already marked filled or cancelled on-chain (replay or duplicate match).
0x70a52d24MismatchedTokenIds()Taker and maker outcome tokens don’t form a valid binary pair.
0xc7e63d99NotCrossing()The proposed prices don’t cross.
Decode the bytes with cast 4byte 0x<selector> or by computing bytes4(keccak256(<errorSig>)).
To avoid OrderExpired() settlement failures, sign orders with expiration = 0 (no on-chain expiry — the contract skips the block.timestamp >= expiration check entirely). UIs that need a hard TTL should set it well above worst-case settlement latency.

Source

Paste 0xc3c197e42AfE809a7f34D3a7eE6aDE0cF7613D2b on Blockscout for verified source + ABI.