https://github.com/cowprotocol/watch-tower
Conducting the music of Composable CoWs 🎶🐮
https://github.com/cowprotocol/watch-tower
cow-protocol cowswap erc1271 erc20 ethersjs
Last synced: 9 months ago
JSON representation
Conducting the music of Composable CoWs 🎶🐮
- Host: GitHub
- URL: https://github.com/cowprotocol/watch-tower
- Owner: cowprotocol
- License: gpl-3.0
- Created: 2023-08-21T12:50:34.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2025-04-11T05:40:25.000Z (9 months ago)
- Last Synced: 2025-04-12T23:13:52.967Z (9 months ago)
- Topics: cow-protocol, cowswap, erc1271, erc20, ethersjs
- Language: TypeScript
- Homepage:
- Size: 1.16 MB
- Stars: 7
- Watchers: 4
- Forks: 1
- Open Issues: 19
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Watch-Tower for Programmatic Orders 🐄🤖
## Overview
The [programmatic order](https://docs.cow.fi/cow-protocol/concepts/order-types/programmatic-orders) framework requires a watch-tower to monitor the blockchain for new orders, and to post them to the [CoW Protocol `OrderBook` API](https://docs.cow.fi/cow-protocol/reference/apis/orderbook). 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.
## Deployment
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 `OrderBook` API](https://docs.cow.fi/cow-protocol/reference/apis/orderbook).
**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](https://github.com/cowprotocol/composable-cow#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.
### Docker
The preferred method of deployment is using `docker`. The watch-tower is available as a docker image on [GitHub](https://github.com/cowprotocol/watch-tower/pkgs/container/watch-tower). 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 the `main` branch.
- `pr-` - the latest version of the watch-tower on the PR.
As an example, to run the latest version of the watch-tower via `docker`:
```bash
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.
### DAppNode
For [DAppNode](https://dappnode.com), the watch-tower is available as a package. This package is held in a [separate repository](https://github.com/cowprotocol/dappnodepackage-cow-watch-tower).
### Running locally
#### Requirements
- `node` (`>= v16.18.0`)
- `yarn`
#### CLI
```bash
# Install dependencies
yarn
# Run watch-tower
yarn cli run --config-path ./config.json
```
## Architecture
### Events
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:
1. Fetch the conditional order(s) from the blockchain.
2. Post the **discrete** order(s) to the [CoW Protocol `OrderBook` API](https://docs.cow.fi/cow-protocol/reference/apis/orderbook).
### Storage (registry)
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.
#### Database
The chosen architecture for the storage is a NoSQL (key-value) store. The watch-tower uses the following:
- [`level`](https://www.npmjs.com/package/level)
- Default location: `$PWD/database`
`LevelDB` is chosen it it provides [ACID](https://en.wikipedia.org/wiki/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.
##### Schema
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.
### Logging
To control logging level, you can set the `LOG_LEVEL` environment variable with one of the following values: `TRACE`, `DEBUG`, `INFO`, `WARN`, `ERROR`:
```ini
LOG_LEVEL=WARN
```
Additionally, you can enable module specific logging by specifying the log level for the module name:
```ini
# 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
```ini
LOG_LEVEL=chainContext=INFO,_placeOrder=TRACE
```
The module definition is actually a regex pattern, so you can make more complex definitions:
```ini
# 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:
```ini
LOG_LEVEL="WARN,commands=DEBUG,^checkForAndPlaceOrder=WARN,^chainContext=INFO,_checkForAndPlaceOrder:1:=INFO" yarn cli
```
### API Server
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 ` CLI option.
The server exposes automatically:
- An API, with:
- Version info: [http://localhost:8080/api/version](http://localhost:8080/api/version)
- Dump Database: `http://localhost:8080/api/dump/:chainId` e.g. [http://localhost:8080/api/dump/1](http://localhost:8080/api/dump/1)
- Prometheus Metrics: [http://localhost:8080/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.
## Developers
### Requirements
- `node` (`>= v16.18.0`)
- `yarn`
- `npm`
### Local development
It is recommended to test against the Goerli testnet. To run the watch-tower:
```bash
# Install dependencies
yarn
# Run watch-tower
yarn cli run --config-path ./config.json
```
### Testing
To run the tests:
```bash
yarn test
```
### Linting / Formatting
```bash
# To lint the code
yarn lint
```
```bash
# To fix linting errors
yarn lint:fix
```
```bash
# To format the code
yarn fmt
```
### Building the docker image
To build the docker image:
```bash
docker build -t watch-tower .
```