Skip to content

devblac/watch-tower

Repository files navigation

watch-tower

CI Go Report Card Codecov

A simple CLI tool for monitoring EVM and Algorand chains. Set up rules in YAML, get alerts when things happen. No SaaS, no complexity—just a single binary that does what you need.

What it does

Watch-tower monitors blockchain events and sends alerts when your rules match. It handles reorgs safely, deduplicates alerts, and works great in CI. Think of it as a lightweight alternative to running your own monitoring infrastructure.

Key features:

  • Reorg-safe: Uses confirmations and stores block hashes to detect and handle chain reorganizations
  • Exactly-once alerts: SQLite ledger ensures you don't get duplicate notifications
  • CI-friendly: --dry-run, --once, and --from/--to flags make it perfect for testing
  • Cross-chain: Works with EVM chains (Ethereum, Polygon, etc.) and Algorand
  • Simple config: YAML files with environment variable interpolation—no code required

Quick start

Installation

go install github.com/devblac/watch-tower/cmd/watch-tower@latest

Or download a pre-built binary from the releases page.

Your first alert (5 minutes)

  1. Create a config file (config.yaml):
version: 1
global:
  db_path: "./watch_tower.db"
  confirmations:
    evm: 12
sources:
  - id: mainnet
    type: evm
    rpc_url: ${EVM_RPC_URL}
    start_block: "latest-1000"
rules:
  - id: large_transfer
    source: mainnet
    match:
      type: log
      contract: "0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"  # USDC
      event: "Transfer(address,address,uint256)"
      where:
        - "value >= 1_000_000 * 1e6"  # 1M USDC
    sinks: ["slack"]
    dedupe:
      key: "txhash:logIndex"
      ttl: "24h"
sinks:
  - id: slack
    type: slack
    webhook_url: ${SLACK_WEBHOOK_URL}
    template: "🚨 Large USDC transfer: {{txhash}}"
  1. Set your environment variables:
export EVM_RPC_URL="https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY"
export SLACK_WEBHOOK_URL="https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
  1. Validate and run:
watch-tower validate -c config.yaml
watch-tower run -c config.yaml --once

That's it. If there's a large USDC transfer in the last 1000 blocks, you'll get a Slack notification.

Configuration

Sources

Sources define which chains to monitor. You can have multiple sources (e.g., mainnet and testnet).

EVM source:

sources:
  - id: mainnet
    type: evm
    rpc_url: ${EVM_RPC_URL}
    start_block: "latest-5000"  # or "12345678" for a specific block
    abi_dirs: ["./abis"]  # optional: directory with ABI JSON files

Algorand source:

sources:
  - id: algo_mainnet
    type: algorand
    algod_url: ${ALGOD_URL}
    indexer_url: ${ALGO_INDEXER_URL}
    start_round: "latest-10000"

Rules

Rules define what to watch for and what to do when it happens.

Match types:

  • log: EVM event logs (requires contract and event)
  • app_call: Algorand application calls (requires app_id)
  • asset_transfer: Algorand ASA transfers

Predicates: Simple expressions to filter events:

  • value >= 1000
  • sender == "0x123..."
  • sender in addr1,addr2,addr3
  • memo contains "alert"
  • value >= wei(1e18) # helper for wei amounts
  • amount >= microAlgos(1e6) # helper for Algorand amounts

Example rule:

rules:
  - id: whale_alert
    source: mainnet
    match:
      type: log
      contract: "0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
      event: "Transfer(address,address,uint256)"
      where:
        - "value >= 1_000_000 * 1e6"
        - "to != 0x0000000000000000000000000000000000000000"  # exclude burns
    sinks: ["slack", "webhook"]
    dedupe:
      key: "txhash:logIndex"
      ttl: "24h"
    rate_limit:  # optional: limit alerts per rule
      capacity: 10
      rate: 1  # 1 alert per second

Sinks

Sinks are where alerts go. You can send to multiple sinks per rule.

Slack:

sinks:
  - id: slack
    type: slack
    webhook_url: ${SLACK_WEBHOOK_URL}
    template: "Alert: {{rule_id}} - {{txhash}}"

Microsoft Teams:

sinks:
  - id: teams
    type: teams
    webhook_url: ${TEAMS_WEBHOOK_URL}
    template: "{{pretty_json}}"

Generic webhook:

sinks:
  - id: webhook
    type: webhook
    url: ${WEBHOOK_URL}
    method: POST
    template: "{{. | toJson}}"  # full event as JSON

Template variables:

  • {{rule_id}} - Rule identifier
  • {{chain}} - Chain name (evm/algorand)
  • {{txhash}} - Transaction hash
  • {{height}} - Block height/round
  • {{pretty_json}} - Formatted event data
  • {{short_addr addr}} - Shortened address
  • Any field from the event args

Reorgs and confirmations

Watch-tower handles chain reorganizations automatically. Here's how it works:

  1. Confirmations: You set how many confirmations to wait (e.g., 12 blocks for EVM). Watch-tower only processes blocks that are this many confirmations behind the chain tip.

  2. Hash verification: Each block's parent hash is checked. If it doesn't match what we expect, a reorg is detected.

  3. Rewind and replay: When a reorg is detected, watch-tower rewinds the cursor and reprocesses blocks. Your alerts stay accurate.

The confirmation count is per-chain in the global.confirmations section. More confirmations = more safety but more lag.

Replay and dry-run

Replay historical blocks:

watch-tower run -c config.yaml --from 18000000 --to 18001000

Dry-run (no alerts sent):

watch-tower run -c config.yaml --dry-run --once

Check current state:

watch-tower state

Export data:

watch-tower export alerts --format json
watch-tower export cursors --format csv

Health and metrics

Health endpoint:

watch-tower run -c config.yaml --health :8080
# Check: curl http://localhost:8080/healthz

Prometheus metrics:

watch-tower run -c config.yaml --metrics :9090
# Scrape: curl http://localhost:9090/metrics

Metrics include:

  • watch_tower_blocks_processed_total
  • watch_tower_alerts_sent_total
  • watch_tower_alerts_dropped_total (dedupe/rate-limit)
  • watch_tower_errors_total

Examples

See the examples/ directory for complete working configurations:

  • examples/evm_usdc_whale/ - Monitor large USDC transfers on Ethereum
  • examples/algo_app_watch/ - Watch Algorand application calls

Each example includes a config file, .env.example, and a README with setup instructions.

CI integration

Watch-tower is designed to work well in CI pipelines:

# .github/workflows/monitor.yml
- name: Check for alerts
  run: |
    watch-tower validate -c config.yaml
    watch-tower run -c config.yaml --once --dry-run

The --dry-run flag processes events but doesn't send alerts, perfect for validating rules in CI.

Development

make lint    # Run linters
make test    # Run tests
make build   # Build binary

How it works

  1. Scan blocks: Watch-tower polls each source for new blocks, respecting confirmation counts
  2. Match events: Events are matched against your rules using ABI decoding (EVM) or app call parsing (Algorand)
  3. Evaluate predicates: Simple expressions filter events (e.g., value > 1000)
  4. Deduplicate: SQLite tracks what's been seen to prevent duplicate alerts
  5. Rate limit: Optional per-rule rate limiting prevents alert spam
  6. Send alerts: Templates are rendered and sent to configured sinks

All state is stored in a local SQLite database (default: ./watch_tower.db). This makes it easy to run multiple instances or move between machines.

Security

  • Secrets: All secrets must come from environment variables. The config validator will reject plain secrets in YAML files.
  • Logging: Secrets are automatically redacted from logs (keys containing "token", "secret", "key", "password").
  • HTTPS: Webhook sinks require HTTPS URLs.

See SECURITY.md for reporting vulnerabilities.

Limitations

This is v0.1.0, so keep these in mind:

  • Predicates are simple: No complex joins, arithmetic, or time windows yet
  • Single binary: No plugins or extensions
  • SQLite only: No Postgres option yet
  • Basic sinks: Slack, Teams, and generic webhooks only

Check the roadmap in tasks.md for what's coming next.

Contributing

Contributions welcome! See CONTRIBUTING.md for guidelines.

License

MIT — see LICENSE.

About

A cross-chain monitoring & alerting cli-tool.

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

No packages published

Languages