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
| Function | Caller | Purpose |
|---|
fillOrder(Order, fillAmount) | onlyOperator | Direct fill against a resting order |
matchOrders(takerOrder, makerOrders[], takerFillAmount, makerFillAmounts[]) | onlyOperator | Single-taker batch |
batchMatchOrders(takerOrders[], makerOrders[][], takerFillAmounts[], makerFillAmounts[][]) | onlyOperator | Multi-taker batch — per-taker reverts caught and emitted as MatchFailed so the rest of the batch still settles |
cancelOrders(Order[]) | signer of each order | Cancel on-chain |
registerToken(uint256 token0, uint256 token1, bytes32 conditionId) | onlyAdmin | Register a new market |
pauseTrading() / unpauseTrading() | onlyAdmin | Emergency halt |
pauseUser(address, uint256 blocks) | onlyAdmin | Block a specific user |
pauseMarket(bytes32 conditionId) | onlyAdmin | Block a specific market |
setVaultFactory(address) | onlyAdmin | Wire the VaultFactory |
addOperator(address) | onlyAdmin | Whitelist an operator key |
// 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 cryptographic —
maker 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 rotatable —
removeOperator(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:
| Selector | Custom error | Cause |
|---|
0xc56873ba | OrderExpired() | 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). |
0xb52e0fec | OrderFilledOrCancelled() | The order’s hash is already marked filled or cancelled on-chain (replay or duplicate match). |
0x70a52d24 | MismatchedTokenIds() | Taker and maker outcome tokens don’t form a valid binary pair. |
0xc7e63d99 | NotCrossing() | 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.