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
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.
Installation
Option 1: Install Locally
-
Clone the repository:
git clone https://github.com/openzeppelin/openzeppelin-monitor cd openzeppelin-monitor
-
Install dependencies:
cargo build
Option 2: Run with Docker
Basic Setup
-
Start the services using the make target:
cargo make docker-compose-up
By default, Docker Compose uses |
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:
-
Prometheus UI:
http://localhost:9090
-
Grafana Dashboard:
http://localhost:3000
Grafana comes pre-configured with default dashboards and metrics visualizations.
Configuration Guidelines
Recommended File Naming Conventions
-
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 networkslug
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 |
---|---|---|---|
|
|
|
Log level. |
|
|
|
Write logs either to console or to file. |
|
|
|
Directory to write log files on host. |
|
|
|
Persist monitor data between container restarts. |
|
|
|
Size after which logs needs to be rolled. |
|
|
|
Enable metrics server for external tools to scrape metrics. |
|
|
|
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.
{
"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 |
Trigger Configuration
A Trigger defines actions to take when monitored conditions are met. Triggers can send notifications, make HTTP requests, or execute scripts.
{
"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 |
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
{
"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"
}
]
}
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 |
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. |
{
"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
Docker Deployment
The monitor can be run as either a development or production container using the corresponding Dockerfile (Dockerfile.development
or Dockerfile.production
).
Build Image and Run Containers
You can build and run containers using single command:
cargo make docker-compose-up
By default, Docker Compose uses |
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
-
Verify container status:
docker ps -a
-
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
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.
-
View the list of RPC calls made by the monitor.
-
-
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 withulimit -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
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.