Skip to main content

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: 10n,
  timeBetweenParts: 3600n, // 1 hour
  durationOfPart: {
    durationType: DurationType.AUTO,
  },
  startTime: {
    startType: StartTimeValue.AT_MINING_TIME,
  },
  appData: '0x...',
})

Properties

handler
string
Address of the handler contract for this conditional order
salt
string
32-byte salt for uniqueness (auto-generated if not provided)
data
D
Order-specific data in user-friendly format
staticInput
S
Order data formatted as contract struct
hasOffChainInput
boolean
Whether the order requires off-chain input
id
string
Unique order ID (keccak256 hash of serialized order)
orderType
string
Descriptive name of the order type (e.g., ‘twap’)
isSingleOrder
boolean
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
calldata
string
Encoded function call to create the order (emits ConditionalOrderCreated event)

removeCalldata

Get calldata for removing the conditional order.
const calldata = twapOrder.removeCalldata
calldata
string
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)
}
chainId
SupportedChainId
required
Chain ID where the order exists
owner
string
required
Owner address (typically a Safe)
provider
Provider
required
Ethereum provider for blockchain calls
orderBookApi
OrderBookApi
required
OrderBook API instance
offChainInput
string
Off-chain input if required by the order type
proof
string[]
Merkle proof if order is part of a tree
blockInfo
BlockInfo
Current block info for validation
result
PollResult
Poll result indicating order status

isAuthorized()

Check if the owner authorized this conditional order.
const isAuthorized = await twapOrder.isAuthorized({
  chainId: SupportedChainId.MAINNET,
  owner: safeAddress,
  provider: ethersProvider,
})
authorized
boolean
True if the owner authorized the order

serialize()

Serialize the order into ABI-encoded form.
const serialized = twapOrder.serialize()
serialized
string
ABI-encoded order params

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: 10n,
  timeBetweenParts: 3600n, // 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: 1800n, // Each part valid for 30 minutes
  },
  appData: keccak256(JSON.stringify(appData)),
})

TwapData Parameters

sellToken
string
required
Sell token contract address
buyToken
string
required
Buy token contract address
receiver
string
required
Address to receive the bought tokens
sellAmount
bigint
required
Total amount to sell across all parts (in token atoms)
buyAmount
bigint
required
Minimum total amount to buy across all parts (in token atoms)
numberOfParts
bigint
required
Number of parts to split the order into (must be > 1)
timeBetweenParts
bigint
required
Duration between each part in seconds (max 1 year)
startTime
StartTime
required
When the TWAP should start
durationOfPart
DurationOfPart
required
How long each part remains valid
appData
string
required
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,
)
chainId
SupportedChainId
required
Chain ID for ComposableCoW
orders
Record<string, ConditionalOrder>
Optional initial orders (Record of string keys to ConditionalOrder values)
root
string
Optional Merkle root to verify against
proofLocation
ProofLocation
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 * 2n, // 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

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: 24n, // 24 parts
  timeBetweenParts: 3600n, // 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
}
Last modified on March 4, 2026