> ## Documentation Index
> Fetch the complete documentation index at: https://docs.testnet.dev.adipredictstreet.com/llms.txt
> Use this file to discover all available pages before exploring further.

# CTFExchange

> Signed-order matching with the PS_QUADRATIC_V1 taker-fee curve.

`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](/concepts/trading/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                                                                                        |

## Fee formula

```solidity theme={null}
// 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

```solidity theme={null}
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>))`.

<Tip>
  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.
</Tip>

## Source

Paste `0x4074c225b296E1E556c565B0C3Ddba305E63E7c4` on
[Blockscout](https://blockscout.ab.testnet.adifoundation.ai) for
verified source + ABI.
