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, or Sepolia.
- Internet access to the CoW Protocol
OrderBook
API.
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 themain
branch.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.json
NOTE: 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.json
The watch-tower monitors the following events:
ConditionalOrderCreated
- emitted when a single new conditional order is created.MerkleRootSet
- emitted when a new merkle root (ie.n
conditional 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
OrderBook
API.
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=WARN
Additionally, 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=INFO
You can specify more than one overrides
LOG_LEVEL=chainContext=INFO,_placeOrder=TRACE
The 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*)$=DEBUG
Combine 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 cli
Commands 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
- Dump Database:
http://localhost:8080/api/dump/:chainId
e.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
)yarn
npm
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.json
To run the tests:
yarn test
# To lint the code
yarn lint
# To fix linting errors
yarn lint:fix
# To format the code
yarn fmt
To build the docker image:
docker build -t watch-tower .