To use ComposableCoW, your Safe must be configured with the proper fallback handler and domain verifier.
1
Set the Fallback Handler
Your Safe needs to use ExtensibleFallbackHandler to support EIP-1271 signature verification with ComposableCoW.
// ExtensibleFallbackHandler address (same on all supported networks)address handler = 0x2f55e8b20D0B9FEFA187AA7d00B6Cbe563605bF5;// From your Safe, call setFallbackHandlersafe.setFallbackHandler(handler);
If you’re creating a new Safe, you can set the fallback handler during deployment. For existing Safes, use the Safe UI to propose and execute this transaction.
2
Set the Domain Verifier
Configure ComposableCoW as the signature verifier for CoW Protocol’s domain.
// ComposableCoW address (same on all supported networks)address composableCow = 0xfdaFc9d1902f4e0b84f65F49f244b32b31013b74;// Get GPv2Settlement domain separatorbytes32 domainSeparator = GPv2Settlement.domainSeparator();// Set ComposableCoW as the domain verifierISignatureVerifierMuxer(address(safe)).setDomainVerifier( domainSeparator, composableCow);
This step is critical. Without setting ComposableCoW as the domain verifier, your conditional orders will not be validated correctly.
Alice wants to sell 12,000,000 DAI for at least 7,500 WETH over 30 days, executing one part per day.
1
Define the TWAP Parameters
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";import {TWAPOrder} from "composable-cow/src/types/twap/libraries/TWAPOrder.sol";TWAPOrder.Data memory twapData = TWAPOrder.Data({ sellToken: IERC20(DAI_ADDRESS), buyToken: IERC20(WETH_ADDRESS), receiver: address(0), // address(0) means Safe receives partSellAmount: 400_000 * 1e18, // 400,000 DAI per part minPartLimit: 250 * 1e18, // 250 WETH minimum per part t0: block.timestamp, // Start now n: 30, // 30 parts total t: 86400, // 86400 seconds (1 day) between parts span: 0, // 0 = valid for entire interval appData: keccak256("my-twap-order")});
Parameters explained:
partSellAmount: Amount to sell in each individual trade (12M DAI ÷ 30 = 400K DAI)
minPartLimit: Minimum amount to receive per trade (7,500 WETH ÷ 30 = 250 WETH)
t0: Start time (unix timestamp)
n: Number of parts
t: Time interval between parts (in seconds)
span: Duration within each interval when order is valid (0 = entire interval)
2
Create ConditionalOrderParams
import {IConditionalOrder} from "composable-cow/src/interfaces/IConditionalOrder.sol";import {ComposableCoW} from "composable-cow/src/ComposableCoW.sol";// TWAP handler address (same on all networks)address twapHandler = 0x6cF1e9cA41f7611dEf408122793c358a3d11E5a5;IConditionalOrder.ConditionalOrderParams memory params = IConditionalOrder.ConditionalOrderParams({ handler: IConditionalOrder(twapHandler), salt: keccak256(abi.encodePacked("alice-dai-weth-twap")), staticInput: abi.encode(twapData) });
The combination of handler, salt, and staticInput must be unique for your Safe. Use a descriptive salt to avoid collisions with other orders.
3
Approve Token Spending
Grant the CoW Protocol vault relayer permission to spend your tokens.
// GPv2VaultRelayer addressaddress vaultRelayer = 0xC92E8bdf79f0507f65a392b0ab4667716BFE0110;// Approve the total amount needed (30 parts × 400,000 DAI)IERC20(DAI_ADDRESS).approve(vaultRelayer, 12_000_000 * 1e18);
This approval transaction must be executed by your Safe before creating the conditional order.
4
Create the Order
ComposableCoW composableCow = ComposableCoW(0xfdaFc9d1902f4e0b84f65F49f244b32b31013b74);// Create the order with dispatch = true to notify watchtowerscomposableCow.create(params, true);
Setting dispatch = true emits a ConditionalOrderCreated event that watchtowers monitor. They will automatically submit orders to CoW Protocol when conditions are met.
5
Execute as Batched Transaction
For the best user experience, batch the approval and order creation into a single Safe transaction:
Once created, you can monitor your conditional order’s execution:
1
View on CoW Explorer
Visit CoW Explorer and search for your Safe address to see all executed trades.
2
Query Order Status
Check if your conditional order can generate a tradeable order:
try composableCow.getTradeableOrderWithSignature( address(yourSafe), params, bytes(""), // offchainInput (empty for TWAP) new bytes32[](0) // proof (empty for single orders)) returns (GPv2Order.Data memory order, bytes memory signature) { // Order is valid and can be submitted console.log("Valid order:"); console.log("Sell amount:", order.sellAmount); console.log("Buy amount:", order.buyAmount); console.log("Valid until:", order.validTo);} catch Error(string memory reason) { // Order conditions not met console.log("Order not valid:", reason);}
To restrict when orders can execute within each interval, use the span parameter. For example, to only trade during business hours:
TWAPOrder.Data memory businessHoursTwap = TWAPOrder.Data({ sellToken: IERC20(DAI_ADDRESS), buyToken: IERC20(WETH_ADDRESS), receiver: address(0), partSellAmount: 400_000 * 1e18, minPartLimit: 250 * 1e18, t0: block.timestamp, n: 30, t: 86400, // 24 hour intervals span: 28800, // Only valid for first 8 hours of each day appData: keccak256("business-hours-twap")});
This example sets span = 28800 (8 hours), so each part is only valid from t0 + (i × t) to t0 + (i × t) + span, allowing weekend-only or weekday-only trading strategies.