Skip to main content

Architecture

Technical architecture and implementation details of the CoW Protocol Watch Tower

High-level architecture

The Watch Tower follows an event-driven architecture with three main components:
  1. Event Monitor - Listens for blockchain events and indexes new programmatic orders
  2. Registry/Storage - Maintains state of all active programmatic orders using LevelDB
  3. Order Poller - Continuously evaluates programmatic orders and submits to OrderBook API

Event monitoring flow

The watch tower monitors the ComposableCoW contract for two critical events:

ConditionalOrderCreated event

Emitted when a single programmatic order is created by a user:
Processing flow:
  1. Watch tower detects the event via eth_getLogs RPC calls
  2. Decodes the event data to extract owner address and order parameters
  3. Validates the order against filter policies (if configured)
  4. Stores the order in the registry indexed by owner
  5. Increments metrics for tracking
The watch tower uses the event topic hash to efficiently filter logs: keccak256("ConditionalOrderCreated(address,(address,bytes32,bytes))").

MerkleRootSet event

Emitted when a batch of programmatic orders (merkle tree) is set for a safe:
Processing flow:
  1. Detects the merkle root event
  2. Flushes any existing orders for the owner with different merkle roots
  3. Decodes the proof data containing multiple orders
  4. Extracts each order’s parameters and merkle path
  5. Stores all orders with their merkle proofs
Merkle roots allow users to efficiently set multiple programmatic orders in a single transaction, reducing gas costs. The watch tower reconstructs each individual order from the merkle proof.

Registry and storage architecture

The watch tower uses LevelDB as its persistent storage layer, chosen for its ACID guarantees and simplicity as a key-value store.

Database implementation

Storage schema

The watch tower maintains the following keys in LevelDB:

Registry data model

The Registry class manages the in-memory and persistent state:
Each ConditionalOrder contains:

Atomic writes

All database writes are batched for atomicity:
If a write fails, the watch tower throws an error and exits. On restart, it re-processes from the last successfully indexed block, ensuring eventual consistency with the blockchain.

Order polling and submission

After indexing programmatic orders, the watch tower continuously polls them to check if execution conditions are met.

Polling process

For each block processed, the watch tower:
  1. Iterates through all registered owners and their programmatic orders
  2. Calls the order’s poll method from the Composable SDK
  3. Evaluates the returned PollResult
  4. Submits discrete orders to the OrderBook API if conditions are satisfied
By default, the watch tower polls using the current processing block (not latest), since it indexes every block. This ensures consistent state and prevents issues with block reorgs.

Posting to OrderBook API

When a programmatic order’s conditions are met, the watch tower:
  1. Extracts the discrete order parameters from the PollResult
  2. Signs the order (if required by the order type)
  3. Posts to the CoW Protocol OrderBook API using the SDK
  4. Records the submitted order UID in the registry
  5. Updates metrics
The OrderBook API is initialized per chain:

Block processing flow

The watch tower processes blocks in two phases:

Phase 1: Warm-up (sync)

When starting, the watch tower syncs from the last processed block to the current chain tip:
Key features:
  • Paging - Fetches blocks in chunks (default 5000) to avoid RPC limits
  • Ordered processing - Processes blocks sequentially to maintain state consistency
  • Resume capability - Starts from last processed block on restart
The pageSize option defaults to 5000 blocks (Infura’s limit). If using your own RPC node, set pageSize: 0 to fetch all blocks in one request for faster syncing.

Phase 2: Real-time monitoring

Once synced, the watch tower subscribes to new blocks:
Reorg handling: The watch tower detects blockchain reorganizations by comparing block hashes. When a reorg is detected:
  1. Increments the reorg counter metric
  2. Records the reorg depth
  3. Re-processes the reorganized block(s)
  4. Updates the registry with the canonical chain state

Block processing pipeline

For each block, the watch tower executes:
  1. Process new order events - Index any new programmatic orders
  2. Poll existing orders - Check all registered orders for execution conditions
  3. Submit discrete orders - Post eligible orders to OrderBook API
  4. Persist state - Atomically write registry and last processed block
The processEveryNumBlocks option allows you to reduce RPC calls by only polling orders every N blocks. Default is 1 (poll every block).

Multi-chain support

The watch tower can monitor multiple chains simultaneously using parallel chain contexts:
Each chain context maintains:
  • Independent provider connection (HTTP or WebSocket)
  • Separate registry namespace in LevelDB
  • Dedicated OrderBook API instance
  • Isolated metrics by chain ID

Monitoring and observability

The watch tower exposes comprehensive monitoring capabilities:

API endpoints

By default on port 8080:
  • GET / - Root endpoint (returns “Moooo!”)
  • GET /api/version - Version and build information
  • GET /config - Current configuration
  • GET /api/dump/:chainId - Dump registry for a chain
  • GET /health - Health check for all chains
  • GET /metrics - Prometheus metrics

Prometheus metrics

Key metrics exposed:

Logging

Structured logging with configurable levels via LOG_LEVEL environment variable:

Health checks

The watch tower maintains health status for each chain:

Watchdog

A watchdog thread monitors for stalled chains:
If running in Kubernetes, the watch tower sets sync status to UNKNOWN instead of exiting, allowing the pod to remain running for debugging. Outside Kubernetes, it exits immediately to trigger a restart.

Error handling and resilience

Atomic operations

All state changes are atomic - if any operation fails during block processing:
  1. The error is logged and metrics updated
  2. Database writes are aborted (batch not committed)
  3. Watch tower exits with error code
  4. On restart, re-processes from last successful block

Retry logic

OrderBook API calls include exponential backoff:

Filter policies

Optional filter policies allow dropping problematic orders before indexing:

Performance considerations

RPC optimization

  • Paging - Configurable pageSize to batch historical event queries
  • Block skipping - processEveryNumBlocks option to reduce polling frequency
  • Address filtering - Optional owners config to only monitor specific addresses
  • Connection type - WebSocket providers reduce latency vs HTTP polling

Storage optimization

  • Expired orders - Automatically removed from registry to conserve space
  • Cancelled orders - Detected and removed during processing
  • JSON encoding - Uses custom serializers for Map/Set types

Concurrency

  • Multiple chains processed in parallel
  • Events within a block processed sequentially for consistency
  • Metrics and logging are thread-safe
For production deployments, use a WebSocket RPC connection for lower latency and reduced overhead compared to HTTP polling.
Last modified on March 12, 2026