Skip to main content

Signature Construction

ComposableCoW orders use EIP-1271 (smart contract) signatures rather than EOA signatures. This guide explains how to construct the signature bytes that the settlement contract needs to verify your programmatic order.
This guide assumes you have already set up your Safe with the ExtensibleFallbackHandler and ComposableCoW as domain verifier, and created a programmatic order via create() or createWithContext().

How signature verification works

When the CoW Protocol settlement contract executes a trade for a ComposableCoW order, the following verification chain runs:
GPv2Settlement

  ├─ calls isValidSignature(orderDigest, signature) on the Safe


Safe (owner)

  ├─ delegates to ExtensibleFallbackHandler (fallback handler)


ExtensibleFallbackHandler

  ├─ extracts the domain separator from the signature
  ├─ routes to the registered domain verifier (ComposableCoW)


ComposableCoW

  ├─ decodes ConditionalOrderParams and offchainInput from signature
  ├─ verifies the order was authorized (via create() or merkle proof)
  ├─ calls getTradeableOrderWithSignature() on the handler
  ├─ compares the resulting order's digest to the provided orderDigest
  └─ returns EIP-1271 magic value (0x1626ba7e) if valid
The signature you construct is what gets passed through this entire chain. ComposableCoW decodes it to recover the ConditionalOrderParams and any offchain input, then uses these to regenerate the expected order and verify it matches.

Signature encoding

The signature for a ComposableCoW order is an ABI-encoded byte string containing two values:
ComponentTypeDescription
paramsConditionalOrderParamsThe handler address, salt, and staticInput used when creating the order
offchainInputbytesAny additional off-chain data the handler needs (empty bytes if none)
The ConditionalOrderParams struct consists of:
FieldTypeDescription
handleraddressThe programmatic order handler contract (e.g., TWAP at 0x6cF1e9cA41f7611dEf408122793c358a3d11E5a5)
saltbytes32Unique identifier for this order
staticInputbytesABI-encoded parameters specific to the handler
The encoding format is:
abi.encode(ConditionalOrderParams params, bytes offchainInput)

Step-by-step construction

1
Retrieve your ConditionalOrderParams
2
Use the exact same handler, salt, and staticInput values that you passed to ComposableCoW.create() or createWithContext() when you created the order.
3
The params must match exactly. Even a single byte difference in staticInput produces a different order hash, and ComposableCoW will reject the signature because the order was never authorized.
4
Prepare offchain input
5
Determine whether your handler requires offchain input. Most standard handlers (TWAP, StopLoss) use empty bytes (0x). Custom handlers may require additional data that is not stored on-chain but is needed to generate the tradeable order.
6
If your handler’s getTradeableOrder() function uses the offchainInput parameter, you must provide the expected value. Otherwise, pass empty bytes.
7
ABI-encode the signature
8
Encode the ConditionalOrderParams struct and offchain input together:
9
ethers.js v6
import { AbiCoder } from 'ethers';

const encoder = new AbiCoder();

// Your order parameters (must match what was passed to create())
const params = {
  handler: '0x6cF1e9cA41f7611dEf408122793c358a3d11E5a5', // e.g. TWAP handler
  salt: '0x...', // your unique salt
  staticInput: '0x...' // ABI-encoded handler-specific params
};

// Empty bytes if handler needs no offchain input
const offchainInput = '0x';

const signature = encoder.encode(
  ['tuple(address handler, bytes32 salt, bytes staticInput)', 'bytes'],
  [params, offchainInput]
);
viem
import { encodeAbiParameters } from 'viem';

const params = {
  handler: '0x6cF1e9cA41f7611dEf408122793c358a3d11E5a5' as `0x${string}`,
  salt: '0x...' as `0x${string}`,
  staticInput: '0x...' as `0x${string}`
};

const offchainInput = '0x' as `0x${string}`;

const signature = encodeAbiParameters(
  [
    {
      type: 'tuple',
      components: [
        { name: 'handler', type: 'address' },
        { name: 'salt', type: 'bytes32' },
        { name: 'staticInput', type: 'bytes' }
      ]
    },
    { type: 'bytes' }
  ],
  [params, offchainInput]
);
web3.py
from eth_abi import encode

params = (
    '0x6cF1e9cA41f7611dEf408122793c358a3d11E5a5',  # handler
    bytes.fromhex('...'),  # salt (32 bytes)
    bytes.fromhex('...')   # staticInput
)

offchain_input = b''  # empty bytes if handler needs no offchain input

signature = encode(
    ['(address,bytes32,bytes)', 'bytes'],
    [params, offchain_input]
)
10
Set the signing scheme to EIP-1271
11
When submitting the order to the CoW Protocol Order Book API, set the signingScheme field to eip1271:
12
{
  "signingScheme": "eip1271"
}
13
Set the from field to the Safe address
14
The from field must be the address of the Safe that owns the programmatic order, not any EOA signer of the Safe.
15
{
  "from": "0x...SafeAddress",
  "signingScheme": "eip1271",
  "signature": "0x...encodedSignature"
}
16
The from field is required for EIP-1271 orders because the signature cannot be used to recover the signer’s address (unlike ECDSA signatures). The settlement contract uses this address to know which contract to call isValidSignature on.

Full example

Putting it all together, here is a complete example of constructing and submitting a ComposableCoW order signature:
import { AbiCoder } from 'ethers';

// The Safe address that owns the programmatic order
const safeAddress = '0xYourSafeAddress';

// Must match exactly what was passed to ComposableCoW.create()
const params = {
  handler: '0x6cF1e9cA41f7611dEf408122793c358a3d11E5a5',
  salt: '0xabc123...', // your unique salt
  staticInput: '0xdef456...' // your encoded handler params
};

const offchainInput = '0x';

// Step 1: Encode the signature
const encoder = new AbiCoder();
const signature = encoder.encode(
  ['tuple(address handler, bytes32 salt, bytes staticInput)', 'bytes'],
  [params, offchainInput]
);

// Step 2: Submit to the CoW Protocol API
const orderPayload = {
  // ... your order fields (sellToken, buyToken, etc.)
  from: safeAddress,
  signingScheme: 'eip1271',
  signature: signature,
};

const response = await fetch(
  'https://api.cow.fi/mainnet/api/v1/orders',
  {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(orderPayload),
  }
);

Common pitfalls

PitfallSymptomFix
Wrong signingSchemeAPI rejects the order or signature verification failsSet signingScheme to eip1271, not eip712 or ethsign
Wrong from addressisValidSignature is called on the wrong contractUse the Safe address, not an EOA signer address
Mismatched ConditionalOrderParamsComposableCoW cannot find the order in singleOrders mappingEnsure handler, salt, and staticInput exactly match what was passed to create()
Handler returns unexpected orderOrder digest mismatch during verificationVerify the handler’s getTradeableOrder() output matches the order you are submitting
Missing offchain inputHandler reverts when generating the tradeable orderProvide the offchain data your handler expects, or empty bytes if none
Safe not configuredisValidSignature reverts or returns wrong valueEnsure ExtensibleFallbackHandler is set and ComposableCoW is registered as domain verifier (Setup guide)

Verifying locally

Before submitting to the API, you can verify your signature will pass validation by calling the isValidSignature function directly on the Safe:
import { Contract, AbiCoder } from 'ethers';

// Compute the order digest using @cowprotocol/contracts
import { domain, hashOrder } from '@cowprotocol/contracts';

const orderDigest = hashOrder(
  domain(1, '0x9008D19f58AAbD9eD0D60971565AA8510560ab41'),
  order
);

// Call isValidSignature on the Safe
const safe = new Contract(safeAddress, [
  'function isValidSignature(bytes32, bytes) view returns (bytes4)'
], provider);

const MAGIC_VALUE = '0x1626ba7e';

try {
  const result = await safe.isValidSignature(orderDigest, signature);
  if (result === MAGIC_VALUE) {
    console.log('Signature is valid');
  } else {
    console.log('Signature is invalid, returned:', result);
  }
} catch (error) {
  console.error('Verification reverted:', error.message);
}
This call simulates the exact check the settlement contract performs during order execution. If it returns the magic value (0x1626ba7e), your signature will be accepted.

Next steps

Last modified on March 12, 2026