OpenZeppelin Monitor

This software is in alpha stage. Use in production environments at your own risk.

In the rapidly evolving world of blockchain technology, effective monitoring is crucial for ensuring security and performance. OpenZeppelin Monitor is a blockchain monitoring service that watches for specific on-chain activities and triggers notifications based on configurable conditions. The service offers multi-chain support with configurable monitoring schedules, flexible trigger conditions, and an extensible architecture for adding new chains.

Features:

  • Multi-chain support

  • Configurable monitoring schedules

  • Flexible trigger conditions

  • Extensible architecture for adding new chains

Supported Networks (OOTB):

  • EVM

  • Stellar

Supported Triggers (OOTB):

  • Slack notifications

  • Email notifications

  • Discord notifications

  • Telegram notifications

  • Webhook notifications

  • Script notifications

To get started immediately, see Quickstart.

Technical Overview

%%{init: { 'theme': 'base', 'themeVariables': { 'background': '#ffffff', 'mainBkg': '#ffffff', 'primaryBorderColor': '#cccccc' } }}%% graph TD subgraph Blockchain Networks ETH[Ethereum RPC] POL[Polygon RPC] BSC[BSC RPC] end subgraph Block Processing BW[BlockWatcherService] BS[(BlockStorage)] JS[JobScheduler] end subgraph Client Layer BC[BlockchainClient] EVM[EVMClient] STL[StellarClient] end subgraph Processing Pipeline FS[FilterService] TS[TriggerService] NS[NotificationService] end subgraph Notifications Slack Email Discord Telegram Webhook Script end %% Block Processing Flow JS -->|Schedule Block Fetch| BW BW -->|Store Last Block| BS BW -->|Read Last Block| BS BW -->|Get New Blocks| BC %% Client Connections BC --> EVM BC --> STL EVM -->|RPC Calls| ETH EVM -->|RPC Calls| POL EVM -->|RPC Calls| BSC %% Processing Flow BW -->|New Block| FS FS -->|Matches| TS TS -->|Execute| NS NS --> Slack NS --> Email NS --> Discord NS --> Telegram NS --> Webhook NS --> Script style STL fill:#f0f0f0 classDef rpc fill:#e1f5fe,stroke:#01579b classDef storage fill:#fff3e0,stroke:#ef6c00 classDef service fill:#e8f5e9,stroke:#2e7d32 classDef notification fill:#f3e5f5,stroke:#7b1fa2 class ETH,POL,BSC rpc class BS storage class BW,FS,TS,NS service class Slack,Email,Discord,Telegram,Webhook,Script notification

Project Structure

The project follows a standard Rust project layout:

openzeppelin-monitor/
├── src/                # Source code
│   ├── bootstrap/      # Bootstrap functions for the application
│   ├── models/         # Data structures and types
│   ├── repositories/   # Configuration storage
│   ├── services/       # Core business logic
│   ├── utils/          # Helper functions
│
├── config/             # Configuration files
├── tests/              # Integration and property-based tests
├── data/               # Runtime data storage
├── docs/               # Documentation
├── scripts/            # Utility scripts
├── cmd/                # Metrics and monitoring
└── ... other root files (Cargo.toml, README.md, etc.)

For detailed information about each directory and its contents, see Project Structure Details.

Getting Started

Prerequisites

  • Rust 2021 edition

  • Docker (optional, for containerized deployment)

Installation

Option 1: Install Locally

  1. Clone the repository:

    git clone https://github.com/openzeppelin/openzeppelin-monitor
    cd openzeppelin-monitor
  2. Install dependencies:

    cargo build

Run the CLI:

  1. View available options:

    ./openzeppelin-monitor --help
  2. Use --log-file to enable file-based logging.

  3. Use --metrics to enable the metrics server.

Option 2: Run with Docker

Basic Setup

  1. Start the services using the make target:

    cargo make docker-compose-up

By default, Docker Compose uses Dockerfile.development. To use production settings, set: DOCKERFILE=Dockerfile.production before running the command.

Metrics Configuration

The metrics server, Prometheus, and Grafana can be enabled by setting METRICS_ENABLED=true in your .env file.

You can start services directly with Docker Compose:

# without metrics profile ( METRICS_ENABLED=false by default )
docker compose up -d

# With metrics enabled
docker compose --profile metrics up -d

You can access the monitoring interfaces through your browser:

Grafana comes pre-configured with default dashboards and metrics visualizations.

Management Commands

  1. Verify container status:

    docker ps -a
  2. Stop services:

    cargo make docker-compose-down
    # or using docker compose directly:
    # without metrics profile
    docker compose down
    # or with metrics profile
    docker compose --profile metrics down
  3. View logs (stdout):

    docker compose logs -f

Configuration Guidelines

  • Network configurations: <network_type>_<network_name>.json

    • Example: ethereum_mainnet.json, stellar_testnet.json

    • Should match the slug property inside the file

  • Monitor configurations: <asset>_<action>_monitor.json

    • Example: usdc_transfer_monitor.json, dai_liquidation_monitor.json

    • Referenced by monitors using their name property

  • Trigger configurations: <type>_<purpose>.json

    • Example: slack_notifications.json, email_alerts.json

    • Individual triggers referenced by their configuration key

Configuration References

  • Monitor’s networks array must contain valid network slug values from network configuration files

  • Monitor’s triggers array must contain valid trigger configuration keys

  • Example valid references:

    // networks/ethereum_mainnet.json
    {
      "slug": "ethereum_mainnet",
      ...
    }
    
    // triggers/slack_notifications.json
    {
      "large_transfer_slack": {
        ...
      }
    }
    
    // monitors/usdc_transfer_monitor.json
    {
      "networks": ["ethereum_mainnet"],
      "triggers": ["large_transfer_slack"],
      ...
    }

Ensure all referenced slugs and trigger keys exist in their respective configuration files. The monitor will fail to start if it cannot resolve these references.

Basic Configuration

  • Set up environment variables:

Copy the example environment file and update values according to your needs

cp .env.example .env

This table lists the environment variables and their default values.

Environment Variable Default Value Accepted Values Description

RUST_LOG

info

info, debug, warn, error, trace

Log level.

LOG_MODE

stdout

stdout, file

Write logs either to console or to file.

LOG_DATA_DIR

logs/

<any file path>

Directory to write log files on host.

MONITOR_DATA_DIR

null

<any file path>

Persist monitor data between container restarts.

LOG_MAX_SIZE (in bytes)

1073741824

<any value in bytes>

Size after which logs needs to be rolled.

METRICS_ENABLED

false

true, false

Enable metrics server for external tools to scrape metrics.

METRICS_PORT

8081

<any tcp port (preferably choose non-privileged ports i.e. (1024-65535))>

Port to use for metrics server.

  • Copy and configure example files:

# EVM Configuration
cp config/monitors/evm_transfer_usdc.json.example config/monitors/evm_transfer_usdc.json
cp config/networks/ethereum_mainnet.json.example config/networks/ethereum_mainnet.json

# Stellar Configuration
cp config/monitors/stellar_transfer_usdc.json.example config/monitors/stellar_transfer_usdc.json
cp config/networks/stellar_mainnet.json.example config/networks/stellar_mainnet.json

# Notification Configuration
cp config/triggers/email_notifications.json.example config/triggers/email_notifications.json
cp config/triggers/slack_notifications.json.example config/triggers/slack_notifications.json
cp config/triggers/discord_notifications.json.example config/triggers/discord_notifications.json
cp config/triggers/telegram_notifications.json.example config/triggers/telegram_notifications.json
cp config/triggers/webhook_notifications.json.example config/triggers/webhook_notifications.json
cp config/triggers/script_notifications.json.example config/triggers/script_notifications.json

Data Storage Configuration

The monitor uses file-based storage by default, with an extensible interface (BlockStorage) for custom storage implementations.

File Storage

When store_blocks is enabled in the network configuration, the monitor stores:

  • Processed blocks: ./data/<network_slug>_blocks_<timestamp>.json

  • Missed blocks: ./data/<network_slug>_missed_blocks.txt (used to store missed blocks)

The content of the missed_blocks.txt file may help to determine the right max_past_blocks value based on the network’s block time and the monitor’s cron schedule.

Additionally, the monitor will always store:

  • Last processed block: ./data/<network_slug>_last_block.txt (enables resuming from last checkpoint)

Configuration Files

Network Configuration

A Network configuration defines connection details and operational parameters for a specific blockchain network, supporting both EVM and Stellar-based chains.

Example Network Configuration
{
  "network_type": "Stellar",
  "slug": "stellar_mainnet",
  "name": "Stellar Mainnet",
  "rpc_urls": [
    {
      "type_": "rpc",
      "url": "https://soroban.stellar.org",
      "weight": 100
    }
  ],
  "network_passphrase": "Public Global Stellar Network ; September 2015",
  "block_time_ms": 5000,
  "confirmation_blocks": 2,
  "cron_schedule": "0 */1 * * * *",
  "max_past_blocks": 20,
  "store_blocks": true
}

Available Fields

Field Type Description

network_type

String

Type of blockchain ("EVM" or "Stellar")

slug

String

Unique identifier for the network

name

String

Human-readable network name

rpc_urls

Array[Object]

List of RPC endpoints with weights for load balancing

chain_id

Number

Network chain ID (EVM only)

network_passphrase

String

Network identifier (Stellar only)

block_time_ms

Number

Average block time in milliseconds

confirmation_blocks

Number

Number of blocks to wait for confirmation

cron_schedule

String

Monitor scheduling in cron format

max_past_blocks

Number

Maximum number of past blocks to process

store_blocks

Boolean

Whether to store processed blocks (defaults output to ./data/ directory)

Important Considerations

  • We strongly recommend using private RPC providers for improved reliability.

Trigger Configuration

A Trigger defines actions to take when monitored conditions are met. Triggers can send notifications, make HTTP requests, or execute scripts.

Example Trigger Configuration
{
  "evm_large_transfer_usdc_slack": {
    "name": "Large Transfer Slack Notification",
    "trigger_type": "slack",
    "config": {
      "slack_url": "https://hooks.slack.com/services/A/B/C",
      "message": {
        "title": "large_transfer_slack triggered",
        "body": "Large transfer of ${event_0_value} USDC from ${event_0_from} to ${event_0_to} | https://etherscan.io/tx/${transaction_hash}#eventlog"
      }
    }
  },
  "stellar_large_transfer_usdc_slack": {
    "name": "Large Transfer Slack Notification",
    "trigger_type": "slack",
    "config": {
      "slack_url": "https://hooks.slack.com/services/A/B/C",
      "message": {
        "title": "large_transfer_usdc_slack triggered",
        "body": "${monitor_name} triggered because of a large transfer of ${function_0_2} USDC to ${function_0_1} | https://stellar.expert/explorer/testnet/tx/${transaction_hash}"
      }
    }
  }
}

Trigger Types

Slack Notifications
{
  "slack_url": "https://hooks.slack.com/...",
  "message": {
    "title": "Alert Title",
    "body": "Alert message for ${transaction_hash}"
  }
}
Slack Notification Fields
Field Type Description

name

String

Human-readable name for the notification

trigger_type

String

Must be "slack" for Slack notifications

config.slack_url

String

Slack webhook URL for sending notifications

config.message.title

String

Title that appears in the Slack message

config.message.body

String

Message template with variable substitution

Email Notifications
{
  "host": "smtp.gmail.com",
  "port": 465,
  "username": "[email protected]",
  "password": "smtp_password",
  "message": {
    "title": "Alert Subject",
    "body": "Alert message for ${transaction_hash}",
  },
  "sender": "[email protected]",
  "recipients": ["[email protected]"]
}
Email Notification Fields
Field Type Description

name

String

Human-readable name for the notification

trigger_type

String

Must be "email" for email notifications

config.host

String

SMTP server hostname

config.port

Number

SMTP port (defaults to 465)

config.username

String

SMTP authentication username

config.password

String

SMTP authentication password

config.message.title

String

Email subject line

config.message.body

String

Email body template with variable substitution

config.sender

String

Sender email address

config.recipients

Array[String]

List of recipient email addresses

Webhook Notifications
{
  "url": "https://webhook.site/123-456-789",
  "method": "POST",
  "secret": "some-secret",
  "headers": {
    "Content-Type": "application/json"
  },
  "message": {
    "title": "Alert Title",
    "body": "Alert message for ${transaction_hash}"
  }
}
Webhook Notification Fields
Field Type Description

name

String

Human-readable name for the notification

trigger_type

String

Must be "webhook" for webhook notifications

config.url

String

Webhook URL

config.method

String

HTTP method (POST, GET, etc.) defaults to POST

config.secret

String

Optional secret for HMAC authentication

config.headers

Object

Headers to include in the webhook request

config.message.title

String

Title that appears in the webhook message

config.message.body

String

Message template with variable substitution

Discord Notifications
{
  "discord_url": "https://discord.com/api/webhooks/123-456-789",
  "message": {
    "title": "Alert Title",
    "body": "Alert message for ${transaction_hash}"
  }
}
Discord Notification Fields
Field Type Description

name

String

Human-readable name for the notification

trigger_type

String

Must be "discord" for Discord notifications

config.discord_url

String

Discord webhook URL must start with https://discord.com/api/webhooks/

config.message.title

String

Title that appears in the Discord message

config.message.body

String

Message template with variable substitution

Telegram Notifications
{
  "token": "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZ",
  "chat_id": "9876543210",
  "message": {
    "title": "Alert Title",
    "body": "Alert message for ${transaction_hash}"
  }
}
Telegram Notification Fields
Field Type Description

name

String

Human-readable name for the notification

trigger_type

String

Must be "telegram" for Telegram notifications

config.token

String

Telegram bot token

config.chat_id

String

Telegram chat ID

config.disable_web_preview

Boolean

Whether to disable web preview in Telegram messages (defaults to false)

config.message.title

String

Title that appears in the Telegram message

config.message.body

String

Message template with variable substitution

Custom Script Notifications
{
  "language": "Bash",
  "script_path": "./config/triggers/scripts/custom_notification.sh",
  "arguments": ["--verbose"],
  "timeout_ms": 1000
}
Script Notification Fields
Field Type Description

name

String

Human-readable name for the notification

trigger_type

String

Must be "script" for Custom Script notifications

language

String

The language of the script

script_path

String

The path to the script

arguments

Array[String]

The arguments of the script (optional).

timeout_ms

Number

The timeout of the script is important to avoid infinite loops during the execution. If the script takes longer than the timeout, it will be killed.

For more information about custom scripts, see Custom Scripts Section.

Security Risk: Only run scripts that you trust and fully understand. Malicious scripts can harm your system or expose sensitive data. Always review script contents and verify their source before execution.

Available Template Variables

Common Variables
Variable Description

monitor_name

Name of the triggered monitor

transaction_hash

Hash of the transaction

function_[index]_signature

Function signature

event_[index]_signature

Event signature

Network-Specific Variables
EVM Variables
Variable Description

transaction_from

Sender address

transaction_to

Recipient address

transaction_value

Transaction value

event_[index]_[param]

Event parameters by name

function_[index]_[param]

Function parameters by name

Stellar Variables
Variable Description

event_[index]_[position]

Event parameters by position

function_[index]_[position]

Function parameters by position

Transaction-related variables (transaction_from, transaction_to, transaction_value) are not available for Stellar networks.

Important Considerations:

  • Email notification port defaults to 465 if not specified.

  • Template variables are context-dependent:

    • Event-triggered notifications only populate event variables.

    • Function-triggered notifications only populate function variables.

    • Mixing contexts results in empty values.

  • Credentials in configuration files should be properly secured.

  • Consider using environment variables for sensitive information.

Monitor Configuration

A Monitor defines what blockchain activity to watch and what actions to take when conditions are met. Each monitor combines:

  • Network targets (which chains to monitor)

  • Contract addresses to watch

  • Conditions to match (functions, events, transactions)

  • Trigger conditions (custom scripts that act as filters for each monitor match to determine whether a trigger should be activated).

  • Triggers to execute when conditions are met

Example Monitor Configuration
{
  "name": "Large USDC Transfers",
  "networks": ["ethereum_mainnet"],
  "paused": false,
  "addresses": [
    {
      "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
      "abi": [ ... ]
    }
  ],
  "match_conditions": {
    "functions": [
      {
        "signature": "transfer(address,uint256)",
        "expression": "value > 1000000"
      }
    ],
    "events": [
      {
        "signature": "Transfer(address,address,uint256)",
        "expression": "amount > 1000000"
      }
    ],
    "transactions": [
      {
        "status": "Success",
        "expression": "value > 1500000000000000000"
      }
    ]
  },
  "trigger_conditions": [
    {
      "script_path": "./config/filters/evm_filter_block_number.sh",
      "language": "bash",
      "arguments": "--verbose",
      "timeout_ms": 1000
    }
  ],
  "triggers": ["evm_large_transfer_usdc_slack", "evm_large_transfer_usdc_email"]
}

Match Conditions

Monitors support three types of match conditions that can be combined:

Function Conditions

Match specific function calls to monitored contracts:

{
  "functions": [
    {
      "signature": "transfer(address,uint256)",
      "expression": "value > 1000"
    }
  ]
}
Event Conditions

Match events emitted by monitored contracts:

{
  "events": [
    {
      "signature": "Transfer(address,address,uint256)",
      "expression": "amount > 1000000"
    }
  ]
}
Transaction Conditions

Match transaction properties:

{
  "transactions": [
    {
      "status": "Success",
      "expression": "value > 1500000000000000000"
    }
  ]
}

Available Fields

Field Type Description

name

String

Unique identifier for this monitor

networks

Array[String]

List of network slugs this monitor should watch

paused

Boolean

Whether this monitor is currently paused

addresses

Array[Object]

Contract addresses to monitor with optional ABIs

match_conditions

Object

Collection of conditions that can trigger the monitor

trigger_conditions

Array[Object]

Collection of filters to apply to monitor matches before executing triggers

triggers

Array[String]

IDs of triggers to execute when conditions match

Matching Rules

  • If no conditions are specified, all transactions match

  • For multiple condition types:

    • Transaction conditions are checked first

    • Then either function OR event conditions must match

    • Both transaction AND (function OR event) must match if both specified

Expressions

Network Type Access Method

Stellar

Arguments accessed by numeric index (e.g., [0, 1, 2])

EVM

Arguments accessed by parameter names from ABI

Examples

For EVM event Transfer(address from, address to, uint256 value):

{
  "expression": "value > 10000000000"
}

For Stellar function transfer(Address,Address,I128):

{
  "expression": "2 > 1000"
}

Trigger Conditions (Custom filters)

Custom filters allow you to create sophisticated filtering logic for processing monitor matches. These filters act as additional validation layers that determine whether a match should trigger the execution of a trigger or not.

For more information about custom scripts, see Custom Scripts Section.

Security Risk: Only run scripts that you trust and fully understand. Malicious scripts can harm your system or expose sensitive data. Always review script contents and verify their source before execution.

Example Trigger Conditions Configuration
{
  "script_path": "./config/filters/evm_filter_block_number.sh",
  "language": "Bash",
  "arguments": ["--verbose"],
  "timeout_ms": 1000
}

Available Fields

Trigger Conditions Fields
Field Type Description

script_path

String

The path to the script

language

String

The language of the script

arguments

Array[String]

The arguments of the script (optional).

timeout_ms

Number

The timeout of the script is important to avoid infinite loops during the execution. If the script takes longer than the timeout, it will be killed and the match will be included by default.

Important Considerations:

  • Network slugs in the monitor must match valid network configurations.

  • Trigger IDs must match configured triggers.

  • Expression syntax and available variables differ between EVM and Stellar networks.

  • ABIs are only relevant for EVM networks.

  • The monitoring frequency is controlled by the network’s cron_schedule.

  • Each monitor can watch multiple networks and addresses simultaneously.

  • Monitors can be paused without removing their configuration.

Running the Monitor

Local Execution

cargo run

Docker Deployment

The monitor can be run as either a development or production container using the corresponding Dockerfile (Dockerfile.development or Dockerfile.production).

Environment Configuration

  • Copy .env.example to .env and change values according to your needs.

Build Image and Run Containers

You can build and run containers using single command:

cargo make docker-compose-up

By default, Docker Compose uses Dockerfile.development. To use production settings, set: DOCKERFILE=Dockerfile.production before running the command.

Metrics Configuration

The metrics server, Prometheus, and Grafana can be enabled by setting METRICS_ENABLED=true in your .env file.

You can start services directly with Docker Compose:

# without metrics profile ( METRICS_ENABLED=false by default )
docker compose up -d

# With metrics enabled
docker compose --profile metrics up -d

Management Commands

  1. Verify container status:

    docker ps -a
  2. Stop services:

    cargo make docker-compose-down
    # or using docker compose directly:
    # without metrics profile
    docker compose down
    # or with metrics profile
    docker compose --profile metrics down

The build process will include:

  • The appropriate .env file

  • Configurations from the ./config folder

To modify configurations without rebuilding:

  • Restart the container

docker compose restart monitor

Data Persistence (Optional)

  • Set LOG_MODE as file will persist the log data in logs/ on host. To change it to a different directory use LOG_DATA_DIR.

  • Set MONITOR_DATA_DIR to specific dir on your host system which will persist data between container restarts.

Error Handling

The monitor implements a comprehensive error handling system with rich context and tracing capabilities. For detailed information about error handling, see Error Handling Guide.

Important Considerations:

Performance Considerations:

  • Monitor performance depends on network congestion and RPC endpoint reliability.

  • The max_past_blocks configuration is critical:

    • Calculate as: (cron_interval_ms/block_time_ms) + confirmation_blocks + 1 (defaults to this calculation if not specified).

    • Example for 1-minute Ethereum cron: (60000/12000) + 12 + 1 = 18 blocks.

    • Too low settings may result in missed blocks.

  • Trigger conditions are executed sequentially based on their position in the trigger conditions array. Proper execution also depends on the number of available file descriptors on your system. To ensure optimal performance, it is recommended to increase the limit for open file descriptors to at least 2048 or higher. On Unix-based systems you can check the current limit by running ulimit -n and temporarily increase it with ulimit -n 2048.

  • Since scripts are loaded at startup, any modifications to script files require restarting the monitor to take effect.

  • See performance considerations about custom scripts here.

Notification Considerations

  • Template variables are context-dependent:

    • Event-triggered notifications only populate event variables.

    • Function-triggered notifications only populate function variables.

    • Mixing contexts results in empty values.

  • Custom script notifications have additional considerations:

    • Scripts receive monitor match data and arguments as JSON input

    • Scripts must complete within their configured timeout_ms or they will be terminated

    • Script modifications require monitor restart to take effect

    • Supported languages are limited to Python, JavaScript, and Bash

Testing

Running Tests

RUST_TEST_THREADS=1 cargo test
RUST_TEST_THREADS=1 cargo test properties
RUST_TEST_THREADS=1 cargo test integration

Coverage Reports

Generate an HTML coverage report:

RUST_TEST_THREADS=1 cargo +stable llvm-cov --html --open

Generate a coverage report in the terminal:

RUST_TEST_THREADS=1 cargo +stable llvm-cov

Support

For support or inquiries, contact us on Telegram.

License

This project is licensed under the GNU Affero General Public License v3.0 - see the LICENSE file for details.

Security

For security concerns, please refer to our Security Policy.