A proof of concept demonstrating how to match CoW Protocol order UIDs with ComposableCoW conditional orders by decoding the order signature.
This project shows how to link orders from the CoW Protocol API back to their originating ConditionalOrderCreated events by decoding the EIP-1271 signature. This approach works for any ComposableCoW order type (Perpetual Swaps, TWAP, etc.) and doesn't require on-chain calls.
The challenge with programmatic orders is that the order details are computed dynamically by a handler contract. The order UID alone doesn't reveal which conditional order created it. However, the signature contains all the information needed to trace back to the original conditional order.
When a ComposableCoW order is submitted to CoW Protocol, it uses EIP-1271 signature verification. For Safe wallets, the signature follows a specific structure that embeds the ComposableCoW payload.
┌─────────────────────────────────────────────────────────────────┐
│ Safe Signature │
│ selector: safeSignature(bytes32,bytes32,bytes,bytes) │
├─────────────────────────────────────────────────────────────────┤
│ ├── domainSeparator (bytes32) │
│ ├── typeHash (bytes32) │
│ ├── encodeData (bytes) │
│ └── payload (bytes) ◄─── Contains ComposableCoW data │
│ ├── proof (bytes32[]) - Merkle proof (if using root) │
│ ├── params (tuple) │
│ │ ├── handler (address) - e.g., PerpetualSwap handler │
│ │ ├── salt (bytes32) │
│ │ └── staticInput (bytes) - Handler-specific parameters │
│ └── offchainInput (bytes) │
└─────────────────────────────────────────────────────────────────┘
1. ConditionalOrderCreated event
├── Compute hash = keccak256(abi.encode(handler, salt, staticInput))
└── Store: owner, handler, salt, staticInput, hash
2. Order from CoW Protocol API
├── Check if signature starts with safeSignature selector
├── Decode Safe signature to extract payload
├── Decode payload to get ComposableCoW params
├── Compute hash = keccak256(abi.encode(params))
└── Match hash with indexed conditional orders
-
Index conditional orders: When a
ConditionalOrderCreatedevent is emitted, compute and store the hash of(handler, salt, staticInput). -
Decode the signature: Extract the function selector to identify Safe signatures, then decode the ABI-encoded parameters.
-
Extract the payload: The fourth parameter of
safeSignaturecontains the ComposableCoW payload with proof and order params. -
Hash and match: Compute the same hash from the decoded params and match against indexed conditional orders.
// 1. Check if signature uses Safe's safeSignature format
function getSignatureSelectorAndData(signature: Hex) {
const selector = signature.slice(0, 10);
const isSafe = selector === SAFE_SIGNATURE_SELECTOR;
return { selector, data, isSafe };
}
// 2. Decode Safe signature to extract components
function decodeSafeSignatureData(signatureData: Hex) {
// Returns: domainSeparator, typeHash, encodeData, payload
return decodeAbiParameters([...], signatureData);
}
// 3. Decode ComposableCoW payload and hash the params
function hashComposableCoWPayload(payload: Hex) {
// Decode: { proof, params: { handler, salt, staticInput }, offchainInput }
// Return: keccak256(abi.encode(params))
}Built with Ponder, an event indexing framework, the application:
- Listens to
ComposableCoW:ConditionalOrderCreatedevents - Computes and stores the conditional order hash
- Decodes signatures from order examples to match them back to events
- Exposes GraphQL and SQL APIs for querying matched orders
| Contract | Address |
|---|---|
| ComposableCoW | 0xfdaFc9d1902f4e0b84f65F49f244b32b31013b74 |
| Perpetual Swap Handler | 0x519ba24e959e33b3b6220ca98bd353d8c2d89920 |
- Node.js >= 18.14
- An Ethereum mainnet RPC URL
pnpm installSet your RPC URL in the environment:
export MAINNET_RPC_URL="https://your-rpc-url"Development mode with hot reload:
pnpm devProduction mode:
pnpm start- GraphQL:
http://localhost:42069/graphql - SQL:
http://localhost:42069/sql
Stores all conditional orders indexed from ComposableCoW.
| Field | Type | Description |
|---|---|---|
| id | text | Event ID (primary key) |
| owner | text | Order owner address |
| handler | text | Handler contract address |
| salt | text | Order salt |
| staticInput | text | Static input for the handler |
| hash | text | keccak256 hash of (handler, salt, staticInput) |
| txHash | text | Transaction hash of the creation event |
Links CoW Protocol orders to their corresponding conditional orders.
| Field | Type | Description |
|---|---|---|
| orderUid | text | The CoW Protocol order UID (primary key) |
| conditionalOrderId | text | Reference to conditional order |
This is a proof of concept with the following limitations:
- Uses a hardcoded order example for demonstration purposes
- Only handles Safe wallet signatures (safeSignature selector)
- Only indexes a specific block range on mainnet
For programmatic orders where on-chain parameters affect the final order (like oracle prices), you cannot simply reconstruct the order UID from stored data. The signature, however, contains:
- The exact conditional order params used to create the order
- Merkle proofs (for orders using merkle roots)
- Off-chain inputs provided at execution time
By decoding the signature, we can trace any ComposableCoW order back to its origin without requiring on-chain calls or storing execution-time data.