Overview
The Composable SDK enables advanced order types like TWAP (Time-Weighted Average Price) and custom conditional orders using the ComposableCoW framework. It provides tools for creating, managing, and polling conditional orders.
Installation
npm install @cowprotocol/sdk-composable
ConditionalOrder
Abstract base class for all conditional orders.
Creating Orders
import { Twap } from '@cowprotocol/sdk-composable'
const twapOrder = Twap . fromData ({
sellToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' , // USDC
buyToken: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2' , // WETH
receiver: userAddress ,
sellAmount: parseUnits ( '10000' , 6 ), // 10,000 USDC
buyAmount: parseUnits ( '5' , 18 ), // 5 WETH minimum
numberOfParts: 10 n ,
timeBetweenParts: 3600 n , // 1 hour
durationOfPart: {
durationType: DurationType . AUTO ,
},
startTime: {
startType: StartTimeValue . AT_MINING_TIME ,
},
appData: '0x...' ,
})
Properties
Address of the handler contract for this conditional order
32-byte salt for uniqueness (auto-generated if not provided)
Order-specific data in user-friendly format
Order data formatted as contract struct
Whether the order requires off-chain input
Unique order ID (keccak256 hash of serialized order)
Descriptive name of the order type (e.g., ‘twap’)
Whether this is a single order (vs part of a merkle tree)
Methods
createCalldata
Get calldata for creating the conditional order on-chain.
const calldata = twapOrder . createCalldata
// Use with Safe transaction or direct contract call
Encoded function call to create the order (emits ConditionalOrderCreated event)
removeCalldata
Get calldata for removing the conditional order.
const calldata = twapOrder . removeCalldata
Encoded function call to remove the order
poll()
Poll the conditional order to check if it’s tradeable.
const result = await twapOrder . poll ({
chainId: SupportedChainId . MAINNET ,
owner: safeAddress ,
provider: ethersProvider ,
orderBookApi ,
})
if ( result . result === PollResultCode . SUCCESS ) {
console . log ( 'Order is tradeable:' , result . order )
console . log ( 'Signature:' , result . signature )
}
Chain ID where the order exists
Owner address (typically a Safe)
Ethereum provider for blockchain calls
Off-chain input if required by the order type
Merkle proof if order is part of a tree
Current block info for validation
Poll result indicating order status SUCCESS | TRY_NEXT_BLOCK | TRY_ON_BLOCK | TRY_AT_EPOCH | DONT_TRY_AGAIN | UNEXPECTED_ERROR
Order data (only present when result is SUCCESS)
Order signature (only present when result is SUCCESS)
Human-readable reason for the result
isAuthorized()
Check if the owner authorized this conditional order.
const isAuthorized = await twapOrder . isAuthorized ({
chainId: SupportedChainId . MAINNET ,
owner: safeAddress ,
provider: ethersProvider ,
})
True if the owner authorized the order
serialize()
Serialize the order into ABI-encoded form.
const serialized = twapOrder . serialize ()
Twap
Time-Weighted Average Price order implementation.
Creating TWAP Orders
import { Twap , StartTimeValue , DurationType } from '@cowprotocol/sdk-composable'
const twap = Twap . fromData ({
sellToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' ,
buyToken: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2' ,
receiver: userAddress ,
sellAmount: parseUnits ( '10000' , 6 ),
buyAmount: parseUnits ( '5' , 18 ),
numberOfParts: 10 n ,
timeBetweenParts: 3600 n , // 1 hour between parts
startTime: {
startType: StartTimeValue . AT_EPOCH ,
epoch: BigInt ( Math . floor ( Date . now () / 1000 ) + 3600 ), // Start in 1 hour
},
durationOfPart: {
durationType: DurationType . LIMIT_DURATION ,
duration: 1800 n , // Each part valid for 30 minutes
},
appData: keccak256 ( JSON . stringify ( appData )),
})
TwapData Parameters
Sell token contract address
Buy token contract address
Address to receive the bought tokens
Total amount to sell across all parts (in token atoms)
Minimum total amount to buy across all parts (in token atoms)
Number of parts to split the order into (must be > 1)
Duration between each part in seconds (max 1 year)
When the TWAP should start AT_MINING_TIME | AT_EPOCH
Unix timestamp for start (required if startType is AT_EPOCH)
How long each part remains valid Duration in seconds (required if durationType is LIMIT_DURATION)
keccak256 hash of app data document
TWAP Methods
fromParams()
Create TWAP from ConditionalOrderParams.
const params : ConditionalOrderParams = {
handler: TWAP_ADDRESS ,
salt: '0x...' ,
staticInput: '0x...' ,
}
const twap = Twap . fromParams ( params )
deserialize()
Deserialize a TWAP from ABI-encoded form.
const twap = Twap . deserialize ( serializedData )
TWAP Constants
import { TWAP_ADDRESS , CURRENT_BLOCK_TIMESTAMP_FACTORY_ADDRESS } from '@cowprotocol/sdk-composable'
// TWAP handler contract address
const handler = TWAP_ADDRESS // 0x6cF1e9cA41f7611dEf408122793c358a3d11E5a5
// Factory for dynamic start times
const factory = CURRENT_BLOCK_TIMESTAMP_FACTORY_ADDRESS // 0x52eD56Da04309Aca4c3FECC595298d80C2f16BAc
Multiplexer
Manages multiple conditional orders using a Merkle tree.
Creating a Multiplexer
import { Multiplexer , Twap } from '@cowprotocol/sdk-composable'
// Register order types before using multiplexer
Multiplexer . registerOrderType ( 'twap' , Twap )
const multiplexer = new Multiplexer (
SupportedChainId . MAINNET ,
undefined , // orders (optional)
undefined , // root (optional)
ProofLocation . EMITTED ,
)
Chain ID for ComposableCoW
orders
Record<string, ConditionalOrder>
Optional initial orders (Record of string keys to ConditionalOrder values)
Optional Merkle root to verify against
PRIVATE | EMITTED | SWARM | WAKU | IPFS
Adding Orders
const twap1 = Twap . fromData ({ /* ... */ })
const twap2 = Twap . fromData ({ /* ... */ })
multiplexer . add ( twap1 )
multiplexer . add ( twap2 )
console . log ( 'Merkle root:' , multiplexer . root )
Removing Orders
multiplexer . remove ( twap1 . id )
Updating Orders
multiplexer . update ( twap1 . id , ( existingOrder ) => {
// Create a new order based on existing one
const twapData = ( existingOrder as Twap ). data
return Twap . fromData ({
... twapData ,
buyAmount: twapData . buyAmount * 2 n , // Double the buy amount
})
})
Accessing Orders
// Get by ID
const order = multiplexer . getById ( twap1 . id )
// Get by index
const firstOrder = multiplexer . getByIndex ( 0 )
// Get all order IDs
const orderIds = multiplexer . orderIds
Preparing Proofs
Generate proofs for setting the Merkle root on-chain.
const proofStruct = await multiplexer . prepareProofStruct (
ProofLocation . EMITTED ,
undefined , // optional filter function
undefined , // optional uploader function for IPFS/Swarm
)
// Use with setRoot or setRootWithContext on ComposableCoW contract
proofStruct
ComposableCoW.ProofStruct
Proof location enum value
Proof data (ABI-encoded for EMITTED, IPFS hash for IPFS, etc.)
Serialization
// Serialize to JSON
const json = multiplexer . toJSON ()
// Deserialize from JSON
const restored = Multiplexer . fromJSON ( json )
// Dump proofs for watchtowers
const proofs = multiplexer . dumpProofs ()
ConditionalOrderFactory
Factory for creating and deploying conditional orders.
import { ConditionalOrderFactory } from '@cowprotocol/sdk-composable'
const factory = new ConditionalOrderFactory ({
chainId: SupportedChainId . MAINNET ,
provider: ethersProvider ,
signer: ethersSigner ,
})
Types
PollResultCode
Enum for poll result status.
enum PollResultCode {
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' ,
}
ProofLocation
Enum for proof storage location.
enum ProofLocation {
PRIVATE = 0 ,
EMITTED = 1 ,
SWARM = 2 ,
WAKU = 3 ,
RESERVED = 4 ,
IPFS = 5 ,
}
ConditionalOrderParams
Parameters for a conditional order.
interface ConditionalOrderParams {
handler : string
salt : string
staticInput : string
}
Examples
Create and Deploy TWAP
import { Twap , StartTimeValue , DurationType } from '@cowprotocol/sdk-composable'
import { SupportedChainId } from '@cowprotocol/sdk-config'
// Create TWAP order
const twap = Twap . fromData ({
sellToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' , // USDC
buyToken: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2' , // WETH
receiver: '0x...' , // Your address
sellAmount: parseUnits ( '10000' , 6 ), // 10k USDC total
buyAmount: parseUnits ( '5' , 18 ), // Min 5 WETH total
numberOfParts: 24 n , // 24 parts
timeBetweenParts: 3600 n , // 1 hour between each
startTime: {
startType: StartTimeValue . AT_MINING_TIME ,
},
durationOfPart: {
durationType: DurationType . AUTO ,
},
appData: '0x0000000000000000000000000000000000000000000000000000000000000000' ,
})
// Validate
const isValid = twap . isValid ()
if ( ! isValid . isValid ) {
console . error ( 'Invalid TWAP:' , isValid . reason )
throw new Error ( isValid . reason )
}
// Get creation calldata
const calldata = twap . createCalldata
// Execute via Safe or direct contract call
// This will emit ConditionalOrderCreated event
Multiple Conditional Orders
import { Multiplexer , Twap , ProofLocation } from '@cowprotocol/sdk-composable'
// Register order type
Multiplexer . registerOrderType ( 'twap' , Twap )
// Create multiplexer
const multiplexer = new Multiplexer (
SupportedChainId . MAINNET ,
undefined ,
undefined ,
ProofLocation . EMITTED ,
)
// Add multiple TWAP orders
const twap1 = Twap . fromData ({ /* USDC -> WETH */ })
const twap2 = Twap . fromData ({ /* DAI -> WETH */ })
const twap3 = Twap . fromData ({ /* USDT -> WETH */ })
multiplexer . add ( twap1 )
multiplexer . add ( twap2 )
multiplexer . add ( twap3 )
// Prepare proof struct for on-chain submission
const proofStruct = await multiplexer . prepareProofStruct ()
// Save the multiplexer state
const savedState = multiplexer . toJSON ()
localStorage . setItem ( 'my-twaps' , savedState )
// Later: restore state
const restored = Multiplexer . fromJSON ( savedState )
console . log ( 'Restored orders:' , restored . orderIds . length )
Watch Tower Integration
import { Multiplexer , Twap } from '@cowprotocol/sdk-composable'
Multiplexer . registerOrderType ( 'twap' , Twap )
// Load multiplexer
const multiplexer = Multiplexer . fromJSON ( savedState )
// Poll each order
for ( const orderId of multiplexer . orderIds ) {
const order = multiplexer . getById ( orderId )
const result = await order . poll ({
chainId: SupportedChainId . MAINNET ,
owner: safeAddress ,
provider: ethersProvider ,
orderBookApi ,
})
if ( result . result === PollResultCode . SUCCESS ) {
console . log ( 'Order ready to execute:' , orderId )
console . log ( 'Order data:' , result . order )
console . log ( 'Signature:' , result . signature )
// Submit to orderbook
await orderBookApi . sendOrder ({
... result . order ,
signature: result . signature ,
signingScheme: 'eip1271' ,
})
} else if ( result . result === PollResultCode . TRY_AT_EPOCH ) {
console . log ( `Order ${ orderId } not ready until` , new Date ( result . epoch * 1000 ))
} else if ( result . result === PollResultCode . DONT_TRY_AGAIN ) {
console . log ( `Order ${ orderId } expired or cancelled:` , result . reason )
}
}
Custom Conditional Order
import { ConditionalOrder } from '@cowprotocol/sdk-composable'
interface MyOrderData {
token : string
amount : bigint
condition : string
}