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)
The address of the handler contract for the conditional order. Must be a valid Ethereum address.
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.
Whether the conditional order requires off-chain input for execution.
chain
Chain
default:"Chain.MAINNET"
The blockchain network to use for this order.
Properties
The unique identifier for the conditional order, calculated as keccak256(serialize()).order_id = order.id # Returns HexStr like "0xabc123..."
The context key used to lookup the cabinet in the ComposableCoW contract. Returns bytes32(0) for merkle tree orders, otherwise H(params).
The leaf data structure used for creating merkle trees, containing handler, salt, and encoded static input.
The calldata for creating the conditional order on-chain. Automatically determines whether to use create or createWithContext based on the order’s context dependency.
The calldata for removing a conditional order that was created as a single order.
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
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
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
def transform_data_to_struct(self, params: D) -> S:
"""Convert friendly data format to contract struct format."""
pass
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
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."""
Polling context including:
owner: Owner’s address
chain: The blockchain network
provider: AsyncWeb3 provider
order_book_api: OrderBookApi instance
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."""
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."""
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."""
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."""
The ABI-encoded IConditionalOrder.Params struct to deserialize.
Expected handler address for validation.
ABI types for the order’s data struct.
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
The merkle tree root hash.root = multiplexer.root # "0xabc123..."
List of all order IDs in the multiplexer.
Methods
add
def add(self, order: ConditionalOrder):
"""Add a new order to the multiplexer."""
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."""
The ID of the order to update.
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.
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.
Optional filter function for orders.
Required for SWARM, WAKU, or IPFS locations. Async function to upload proof data.
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."""
The proof and parameters for the order.
off_chain_input_fn
Optional[Callable]
default:"None"
Optional async function to provide off-chain input.
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."""
The string identifier for the order type (e.g., “twap”).
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
)
Must be the TWAP handler address: 0x6cF1e9cA41f7611dEf408122793c358a3d11E5a5
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
Address of the token to sell.
Address of the token to buy.
Address to receive the bought tokens.
Total amount of sell tokens (across all parts).
Minimum total amount of buy tokens (across all parts).
When the TWAP starts: StartType.AT_MINING_TIME or StartType.AT_EPOCH.
Number of parts to split the order into (2 to 2³²).
Seconds between each part (1 to 31,536,000).
DurationType.AUTO or DurationType.LIMIT_DURATION.
App data hash for the order.
Unix timestamp for start time (required if start_type is AT_EPOCH).
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."""