Skip to main content

Troubleshooting

This guide covers the most common issues developers hit when setting up ComposableCoW, based on frequent questions in the CoW Protocol Discord.

Setup issues

setDomainVerifier transaction reverts

The most common setup error. The call to setDomainVerifier must be made from the Safe to itself — not to the ExtensibleFallbackHandler.
setDomainVerifier is a function on the ExtensibleFallbackHandler, but it must be called via the Safe’s execTransaction. The Safe calls itself, and the fallback handler intercepts the call.
Correct flow:
  1. Safe executes a transaction where to = the Safe’s own address
  2. The call data encodes setDomainVerifier(domainSeparator, composableCowAddress)
  3. The ExtensibleFallbackHandler (set as the Safe’s fallback handler) processes it
Wrong: Calling setDomainVerifier directly on the ExtensibleFallbackHandler address — this will revert or have no effect.

Correct setup sequence

The setup must happen in this exact order:
  1. Set the fallback handler on the Safe to ExtensibleFallbackHandler (0x2f55e8b20D0B9FEFA187AA7d00B6Cbe563605bF5)
  2. Register ComposableCoW as domain verifier by calling setDomainVerifier from the Safe to itself
  3. Create programmatic orders via ComposableCoW.create()
If step 1 is not done before step 2, the setDomainVerifier call will silently fail (no fallback handler to intercept it).

Verifying setup is correct

Check both configurations on-chain: 1. Verify fallback handler:
Expected: 0x2f55e8b20D0B9FEFA187AA7d00B6Cbe563605bF5 (ExtensibleFallbackHandler) 2. Verify domain verifier:
Expected: 0xfdaFc9d1902f4e0b84f65F49f244b32b31013b74 (ComposableCoW) The domain separator for CoW Protocol is chain-specific. You can read it from the settlement contract:

MultiSend atomic setup

To do both steps atomically in a single Safe transaction:
Bundle via Safe’s MultiSend contract.

Order not appearing in explorer

After creating a programmatic order, it should appear in CoW Explorer. If it doesn’t:

1. Check if the watch-tower picked it up

The watch-tower monitors ConditionalOrderCreated events. If it hasn’t indexed your order:
  • Was the transaction confirmed? Check the tx hash on the block explorer
  • Did the event fire? Look for ConditionalOrderCreated in the transaction logs
  • Is the watch-tower running for your chain? The public watch-tower covers Ethereum, Gnosis Chain, Arbitrum One, Base, Polygon, Avalanche, BNB Chain, Linea, Plasma, Ink, and Sepolia
  • Is there a delay? The watch-tower processes blocks periodically — wait a few minutes

2. Check if the order is currently valid

The watch-tower calls getTradeableOrder() on your handler. If it reverts, the order won’t be posted. Common reasons:
  • Balance insufficient — the Safe doesn’t hold enough sell tokens
  • Time condition not met — for TWAP, the current part hasn’t started yet
  • Guard rejection — a swap guard is blocking the order

3. Run your own watch-tower for debugging

The --one-shot flag processes once and exits. --dry-run shows what orders would be posted without actually posting them.

Foundry / Forge dependency issues

When installing ComposableCoW with Forge, Safe library dependencies may not resolve automatically.

Missing SignatureVerifierMuxer.sol

If this fails to resolve Safe dependencies, add the remappings manually:
You may also need to install the Safe contracts explicitly:

Order stopped being polled unexpectedly

If your programmatic order was being polled by the Watch Tower but silently stopped without being cancelled or fulfilled, the most likely cause is an unrecognized revert error from your handler contract.

Root cause

When the Watch Tower calls getTradeableOrderWithSignature on your handler and the call reverts, it attempts to decode the revert data against a set of known custom errors:
  • OrderNotValid(string)
  • PollTryNextBlock
  • PollTryAtBlock(uint256)
  • PollTryAtEpoch(uint256, uint256)
  • PollNever(string)
If the revert bytes do not match any of these, the Watch Tower classifies the result as UNEXPECTED_ERROR and permanently stops polling the order. There is no notification to the order creator.

Common triggers

  • Arithmetic overflow/underflow — Solidity 0.8+ reverts with a Panic(uint256) error code, which the Watch Tower does not recognize as a polling hint
  • Failed external calls — If your handler calls another contract that reverts with its own custom error, those bytes propagate up and won’t match the known set
  • Generic revert() or require(false) — These produce empty or non-standard revert data
  • Out-of-gas in sub-calls — Can produce unexpected revert behavior

Debugging steps

  1. Reproduce the revert locally. Call getTradeableOrderWithSignature with the same parameters the Watch Tower uses (owner, params, offchainInput, proof) using cast call or a Foundry test:
  2. Inspect the revert data. If the call fails, check the raw revert bytes. Compare the first 4 bytes (the error selector) against the known selectors:
    • OrderNotValid: 0x16f2d3f3
    • PollTryNextBlock: 0x44882c68
    • PollTryAtBlock: 0x2d6e06d5
    • PollTryAtEpoch: 0x4184e129
  3. Check for arithmetic edge cases. Review your handler for divisions that could hit zero denominators, multiplications that could overflow, or timestamp arithmetic that could underflow.
  4. Run a local Watch Tower with --one-shot --only-owner <YOUR_SAFE> and LOG_LEVEL=DEBUG to see the exact error classification.

Best practice: defensive error handling

Wrap your handler’s core logic in a try/catch so that any unexpected failure maps to a recognized error:
This ensures the Watch Tower always receives a recognized error and continues polling the order on subsequent blocks.
This is a critical pattern for any custom handler. Without it, a single unexpected revert permanently removes your order from the Watch Tower’s polling cycle with no notification. See Watch Tower Polling — On-chain errors for full details.

Salt and replay protection

Each programmatic order needs a unique salt. If you reuse a salt for the same handler + parameters + Safe, the order will be a duplicate.
  • Use keccak256(abi.encodePacked(block.timestamp, nonce)) or similar for unique salts
  • The salt is used to compute the order’s storage key in ComposableCoW
  • Cancelling an order frees its salt for reuse

Contract addresses (all chains)

These addresses are the same on Ethereum, Gnosis Chain, Arbitrum One, Base, Polygon, Avalanche, BNB Chain, Linea, Plasma, Ink, and Sepolia.
Last modified on March 17, 2026