Custom conditional orders are the core of ComposableCoW’s flexibility. By implementing the IConditionalOrder or IConditionalOrderGenerator interface, you can create sophisticated trading strategies that execute based on on-chain conditions.
Most custom orders should extend BaseConditionalOrder, which handles signature verification and implements IConditionalOrderGenerator for you.
The base interface that all conditional orders must implement:
interface IConditionalOrder { /** * Verify if a given discrete order is valid. * @param owner the contract who is the owner of the order * @param sender the `msg.sender` of the transaction * @param _hash the hash of the order * @param domainSeparator the domain separator used to sign the order * @param ctx the context key (bytes32(0) if merkle tree, otherwise H(params)) * @param staticInput the static input for all discrete orders * @param offchainInput dynamic off-chain input for this discrete order * @param order GPv2Order.Data of the discrete order to be verified */ function verify( address owner, address sender, bytes32 _hash, bytes32 domainSeparator, bytes32 ctx, bytes calldata staticInput, bytes calldata offchainInput, GPv2Order.Data calldata order ) external view;}
Extends IConditionalOrder to generate orders on-chain:
interface IConditionalOrderGenerator is IConditionalOrder, IERC165 { /** * Get a tradeable order that can be posted to the CoW Protocol API. * @param owner the contract who is the owner of the order * @param sender the `msg.sender` of the parent `isValidSignature` call * @param ctx the context (bytes32(0) if merkle tree, otherwise H(params)) * @param staticInput the static input for all discrete orders * @param offchainInput dynamic off-chain input for this discrete order * @return the tradeable order for submission to CoW Protocol API */ function getTradeableOrder( address owner, address sender, bytes32 ctx, bytes calldata staticInput, bytes calldata offchainInput ) external view returns (GPv2Order.Data memory);}
Use specific revert errors to communicate with watchtowers:
// Order is not valid right now, try again next blockrevert IConditionalOrder.PollTryNextBlock("balance insufficient");// Order will be valid at a specific blockrevert IConditionalOrder.PollTryAtBlock(blockNumber, "wait for oracle update");// Order will be valid at a specific timestamprevert IConditionalOrder.PollTryAtEpoch(timestamp, "order still locked");// Order should never be polled again (permanent)revert IConditionalOrder.PollNever("expired");// Generic invalid orderrevert IConditionalOrder.OrderNotValid("condition not met");
Choose the right error type! PollNever tells watchtowers to stop monitoring this order permanently. Use PollTryNextBlock for temporary conditions.
If you need more control, implement IConditionalOrder directly without getTradeableOrder. This requires the discrete order to be provided off-chain, and you only verify it:
contract CustomVerifyOnly is IConditionalOrder, IERC165 { function verify( address owner, address sender, bytes32 _hash, bytes32 domainSeparator, bytes32 ctx, bytes calldata staticInput, bytes calldata offchainInput, GPv2Order.Data calldata order ) external view override { // Custom verification logic // Check if the provided order meets your conditions // Verify the hash matches require( _hash == GPv2Order.hash(order, domainSeparator), "hash mismatch" ); // Your custom checks... } function supportsInterface(bytes4 interfaceId) external view override returns (bool) { return interfaceId == type(IConditionalOrder).interfaceId || interfaceId == type(IERC165).interfaceId; }}