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
Calculate Order Hash
bytes32 orderHash = keccak256 ( abi . encode (params));
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)
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