Skip to main content

Overview

Composable orders enable you to create programmatic orders (sometimes called conditional orders) that execute based on custom logic rather than executing immediately. These orders leverage the ComposableCoW smart contract framework to define conditions that must be met before an order becomes tradeable. Key benefits of composable orders:
  • Programmatic Execution — Orders only execute when specific conditions are met
  • Advanced Strategies — Build complex trading strategies like TWAP, DCA, or custom logic
  • Gas Efficiency — Reduce on-chain transactions by batching order logic
  • Merkle Tree Storage — Efficiently store multiple orders using Merkle proofs

ConditionalOrder Base Class

All composable orders inherit from the ConditionalOrder abstract base class:
from cowdao_cowpy.composable import ConditionalOrder
from cowdao_cowpy.common.chains import Chain
from eth_typing import HexStr

class MyCustomOrder(ConditionalOrder[DataType, StructType]):
    def __init__(self, handler: HexStr, data: DataType, salt: HexStr = None):
        super().__init__(
            handler=handler,
            data=data,
            salt=salt,
            has_off_chain_input=False,
            chain=Chain.MAINNET
        )

    @property
    def is_single_order(self) -> bool:
        return True

    @property
    def order_type(self) -> str:
        return "my_custom_order"

    def is_valid(self) -> IsValidResult:
        pass

    async def poll_validate(self, params: PollParams) -> Optional[PollResultError]:
        pass

Key Properties and Methods

Order Identification

order_id = conditional_order.id    # keccak256 hash of serialized order
ctx_key = conditional_order.ctx     # Context key for cabinet lookups
leaf = conditional_order.leaf       # Leaf data for Merkle tree storage

Creating Orders

Generate calldata to create a programmatic order on-chain:
from cowdao_cowpy.composable.order_types.twap import Twap, TwapData, StartType, DurationType
from cowdao_cowpy.common.chains import Chain
from web3 import Web3

twap_data = TwapData(
    sell_token=Web3.to_checksum_address("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"),
    buy_token=Web3.to_checksum_address("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"),
    receiver=Web3.to_checksum_address("0xYourAddress"),
    sell_amount=1000000000,
    buy_amount=500000000000000000,
    start_type=StartType.AT_MINING_TIME,
    number_of_parts=10,
    time_between_parts=3600,
    duration_type=DurationType.AUTO,
    app_data="0x" + "0" * 64
)

twap_order = Twap.from_data(twap_data)
create_calldata = twap_order.create_calldata
twap_order.assert_is_valid()

Polling Orders

Check if a programmatic order is ready to be executed:
import asyncio
from cowdao_cowpy.composable.types import PollParams, PollResultCode
from cowdao_cowpy.order_book.api import OrderBookApi
from web3 import AsyncWeb3

async def poll_conditional_order(order, owner_address, chain):
    provider = AsyncWeb3(AsyncWeb3.AsyncHTTPProvider("https://rpc.ankr.com/eth"))
    order_book_api = OrderBookApi()

    poll_params = PollParams(
        owner=owner_address,
        chain=chain,
        provider=provider,
        order_book_api=order_book_api
    )

    result = await order.poll(poll_params)

    if result.result == PollResultCode.SUCCESS:
        print(f"Order ready! Order: {result.order}")
        print(f"Signature: {result.signature}")
    elif result.result == PollResultCode.TRY_NEXT_BLOCK:
        print(f"Not ready yet: {result.reason}")
    elif result.result == PollResultCode.DONT_TRY_AGAIN:
        print(f"Order cannot execute: {result.reason}")

await poll_conditional_order(twap_order, "0xYourAddress", Chain.MAINNET)

Authorization

from cowdao_cowpy.composable.types import OwnerParams

async def check_authorization(order, owner_address, chain, provider):
    params = OwnerParams(
        owner=owner_address,
        chain=chain,
        provider=provider
    )

    is_authorized = await order.is_authorized(params)
    print(f"Order authorized: {is_authorized}")

    cabinet_value = await order.cabinet(params)
    print(f"Cabinet value: {cabinet_value}")

ComposableCow Contract Integration

from cowdao_cowpy.codegen.__generated__.ComposableCow import (
    ComposableCow,
    IConditionalOrder_ConditionalOrderParams
)
from hexbytes import HexBytes

composable_cow = ComposableCow(chain=Chain.MAINNET)

order_params = IConditionalOrder_ConditionalOrderParams(
    handler="0xHandlerAddress",
    salt=HexBytes("0x" + "0" * 64),
    staticInput=HexBytes("0x...")
)

is_single_order = await composable_cow.single_orders(
    owner_address,
    HexBytes(order_id)
)

tradeable_order, signature = await composable_cow.get_tradeable_order_with_signature(
    owner=owner_address,
    params=order_params,
    offchain_input=HexBytes("0x"),
    proof=[]
)

Multiplexer for Multiple Orders

The Multiplexer class enables efficient management of multiple programmatic orders using Merkle trees:
from cowdao_cowpy.composable import Multiplexer
from cowdao_cowpy.composable.types import ProofLocation

orders_dict = {
    order1.id: order1,
    order2.id: order2,
    order3.id: order3
}

multiplexer = Multiplexer(
    orders=orders_dict,
    root=None,
    location=ProofLocation.PRIVATE
)

# Get Merkle root
root = multiplexer.root
print(f"Merkle root: {root}")

# Get proofs for all orders
proofs = multiplexer.get_proofs()

for proof_with_params in proofs:
    print(f"Handler: {proof_with_params.params.handler}")
    print(f"Salt: {proof_with_params.params.salt}")
    print(f"Merkle path: {[p.hex() for p in proof_with_params.proof.path]}")

# Export/Import JSON
json_data = multiplexer.to_json()
restored_multiplexer = Multiplexer.from_json(json_data)

Custom Handler Implementation

from dataclasses import dataclass
from typing import Optional
from cowdao_cowpy.composable import ConditionalOrder
from cowdao_cowpy.composable.types import IsValidResult, PollParams, PollResultError
from eth_typing import HexStr

@dataclass
class PriceThresholdData:
    sell_token: str
    buy_token: str
    sell_amount: int
    min_price: int
    receiver: str

class PriceThresholdOrder(ConditionalOrder[PriceThresholdData, PriceThresholdStruct]):
    HANDLER_ADDRESS = HexStr("0xYourHandlerAddress")

    @property
    def is_single_order(self) -> bool:
        return True

    @property
    def order_type(self) -> str:
        return "price_threshold"

    def is_valid(self) -> IsValidResult:
        if self.data.sell_amount <= 0:
            return IsValidResult(is_valid=False, reason="Invalid sell amount")
        if self.data.min_price <= 0:
            return IsValidResult(is_valid=False, reason="Invalid min price")
        return IsValidResult(is_valid=True)

    async def poll_validate(self, params: PollParams) -> Optional[PollResultError]:
        return None

Best Practices

Validate Order Parameters: Always implement thorough validation in the is_valid() method to catch errors before submitting orders on-chain.
  • Handle Polling Errors Gracefully: Use appropriate PollResultCode values to signal whether polling should be retried, delayed, or stopped entirely.
  • Test with Testnets First: Always test programmatic orders on testnets (like Sepolia) before deploying to mainnet.
  • Use Salt for Uniqueness: The salt parameter ensures each order has a unique ID even if other parameters are identical.
Last modified on March 12, 2026