Skip to main content

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

1

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")
});
2

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.
3

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
);
4

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

Build docs developers (and LLMs) love