The programmatic order framework requires a watch-tower to monitor the blockchain for new orders, and to post them to the CoW Protocol OrderBook API. The watch-tower is a standalone application that can be run locally as a script for development, or deployed as a docker container to a server, or dappnode.
If running your own watch-tower instance, you will need the following:
- An RPC node connected to the Ethereum mainnet, Arbitrum One, Gnosis Chain, Base, or Sepolia.
- Internet access to the CoW Protocol
OrderBookAPI.
CAUTION: Conditional order types may consume considerable RPC calls.
NOTE: deployment-block refers to the block number at which the ComposableCoW contract was deployed to the respective chain. This is used to optimise the watch-tower by only fetching events from the blockchain after this block number. Refer to Deployed Contracts for the respective chains.
NOTE: The pageSize option is used to specify the number of blocks to fetch from the blockchain when querying historical events (eth_getLogs). The default is 5000, which is the maximum number of blocks that can be fetched in a single request from Infura. If you are running the watch-tower against your own RPC, you may want to set this to 0 to fetch all blocks in one request, as opposed to paging requests.
The preferred method of deployment is using docker. The watch-tower is available as a docker image on GitHub. The tags available are:
latest- the latest version of the watch-tower.vX.Y.Z- the version of the watch-tower.main- the latest version of the watch-tower on themainbranch.pr-<PR_NUMBER>- the latest version of the watch-tower on the PR.
As an example, to run the latest version of the watch-tower via docker:
docker run --rm -it \
-v "$(pwd)/config.json.example:/config.json" \
ghcr.io/cowprotocol/watch-tower:latest \
run \
--config-path /config.jsonNOTE: See the example config.json.example for an example configuration file.
For DAppNode, the watch-tower is available as a package. This package is held in a separate repository.
node(>= v16.18.0)yarn
# Install dependencies
yarn
# Run watch-tower
yarn cli run --config-path ./config.jsonThe watch-tower monitors the following events:
ConditionalOrderCreated- emitted when a single new conditional order is created.MerkleRootSet- emitted when a new merkle root (ie.nconditional orders) is set for a safe.
When a new event is discovered, the watch-tower will:
- Fetch the conditional order(s) from the blockchain.
- Post the discrete order(s) to the CoW Protocol
OrderBookAPI.
The watch-tower stores the following state:
- All owners (ie. safes) that have created at least one conditional order.
- All conditional orders by safe that have not expired or been cancelled.
As orders expire, or are cancelled, they are removed from the registry to conserve storage space.
The chosen architecture for the storage is a NoSQL (key-value) store. The watch-tower uses the following:
level- Default location:
$PWD/database
LevelDB is chosen it it provides ACID guarantees, and is a simple key-value store. The watch-tower uses the level package to provide a simple interface to the database. All writes are batched, and if a write fails, the watch-tower will throw an error and exit. On restarting, the watch-tower will attempt to re-process from the last block that was successfully indexed, resulting in the database becoming eventually consistent with the blockchain.
The following keys are used:
LAST_PROCESSED_BLOCK- the last block (number, timestamp, and hash) that was processed by the watch-tower.CONDITIONAL_ORDER_REGISTRY- the registry of conditional orders by safe.CONDITIONAL_ORDER_REGISTRY_VERSION- the version of the registry. This is used to migrate the registry when the schema changes.LAST_NOTIFIED_ERROR- the last time an error was notified via Slack. This is used to prevent spamming the slack channel.
To control logging level, you can set the LOG_LEVEL environment variable with one of the following values: TRACE, DEBUG, INFO, WARN, ERROR:
LOG_LEVEL=WARNAdditionally, you can enable module specific logging by specifying the log level for the module name:
# Enable logging for an specific module (chainContext in this case)
LOG_LEVEL=chainContext=INFO
# Of-course, you can provide the root log level, and the override at the same time
# - All loggers will have WARN level
# - Except the "chainContext" which will have INFO level
LOG_LEVEL=WARN,chainContext=INFOYou can specify more than one overrides
LOG_LEVEL=chainContext=INFO,_placeOrder=TRACEThe module definition is actually a regex pattern, so you can make more complex definitions:
# Match a logger using a pattern
# Matches: chainContext:processBlock:100:30212964
# Matches: chainContext:processBlock:1:30212964
# Matches: chainContext:processBlock:5:30212964
LOG_LEVEL=chainContext:processBlock:(\d{1,3}):(\d*)$=DEBUG
# Another example
# Matches: chainContext:processBlock:100:30212964
# Matches: chainContext:processBlock:1:30212964
# But not: chainContext:processBlock:5:30212964
LOG_LEVEL=chainContext:processBlock:(100|1):(\d*)$=DEBUGCombine all of the above to control the log level of any modules:
LOG_LEVEL="WARN,commands=DEBUG,^checkForAndPlaceOrder=WARN,^chainContext=INFO,_checkForAndPlaceOrder:1:=INFO" yarn cliCommands that run the watch-tower in a watching mode, will also start an API server. By default the API server will start on port 8080. You can change the port using the --api-port <apiPort> CLI option.
The server exposes automatically:
- An API, with:
- Version info: http://localhost:8080/api/version
- Config: http://localhost:8080/api/config
- Dump Database:
http://localhost:8080/api/dump/:chainIde.g. http://localhost:8080/api/dump/1
- Prometheus Metrics: http://localhost:8080/metrics
You can prevent the API server from starting by setting the --disable-api flag for the run command.
The /api/version endpoint, exposes the information in the package.json. This can be helpful to identify the version of the watch-tower. Additionally, for environments using docker, the environment variable DOCKER_IMAGE_TAG can be used to specify the Docker image tag used.
node(>= v16.18.0)yarnnpm
It is recommended to test against the Goerli testnet. To run the watch-tower:
# Install dependencies
yarn
# Run watch-tower
yarn cli run --config-path ./config.jsonTo run the tests:
yarn test# To lint the code
yarn lint# To fix linting errors
yarn lint:fix# To format the code
yarn fmtTo build the docker image:
docker build -t watch-tower .