Skip to main content

Overview

The composable module provides functionality for creating and managing conditional orders that execute based on specific conditions. It includes the core ConditionalOrder abstract class, the Multiplexer for managing multiple orders, and order types like TWAP.

ConditionalOrder

Abstract base class for all conditional orders. Provides core functionality for order validation, serialization, and polling.

Constructor

from cowdao_cowpy.composable import ConditionalOrder
from cowdao_cowpy.common.chains import Chain
from eth_typing import HexStr

class MyOrder(ConditionalOrder[DataType, StructType]):
    def __init__(
        self,
        handler: HexStr,
        data: DataType,
        salt: HexStr | None = None,
        has_off_chain_input: bool = False,
        chain: Chain = Chain.MAINNET,
    ):
        super().__init__(handler, data, salt, has_off_chain_input, chain)
handler
HexStr
required
The address of the handler contract for the conditional order. Must be a valid Ethereum address.
data
D
required
The data structure containing order parameters in a friendly format.
salt
HexStr | None
default:"None"
A 32-byte hex string used to salt the conditional order. If not provided, a random salt is generated.
has_off_chain_input
bool
default:"False"
Whether the conditional order requires off-chain input for execution.
chain
Chain
default:"Chain.MAINNET"
The blockchain network to use for this order.

Properties

id
HexStr
The unique identifier for the conditional order, calculated as keccak256(serialize()).
order_id = order.id  # Returns HexStr like "0xabc123..."
ctx
str
The context key used to lookup the cabinet in the ComposableCoW contract. Returns bytes32(0) for merkle tree orders, otherwise H(params).
leaf
ConditionalOrderParams
The leaf data structure used for creating merkle trees, containing handler, salt, and encoded static input.
create_calldata
HexStr
The calldata for creating the conditional order on-chain. Automatically determines whether to use create or createWithContext based on the order’s context dependency.
remove_calldata
str
The calldata for removing a conditional order that was created as a single order.
off_chain_input
HexStr
Returns the off-chain input data if the order requires it. Override this method in subclasses that use off-chain input.

Abstract Methods

These methods must be implemented by any class inheriting from ConditionalOrder:

is_valid

def is_valid(self) -> IsValidResult:
    """Validate the conditional order's parameters."""
    pass
return
IsValidResult
Returns an IsValidResult object with is_valid (bool) and optional reason (str) fields.

poll_validate

async def poll_validate(self, params: PollParams) -> Optional[PollResultError]:
    """Perform order-specific validation during polling."""
    pass
params
PollParams
required
Polling parameters including owner, chain, provider, and orderbook API.
return
Optional[PollResultError]
Returns None if validation passes, or a PollResultError if the order should not be polled.

handle_poll_failed_already_present

async def handle_poll_failed_already_present(
    self, order_uid: str, order: Order, params: PollParams
) -> Optional[PollResultError]:
    """Handle the case when the polled order is already in the orderbook."""
    pass

transform_data_to_struct

def transform_data_to_struct(self, params: D) -> S:
    """Convert friendly data format to contract struct format."""
    pass

transform_struct_to_data

def transform_struct_to_data(self, params: S) -> D:
    """Convert contract struct format to friendly data format."""
    pass

to_string

def to_string(self, token_formatter: Optional[Callable] = None) -> str:
    """Create a human-readable representation of the order."""
    pass

serialize

def serialize(self) -> HexStr:
    """Serialize the order to its ABI-encoded form."""
    pass

encode_static_input

def encode_static_input(self) -> HexStr:
    """Encode the staticInput for the conditional order."""
    pass

Methods

poll

async def poll(self, params: PollParams) -> PollResult:
    """Poll the conditional order to check if it's tradeable."""
params
PollParams
required
Polling context including:
  • owner: Owner’s address
  • chain: The blockchain network
  • provider: AsyncWeb3 provider
  • order_book_api: OrderBookApi instance
return
PollResult
Returns either:
  • PollResultSuccess with tradeable order and signature
  • PollResultError with error code and reason
from cowdao_cowpy.composable.types import PollParams

result = await order.poll(PollParams(
    owner="0x...",
    chain=Chain.MAINNET,
    provider=web3,
    order_book_api=api
))

if result.result == PollResultCode.SUCCESS:
    print(f"Order ready: {result.order}")
    print(f"Signature: {result.signature}")

is_authorized

async def is_authorized(self, params: OwnerParams) -> bool:
    """Check if the owner has authorized this conditional order."""
return
bool
Returns True if the order is authorized, False otherwise.

cabinet

async def cabinet(self, params: OwnerParams) -> str:
    """Get the cabinet value for this order and owner."""
return
str
The cabinet value as a hex string without the ‘0x’ prefix.

compute_order_uid

def compute_order_uid(self, chain: Chain, owner: str, order: Order) -> str:
    """Compute the unique identifier for an order."""

encode_static_input_helper

def encode_static_input_helper(
    self, order_data_types: Iterable[TypeStr], static_input: S
) -> HexStr:
    """Helper function for generically serializing static input."""

Static Methods

leaf_to_id

@staticmethod
def leaf_to_id(leaf: ConditionalOrderParams) -> str:
    """Calculate the ID from a conditional order leaf."""

deserialize_helper

@staticmethod
def deserialize_helper(
    encoded_data: HexStr,
    handler: str,
    order_data_types: List[str],
    callback: Callable[[Any, HexStr], T],
) -> T:
    """Helper function for generically deserializing a conditional order."""
encoded_data
HexStr
required
The ABI-encoded IConditionalOrder.Params struct to deserialize.
handler
str
required
Expected handler address for validation.
order_data_types
List[str]
required
ABI types for the order’s data struct.
callback
Callable
required
Function that takes deserialized data and salt, returns order instance.

Multiplexer

Manages multiple conditional orders using merkle trees for efficient on-chain verification.

Constructor

from cowdao_cowpy.composable import Multiplexer, ProofLocation

multiplexer = Multiplexer(
    orders={"order_id_1": order1, "order_id_2": order2},
    root="0xabc...",
    location=ProofLocation.PRIVATE
)
orders
Optional[Dict[str, ConditionalOrder]]
default:"None"
Dictionary mapping order IDs to ConditionalOrder instances. Must have non-zero length if provided.
root
Optional[str]
default:"None"
The merkle tree root. If orders are provided, root must also be provided and must match the computed root.
location
ProofLocation
default:"ProofLocation.PRIVATE"
Where proofs are stored. Options: PRIVATE, EMITTED, SWARM, WAKU, IPFS.

Properties

root
str
The merkle tree root hash.
root = multiplexer.root  # "0xabc123..."
order_ids
List[str]
List of all order IDs in the multiplexer.

Methods

add

def add(self, order: ConditionalOrder):
    """Add a new order to the multiplexer."""
order
ConditionalOrder
required
The conditional order to add. Order is validated before adding.
from cowdao_cowpy.composable import Twap, TwapData

twap = Twap.from_data(TwapData(
    sell_token="0x...",
    buy_token="0x...",
    receiver="0x...",
    sell_amount=1000000,
    buy_amount=900000,
    start_type=StartType.AT_MINING_TIME,
    number_of_parts=10,
    time_between_parts=3600,
    duration_type=DurationType.AUTO,
    app_data="0x..."
))

multiplexer.add(twap)

remove

def remove(self, id: str):
    """Remove an order by ID."""

update

def update(self, id: str, updater: Callable):
    """Update an order using an updater function."""
id
str
required
The ID of the order to update.
updater
Callable
required
Function that takes (order, ctx) and returns the updated order.

get_by_id

def get_by_id(self, id: str) -> ConditionalOrder:
    """Get an order by its ID."""

get_by_index

def get_by_index(self, i: int) -> ConditionalOrder:
    """Get an order by its index in the order list."""

get_proofs

def get_proofs(self, filter: Optional[Callable] = None) -> List[ProofWithParams]:
    """Get merkle proofs for orders, optionally filtered."""
filter
Optional[Callable]
default:"None"
Optional function to filter which orders to include. Takes a ConditionalOrder and returns bool.
return
List[ProofWithParams]
List of proofs with their associated order parameters.

prepare_proof_struct

async def prepare_proof_struct(
    self,
    location: ProofLocation = None,
    filter: Callable = None,
    uploader: Callable = None,
) -> tuple[int, HexBytes]:
    """Prepare proof data for on-chain submission."""
location
ProofLocation
default:"None"
Override the default location for this proof preparation.
filter
Callable
default:"None"
Optional filter function for orders.
uploader
Callable
default:"None"
Required for SWARM, WAKU, or IPFS locations. Async function to upload proof data.
return
tuple[int, HexBytes]
Tuple of (location enum value, proof data bytes).

poll

@staticmethod
async def poll(
    owner: HexStr,
    p: ProofWithParams,
    chain: Chain,
    off_chain_input_fn: Optional[Callable] = None,
) -> Tuple[Order, HexStr]:
    """Poll a specific order from the multiplexer."""
owner
HexStr
required
The owner’s address.
p
ProofWithParams
required
The proof and parameters for the order.
chain
Chain
required
The blockchain network.
off_chain_input_fn
Optional[Callable]
default:"None"
Optional async function to provide off-chain input.
return
Tuple[Order, HexStr]
Returns (tradeable order, signature).

to_json / from_json

def to_json(self) -> str:
    """Serialize multiplexer to JSON."""

@classmethod
def from_json(cls, s: str) -> "Multiplexer":
    """Deserialize multiplexer from JSON."""

register_order_type

@classmethod
def register_order_type(cls, order_type: str, conditional_order_class: type):
    """Register a new order type with the multiplexer."""
order_type
str
required
The string identifier for the order type (e.g., “twap”).
conditional_order_class
type
required
The ConditionalOrder subclass to register.
Multiplexer.register_order_type("twap", Twap)

TWAP Order Type

Time-Weighted Average Price orders split a large order into smaller parts executed over time.

Twap

from cowdao_cowpy.composable import Twap, TwapData, StartType, DurationType
from eth_typing import HexStr

twap = Twap(
    handler=TWAP_ADDRESS,
    data=TwapData(...),
    salt=None
)
handler
HexStr
required
Must be the TWAP handler address: 0x6cF1e9cA41f7611dEf408122793c358a3d11E5a5
data
TwapData
required
The TWAP order parameters.
salt
Optional[HexStr]
default:"None"
Optional salt value.

TwapData

@dataclass
class TwapData:
    sell_token: str
    buy_token: str
    receiver: str
    sell_amount: int
    buy_amount: int
    start_type: StartType
    number_of_parts: int
    time_between_parts: int
    duration_type: DurationType
    app_data: str
    start_time_epoch: int = 0
    duration_of_part: int = 0
sell_token
str
required
Address of the token to sell.
buy_token
str
required
Address of the token to buy.
receiver
str
required
Address to receive the bought tokens.
sell_amount
int
required
Total amount of sell tokens (across all parts).
buy_amount
int
required
Minimum total amount of buy tokens (across all parts).
start_type
StartType
required
When the TWAP starts: StartType.AT_MINING_TIME or StartType.AT_EPOCH.
number_of_parts
int
required
Number of parts to split the order into (2 to 2³²).
time_between_parts
int
required
Seconds between each part (1 to 31,536,000).
duration_type
DurationType
required
DurationType.AUTO or DurationType.LIMIT_DURATION.
app_data
str
required
App data hash for the order.
start_time_epoch
int
default:"0"
Unix timestamp for start time (required if start_type is AT_EPOCH).
duration_of_part
int
default:"0"
Duration of each part in seconds (required if duration_type is LIMIT_DURATION).

Example

from cowdao_cowpy.composable import Twap, TwapData, StartType, DurationType

# Create a TWAP that sells 1000 tokens in 10 parts over 10 hours
twap_data = TwapData(
    sell_token="0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",  # WETH
    buy_token="0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",   # USDC
    receiver="0x...",
    sell_amount=1000000000000000000000,  # 1000 WETH
    buy_amount=3000000000,  # 3000 USDC minimum
    start_type=StartType.AT_MINING_TIME,
    number_of_parts=10,
    time_between_parts=3600,  # 1 hour
    duration_type=DurationType.AUTO,
    app_data="0x..."
)

twap = Twap.from_data(twap_data)

# Validate the order
validation = twap.is_valid()
if validation.is_valid:
    print(f"Order ID: {twap.id}")
    print(f"Create calldata: {twap.create_calldata}")

Types

ProofLocation

class ProofLocation(Enum):
    PRIVATE = 0
    EMITTED = 1
    SWARM = 2
    WAKU = 3
    RESERVED = 4
    IPFS = 5

PollResultCode

class PollResultCode(Enum):
    SUCCESS = "SUCCESS"
    UNEXPECTED_ERROR = "UNEXPECTED_ERROR"
    TRY_NEXT_BLOCK = "TRY_NEXT_BLOCK"
    TRY_ON_BLOCK = "TRY_ON_BLOCK"
    TRY_AT_EPOCH = "TRY_AT_EPOCH"
    DONT_TRY_AGAIN = "DONT_TRY_AGAIN"

IsValidResult

@dataclass
class IsValidResult:
    is_valid: bool
    reason: Optional[str] = None

ConditionalOrderParams

@dataclass
class ConditionalOrderParams:
    handler: HexStr
    salt: HexStr
    static_input: HexStr

Utility Functions

encode_params

def encode_params(params: ConditionalOrderParams) -> HexStr:
    """Encode conditional order parameters to ABI format."""

decode_params

def decode_params(encoded: HexStr) -> ConditionalOrderParams:
    """Decode ABI-encoded parameters to ConditionalOrderParams."""

hash_order

def hash_order(domain: Dict[str, Any], order: Dict[str, Any]) -> str:
    """Compute the EIP-712 hash of an order."""

hash_order_cancellation

def hash_order_cancellation(domain: Dict[str, Any], order_uid: str) -> str:
    """Compute the EIP-712 hash for cancelling a single order."""

hash_order_cancellations

def hash_order_cancellations(domain: Dict[str, Any], order_uids: List[str]) -> str:
    """Compute the EIP-712 hash for cancelling multiple orders."""

encode_order

def encode_order(order: Dict[str, Any]) -> bytes:
    """Encode an order to bytes for hashing."""

Build docs developers (and LLMs) love