Overview
Conditional orders in ComposableCoW are created using the ConditionalOrderParams struct, which defines the order type (handler), uniqueness (salt), and order-specific data (staticInput).
ConditionalOrderParams Structure
Every conditional order is uniquely identified by three components:
struct ConditionalOrderParams {
IConditionalOrder handler; // The order type contract (e.g., TWAP, GoodAfterTime)
bytes32 salt; // Unique identifier for this order
bytes staticInput; // ABI-encoded order-specific parameters
}
The combination of handler, salt, and staticInput must be unique for each order. The hash H(handler || salt || staticInput) serves as the order’s identifier.
Creating a Single Order
Prepare Order Parameters
Define your conditional order parameters based on the handler type you’re using:import {IConditionalOrder} from "composable-cow/interfaces/IConditionalOrder.sol";
import {TWAP, TWAPOrder} from "composable-cow/types/twap/TWAP.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
// Example: TWAP order data
TWAPOrder.Data memory twapData = TWAPOrder.Data({
sellToken: IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F), // DAI
buyToken: IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2), // WETH
receiver: address(0), // Use Safe as receiver
partSellAmount: 400_000 * 1e18, // 400k DAI per part
minPartLimit: 250 * 1e18, // Minimum 250 WETH per part
t0: block.timestamp, // Start immediately
n: 30, // 30 parts
t: 86400, // 1 day interval
span: 0, // Valid for entire interval
appData: keccak256("my-twap-order")
});
Create ConditionalOrderParams
Wrap your order data in a ConditionalOrderParams struct:IConditionalOrder.ConditionalOrderParams memory params =
IConditionalOrder.ConditionalOrderParams({
handler: IConditionalOrder(0x6cF1e9cA41f7611dEf408122793c358a3d11E5a5), // TWAP handler
salt: keccak256(abi.encodePacked("my-unique-salt")),
staticInput: abi.encode(twapData)
});
Use a unique salt for each order. Reusing the same salt with identical handler and staticInput will reference the same order.
Approve Token Spending
Grant the CoW Protocol vault relayer permission to spend your tokens:// GPv2VaultRelayer address (same across networks)
address constant VAULT_RELAYER = 0xC92E8bdf79f0507f65a392b0ab4667716BFE0110;
// For a TWAP, approve the total amount (partSellAmount * n)
uint256 totalAmount = twapData.partSellAmount * twapData.n;
safe.execute(
address(twapData.sellToken),
0,
abi.encodeWithSelector(
IERC20.approve.selector,
VAULT_RELAYER,
totalAmount
),
Enum.Operation.Call,
signers
);
Create the Order
Call ComposableCoW.create() from your Safe:ComposableCoW constant COMPOSABLE_COW =
ComposableCoW(0xfdaFc9d1902f4e0b84f65F49f244b32b31013b74);
safe.execute(
address(COMPOSABLE_COW),
0,
abi.encodeWithSelector(
ComposableCoW.create.selector,
params,
true // dispatch = true to emit events for watchtowers
),
Enum.Operation.Call,
signers
);
The dispatch parameter controls event emission:
true: Emits ConditionalOrderCreated event for public watchtowers
false: Private order, no events emitted (you must submit to CoW API manually)
Complete Example
Here’s a full example from the ComposableCoW repository:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0 <0.9.0;
import {Safe} from "safe/Safe.sol";
import {Enum} from "safe/common/Enum.sol";
import {IConditionalOrder, ComposableCoW} from "composable-cow/ComposableCoW.sol";
import {TWAP, TWAPOrder} from "composable-cow/types/twap/TWAP.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract SubmitSingleOrder {
Safe public safe;
TWAP public constant TWAP_HANDLER =
TWAP(0x6cF1e9cA41f7611dEf408122793c358a3d11E5a5);
ComposableCoW public constant COMPOSABLE_COW =
ComposableCoW(0xfdaFc9d1902f4e0b84f65F49f244b32b31013b74);
function submitTWAPOrder() external {
// Define TWAP parameters
TWAPOrder.Data memory twapOrder = TWAPOrder.Data({
sellToken: IERC20(address(0x6B175...)), // DAI
buyToken: IERC20(address(0xC02a...)), // WETH
receiver: address(0),
partSellAmount: 10 * 1e18,
minPartLimit: 1 * 1e18,
t0: block.timestamp,
n: 10,
t: 120, // 2 minutes for testing
span: 0,
appData: keccak256("forge.scripts.twap")
});
// Create conditional order params
IConditionalOrder.ConditionalOrderParams memory params =
IConditionalOrder.ConditionalOrderParams({
handler: IConditionalOrder(address(TWAP_HANDLER)),
salt: keccak256(abi.encodePacked("TWAP")),
staticInput: abi.encode(twapOrder)
});
// Submit via Safe
safe.execTransaction(
address(COMPOSABLE_COW),
0,
abi.encodeWithSelector(
ComposableCoW.create.selector,
params,
true
),
Enum.Operation.Call,
// ... gas parameters and signatures
);
}
}
This example is from script/submit_SingleOrder.s.sol in the ComposableCoW repository.
Order Types and Handlers
ComposableCoW supports multiple order types through different handler contracts:
TWAP (Time-Weighted Average Price)
Handler: 0x6cF1e9cA41f7611dEf408122793c358a3d11E5a5
Splits an order into n parts executed every t seconds:
struct Data {
IERC20 sellToken;
IERC20 buyToken;
address receiver;
uint256 partSellAmount; // Amount per part
uint256 minPartLimit; // Min buy amount per part
uint256 t0; // Start time
uint256 n; // Number of parts
uint256 t; // Interval duration
uint256 span; // Validity window within each interval
bytes32 appData;
}
GoodAfterTime
Handler: 0xdaf33924925e03c9cc3a10d434016d6cfad0add5
Places an order valid only after a specific timestamp:
struct Data {
IERC20 sellToken;
IERC20 buyToken;
address receiver;
uint256 sellAmount;
uint256 minSellBalance; // Minimum balance required
uint256 startTime; // When order becomes valid
uint256 endTime; // When order expires
bool allowPartialFill;
bytes priceCheckerPayload; // Optional price validation
bytes32 appData;
}
StopLoss
Handler: 0x412c36e5011cd2517016d243a2dfb37f73a242e7
Triggers when price falls below a strike price using oracles:
struct Data {
IERC20 sellToken;
IERC20 buyToken;
uint256 sellAmount;
uint256 buyAmount;
bytes32 appData;
address receiver;
bool isSellOrder;
bool isPartiallyFillable;
uint32 validTo;
IAggregatorV3Interface sellTokenPriceOracle;
IAggregatorV3Interface buyTokenPriceOracle;
int256 strike; // Strike price (18 decimals)
uint256 maxTimeSinceLastOracleUpdate; // Max staleness
}
Creating Orders with Context
Use createWithContext to store on-chain values with your order:
import {IValueFactory} from "composable-cow/interfaces/IValueFactory.sol";
import {CurrentBlockTimestampFactory} from "composable-cow/value_factories/CurrentBlockTimestampFactory.sol";
// Store current block timestamp as t0 for a TWAP
IValueFactory timestampFactory = IValueFactory(0x52eD56Da04309Aca4c3FECC595298d80C2f16BAc);
composableCow.createWithContext(
params,
timestampFactory,
abi.encode(block.timestamp),
true // dispatch
);
The stored value can be retrieved from the cabinet mapping:
bytes32 orderHash = keccak256(abi.encode(params));
bytes32 storedValue = composableCow.cabinet(address(safe), orderHash);
For TWAP orders with t0 = 0, ComposableCoW automatically reads the start time from the cabinet using the order hash as the key.
Batching Operations
Combine approval and order creation in a single Safe transaction:
import {MultiSend} from "safe/libraries/MultiSend.sol";
bytes memory transactions = abi.encodePacked(
// Transaction 1: Approve tokens
uint8(Enum.Operation.Call),
address(sellToken),
uint256(0),
uint256(approveData.length),
approveData,
// Transaction 2: Create order
uint8(Enum.Operation.Call),
address(COMPOSABLE_COW),
uint256(0),
uint256(createData.length),
createData
);
safe.execTransaction(
address(multiSend),
0,
abi.encodeWithSelector(MultiSend.multiSend.selector, transactions),
Enum.Operation.DelegateCall,
// ... remaining parameters
);
Verification
Verify your order was created successfully:
bytes32 orderHash = keccak256(abi.encode(params));
bool isAuthorized = composableCow.singleOrders(address(safe), orderHash);
require(isAuthorized, "Order not authorized");
Retrieve a tradeable order:
(
GPv2Order.Data memory order,
bytes memory signature
) = composableCow.getTradeableOrderWithSignature(
address(safe),
params,
bytes(""), // offchainInput
new bytes32[](0) // proof (empty for single orders)
);
Next Steps