Skip to main content

Overview

Once created, conditional orders can be queried for their status, retrieved as tradeable orders, and removed when no longer needed. ComposableCoW provides methods to manage both single orders and merkle root-based orders.

Querying Order Status

Check if Order Exists

For single orders, check authorization status:
import {ComposableCoW} from "composable-cow/ComposableCoW.sol";
import {IConditionalOrder} from "composable-cow/interfaces/IConditionalOrder.sol";

ComposableCoW composableCow = ComposableCoW(0xfdaFc9d1902f4e0b84f65F49f244b32b31013b74);

// Calculate order hash
bytes32 orderHash = keccak256(abi.encode(params));

// Check if order is authorized
bool isAuthorized = composableCow.singleOrders(ownerAddress, orderHash);

if (isAuthorized) {
    // Order exists and is active
} else {
    // Order doesn't exist or has been removed
}

Check Merkle Root

For merkle root-based orders:
// Get the current merkle root for an owner
bytes32 currentRoot = composableCow.roots(ownerAddress);

if (currentRoot == bytes32(0)) {
    // No merkle root set
} else {
    // Merkle root is active
}

Retrieve Cabinet Values

Orders created with context store values in the cabinet:
// For single orders, use order hash as key
bytes32 orderHash = keccak256(abi.encode(params));
bytes32 storedValue = composableCow.cabinet(ownerAddress, orderHash);

// For merkle root orders, context is typically at slot 0
bytes32 merkleContext = composableCow.cabinet(ownerAddress, bytes32(0));
The cabinet is a key-value storage where:
  • Single orders use H(params) as the key
  • Merkle root orders typically use bytes32(0) as the key

Getting Tradeable Orders

Retrieve Order for Submission

Convert a conditional order to a discrete GPv2 order ready for CoW Protocol API:
import {GPv2Order} from "cowprotocol/contracts/libraries/GPv2Order.sol";

// For single orders (empty proof)
(
    GPv2Order.Data memory order,
    bytes memory signature
) = composableCow.getTradeableOrderWithSignature(
    ownerAddress,
    params,
    bytes(""),        // offchainInput - implementation specific
    new bytes32[](0)  // Empty proof for single orders
);

// For merkle root orders (include proof)
(
    GPv2Order.Data memory order,
    bytes memory signature
) = composableCow.getTradeableOrderWithSignature(
    ownerAddress,
    params,
    bytes(""),        // offchainInput
    merkleProof       // Merkle proof array
);
This function will revert with custom errors if:
  • Order is not authorized (ProofNotAuthed or SingleOrderNotAuthed)
  • Order conditions are not met (e.g., PollTryNextBlock, OrderNotValid)
  • Swap guard restrictions apply (SwapGuardRestricted)

Submit to CoW Protocol API

Once you have the order and signature, submit to the CoW Protocol API:
import { OrderBookApi } from '@cowprotocol/cow-sdk';

const orderBookApi = new OrderBookApi({ chainId: 1 });

const orderCreation = {
  ...order,
  signature: signature,
  signingScheme: 'eip1271',
  from: safeAddress
};

const orderId = await orderBookApi.sendOrder(orderCreation);
console.log(`Order created: https://explorer.cow.fi/orders/${orderId}`);

Understanding Order Errors

ComposableCoW uses custom errors to communicate order status to watchtowers:

PollTryNextBlock

error PollTryNextBlock(string reason);
Indicates the order condition is not met yet, but watchtower should check again next block. Example: StopLoss strike price not reached yet.

PollTryAtBlock

error PollTryAtBlock(uint256 blockNumber, string reason);
Watchtower should retry at a specific block number.

PollTryAtEpoch

error PollTryAtEpoch(uint256 timestamp, string reason);
Watchtower should retry at a specific Unix timestamp. Example: GoodAfterTime order that becomes valid at startTime:
if (block.timestamp < data.startTime) {
    revert IConditionalOrder.PollTryAtEpoch(data.startTime, "too early");
}

PollNever

error PollNever(string reason);
Order should never be polled again (permanent failure).

OrderNotValid

error OrderNotValid(string reason);
Order condition check failed (e.g., insufficient balance, validation failed).

Removing Orders

Remove Single Order

1

Calculate Order Hash

bytes32 orderHash = keccak256(abi.encode(params));
2

Call remove()

From your Safe, call ComposableCoW.remove():
safe.execute(
    address(composableCow),
    0,
    abi.encodeWithSelector(
        ComposableCoW.remove.selector,
        orderHash
    ),
    Enum.Operation.Call,
    signers
);
This will:
  • Set singleOrders[owner][orderHash] to false
  • Clear the cabinet value: cabinet[owner][orderHash] = bytes32(0)
3

Verify Removal

bool isAuthorized = composableCow.singleOrders(ownerAddress, orderHash);
require(!isAuthorized, "Order still exists");

bytes32 cabinetValue = composableCow.cabinet(ownerAddress, orderHash);
require(cabinetValue == bytes32(0), "Cabinet not cleared");

Remove Multiple Orders

Batch multiple removals using MultiSend:
import {MultiSend} from "safe/libraries/MultiSend.sol";

bytes memory transactions;

for (uint i = 0; i < orderHashes.length; i++) {
    bytes memory removeData = abi.encodeWithSelector(
        ComposableCoW.remove.selector,
        orderHashes[i]
    );
    
    transactions = abi.encodePacked(
        transactions,
        uint8(Enum.Operation.Call),
        address(composableCow),
        uint256(0),
        uint256(removeData.length),
        removeData
    );
}

safe.execTransaction(
    address(multiSend),
    0,
    abi.encodeWithSelector(MultiSend.multiSend.selector, transactions),
    Enum.Operation.DelegateCall,
    // ... remaining parameters
);

Monitoring Order Events

ComposableCoW emits events that watchtowers monitor:

ConditionalOrderCreated

event ConditionalOrderCreated(
    address indexed owner,
    IConditionalOrder.ConditionalOrderParams params
);
Emitted when create() is called with dispatch = true. Watch for order creation:
const filter = composableCow.filters.ConditionalOrderCreated(safeAddress);

composableCow.on(filter, (owner, params, event) => {
  console.log(`New order created by ${owner}`);
  console.log(`Handler: ${params.handler}`);
  console.log(`Salt: ${params.salt}`);
});

MerkleRootSet

event MerkleRootSet(
    address indexed owner,
    bytes32 root,
    Proof proof
);
Emitted when merkle root is set or updated. Monitor merkle root changes:
const filter = composableCow.filters.MerkleRootSet(safeAddress);

composableCow.on(filter, (owner, root, proof, event) => {
  console.log(`Merkle root updated for ${owner}`);
  console.log(`New root: ${root}`);
  console.log(`Proof location: ${proof.location}`);
  
  if (proof.location === 1) {
    // Proof data contains array of proofs
    const proofs = decodeProofs(proof.data);
  }
});

SwapGuardSet

event SwapGuardSet(
    address indexed owner,
    ISwapGuard swapGuard
);
Emitted when a swap guard is configured.

Using Swap Guards

Swap guards add additional validation to orders:

Set Swap Guard

import {ISwapGuard} from "composable-cow/interfaces/ISwapGuard.sol";

ISwapGuard guard = ISwapGuard(customGuardAddress);

safe.execute(
    address(composableCow),
    0,
    abi.encodeWithSelector(
        ComposableCoW.setSwapGuard.selector,
        guard
    ),
    Enum.Operation.Call,
    signers
);

Remove Swap Guard

safe.execute(
    address(composableCow),
    0,
    abi.encodeWithSelector(
        ComposableCoW.setSwapGuard.selector,
        ISwapGuard(address(0))  // Set to zero address
    ),
    Enum.Operation.Call,
    signers
);

Check Current Swap Guard

ISwapGuard currentGuard = composableCow.swapGuards(ownerAddress);

if (address(currentGuard) == address(0)) {
    // No swap guard set
} else {
    // Swap guard is active
}
Swap guards are called during order verification and can reject orders based on custom logic (e.g., receiver restrictions, amount limits).

Complete Management Example

Here’s a comprehensive example managing order lifecycle:
contract OrderManager {
    ComposableCoW immutable composableCow;
    Safe immutable safe;
    
    constructor(address _composableCow, address _safe) {
        composableCow = ComposableCoW(_composableCow);
        safe = Safe(payable(_safe));
    }
    
    function isOrderActive(
        IConditionalOrder.ConditionalOrderParams memory params
    ) external view returns (bool) {
        bytes32 orderHash = keccak256(abi.encode(params));
        return composableCow.singleOrders(address(safe), orderHash);
    }
    
    function getOrderDetails(
        IConditionalOrder.ConditionalOrderParams memory params
    ) external view returns (
        bool isActive,
        bytes32 contextValue,
        GPv2Order.Data memory order,
        bytes memory signature
    ) {
        bytes32 orderHash = keccak256(abi.encode(params));
        isActive = composableCow.singleOrders(address(safe), orderHash);
        contextValue = composableCow.cabinet(address(safe), orderHash);
        
        if (isActive) {
            (order, signature) = composableCow.getTradeableOrderWithSignature(
                address(safe),
                params,
                bytes(""),
                new bytes32[](0)
            );
        }
    }
    
    function removeOrder(
        IConditionalOrder.ConditionalOrderParams memory params
    ) external {
        bytes32 orderHash = keccak256(abi.encode(params));
        
        safe.execTransaction(
            address(composableCow),
            0,
            abi.encodeWithSelector(
                ComposableCoW.remove.selector,
                orderHash
            ),
            Enum.Operation.Call,
            // ... transaction parameters
        );
    }
}

Best Practices

Monitor Events

Subscribe to ConditionalOrderCreated events to track order creation

Check Authorization

Always verify order authorization before attempting to retrieve tradeable orders

Handle Reverts

Implement proper error handling for the various Poll* and OrderNotValid errors

Batch Operations

Use MultiSend to remove multiple orders in a single transaction

Next Steps

Build docs developers (and LLMs) love