OpenZeppelin Monitor
This software is in alpha stage. Use in production environments at your own risk. |
Overview
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.
Key Capabilities
-
Real-time Monitoring: Watch blockchain networks in real-time for specific events and transactions
-
Smart Filtering: Use flexible expressions to define exactly what you want to monitor
-
Multi-notification Support: Send alerts via Slack, Discord, Email, Telegram, Webhooks, or custom scripts
-
Configurable Scheduling: Set custom monitoring schedules using cron expressions
-
Data Persistence: Store monitoring data and resume from checkpoints
-
Extensible Architecture: Easy to add support for new blockchains and notification types
Notification Channels
-
Slack - Send formatted messages to Slack channels
-
Discord - Post alerts to Discord channels via webhooks
-
Email - Send email notifications with SMTP support
-
Telegram - Send messages to Telegram chats via bot API
-
Webhooks - Send HTTP requests to custom endpoints
-
Custom Scripts - Execute Python, JavaScript, or Bash scripts
To get started immediately, see Quickstart. |
Installation
Local Installation
-
Clone the repository:
git clone https://github.com/openzeppelin/openzeppelin-monitor cd openzeppelin-monitor
-
Build the application:
cargo build --release
-
Move binary to project root:
mv ./target/release/openzeppelin-monitor .
-
Verify installation:
./openzeppelin-monitor --help
-
View available options:
./openzeppelin-monitor --help # Enable logging to file ./openzeppelin-monitor --log-file # Enable metrics server ./openzeppelin-monitor --metrics # Validate configuration files without starting the service ./openzeppelin-monitor --check
Docker Installation
-
Clone the repository:
git clone https://github.com/openzeppelin/openzeppelin-monitor cd openzeppelin-monitor
-
Set up environment:
cp .env.example .env # Edit .env file with your configuration
-
Start with Docker Compose:
cargo make docker-compose-up
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
To view prometheus metrics in a UI, you can use http://localhost:9090
on your browser.
To view grafana dashboard, you can use http://localhost:3000
on your browser.
By default, predefined metrics within a dashboard is populated in grafana.
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, network, and trigger names must be unique across all configurations files
-
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. |
Safe Protocol Guidelines
The monitor implements protocol security validations across different components and will issue warnings when potentially insecure configurations are detected. While insecure protocols are not blocked, we strongly recommend following these security guidelines:
Notification Protocols
Webhook Notifications
-
HTTPS Recommended: URLs should use HTTPS protocol
-
Authentication Recommended: Including either:
-
X-API-Key
header -
Authorization
header
-
-
Optional Secret: Can include a secret for HMAC authentication
-
When a secret is provided, the monitor will:
-
Generate a timestamp in milliseconds
-
Create an HMAC-SHA256 signature of the payload and timestamp
-
Add the signature in the
X-Signature
header -
Add the timestamp in the
X-Timestamp
header
-
-
The signature is computed as:
HMAC-SHA256(secret, payload + timestamp)
-
-
Warning: Non-HTTPS URLs or missing authentication headers will trigger security warnings
Slack Notifications
-
HTTPS Recommended: Webhook URLs should start with
https://hooks.slack.com/
-
Warning: Non-HTTPS URLs will trigger security warnings
Discord Notifications
-
HTTPS Recommended: Webhook URLs should start with
https://discord.com/api/webhooks/
-
Warning: Non-HTTPS URLs will trigger security warnings
Telegram Notifications
-
Protocol:
POST
request with aapplication/json
payload to thesendMessage
method. -
Security:
-
HTTPS Required: The API endpoint uses HTTPS.
-
Authentication is handled via the Bot Token in the URL. Keep this token secure.
-
-
Formatting: Messages are sent with
parse_mode
set toMarkdownV2
. Special characters in the message title and body are automatically escaped to prevent formatting errors.
Email Notifications
-
Secure Ports Recommended: The following ports are considered secure:
-
465: SMTPS (SMTP over SSL)
-
587: SMTP with STARTTLS
-
993: IMAPS (IMAP over SSL)
-
-
Warning: Using other ports will trigger security warnings
-
Valid Format: Email addresses must follow RFC 5322 format
Notifcations Retry Policy
Following notification protocols support retry policies:
-
Slack
-
Discord
-
Telegram
-
Webhook
Default retry policy is using exponential backoff with the following parameters:
Parameter |
Default Value |
Description |
|
|
Maximum number of retries before giving up |
|
|
Base duration for exponential backoff calculations in seconds |
|
|
Initial backoff duration in milliseconds |
|
|
Maximum backoff duration in seconds |
|
|
Jitter strategy to apply to the backoff duration, currently supports |
These parameters can be overridden by providing custom HttpRetryConfig
struct in retry_policy
field in trigger configuration.
Script Security
File Permissions (Unix Systems)
-
Restricted Write Access: Script files should not have overly permissive write permissions
-
Recommended Permissions: Use
644
(rw-r—r--
) for script files -
Warning: Files with mode
022
or more permissive will trigger security warnings
chmod 644 ./config/filters/my_script.sh
Secret Management
The monitor implements a secure secret management system with support for multiple secret sources and automatic memory zeroization.
Secret Sources
The monitor supports three types of secret sources:
-
Plain Text: Direct secret values (wrapped in
SecretString
for secure memory handling) -
Environment Variables: Secrets stored in environment variables
-
Hashicorp Cloud Vault: Secrets stored in Hashicorp Cloud Vault
Security Features
-
Automatic Zeroization: Secrets are automatically zeroized from memory when no longer needed
-
Type-Safe Resolution: Secure handling of secret resolution with proper error handling
-
Configuration Support: Serde support for configuration files
Configuration
Secrets can be configured in the JSON files using the following format:
{
"type": "Plain",
"value": "my-secret-value"
}
{
"type": "Environment",
"value": "MY_SECRET_ENV_VAR"
}
{
"type": "HashicorpCloudVault",
"value": "my-secret-name"
}
Hashicorp Cloud Vault Integration
To use Hashicorp Cloud Vault, configure the following environment variables:
Environment Variable | Description |
---|---|
|
Hashicorp Cloud Vault client ID |
|
Hashicorp Cloud Vault client secret |
|
Hashicorp Cloud Vault organization ID |
|
Hashicorp Cloud Vault project ID |
|
Hashicorp Cloud Vault application name |
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. Accepts both raw bytes (e.g., "1073741824") or human-readable formats (e.g., "1GB", "500MB"). |
|
|
|
Enable metrics server for external tools to scrape metrics. |
|
|
|
Port to use for metrics server. |
|
- |
|
Hashicorp Cloud Vault client ID for secret management. |
|
- |
|
Hashicorp Cloud Vault client secret for secret management. |
|
- |
|
Hashicorp Cloud Vault organization ID for secret management. |
|
- |
|
Hashicorp Cloud Vault project ID for secret management. |
|
- |
|
Hashicorp Cloud Vault application name for secret management. |
-
Copy and configure some example files:
# EVM Configuration
cp examples/config/monitors/evm_transfer_usdc.json config/monitors/evm_transfer_usdc.json
cp examples/config/networks/ethereum_mainnet.json config/networks/ethereum_mainnet.json
# Stellar Configuration
cp examples/config/monitors/stellar_swap_dex.json config/monitors/stellar_swap_dex.json
cp examples/config/networks/stellar_mainnet.json config/networks/stellar_mainnet.json
# Notification Configuration
cp examples/config/triggers/slack_notifications.json config/triggers/slack_notifications.json
cp examples/config/triggers/email_notifications.json config/triggers/email_notifications.json
# Filter Configuration
cp examples/config/filters/evm_filter_block_number.sh config/filters/evm_filter_block_number.sh
cp examples/config/filters/stellar_filter_block_number.sh config/filters/stellar_filter_block_number.sh
Command Line Options
The monitor supports several command-line options for configuration and control:
Option | Default | Description |
---|---|---|
|
|
Write logs to file instead of stdout |
|
|
Set log level (trace, debug, info, warn, error) |
|
|
Path to store log files |
|
|
Maximum log file size before rolling |
|
|
Address to start the metrics server on |
|
|
Enable metrics server |
|
- |
Path to the monitor to execute (for testing) |
|
- |
Network to execute the monitor for (for testing) |
|
- |
Block number to execute the monitor for (for testing) |
|
|
Validate configuration files without starting the service |
Data Storage Configuration
The monitor uses file-based storage by default.
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": {
"type": "plain",
"value": "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 |
---|---|---|
|
|
Type of blockchain ("EVM" or "Stellar") |
|
|
Required - Unique identifier for the network |
|
|
Required - Unique Human-readable network name |
|
|
List of RPC endpoints with weights for load balancing |
|
|
Network chain ID (EVM only) |
|
|
Network identifier (Stellar only) |
|
|
Average block time in milliseconds |
|
|
Number of blocks to wait for confirmation |
|
|
Monitor scheduling in cron format |
|
|
Maximum number of past blocks to process |
|
|
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": {
"type": "plain",
"value": "https://hooks.slack.com/services/A/B/C"
},
"message": {
"title": "large_transfer_slack triggered",
"body": "Large transfer of ${events.0.args.value} USDC from ${events.0.args.from} to ${events.0.args.to} | https://etherscan.io/tx/${transaction.hash}#eventlog"
}
}
},
"stellar_large_transfer_usdc_slack": {
"name": "Large Transfer Slack Notification",
"trigger_type": "slack",
"config": {
"slack_url": {
"type": "environment",
"value": "SLACK_WEBHOOK_URL"
},
"message": {
"title": "large_transfer_usdc_slack triggered",
"body": "${monitor.name} triggered because of a large transfer of ${functions.0.args.amount} USDC to ${functions.0.args.to} | https://stellar.expert/explorer/testnet/tx/${transaction.hash}"
}
}
}
}
Trigger Types
Slack Notifications
{
"slack_url": {
"type": "HashicorpCloudVault",
"value": "slack-webhook-url"
},
"message": {
"title": "Alert Title",
"body": "Alert message for ${transaction.hash}"
}
}
Slack Notification Fields
Field | Type | Description |
---|---|---|
|
|
Required - Unique Human-readable name for the notification |
|
|
Must be "slack" for Slack notifications |
|
|
Secret type ("Plain", "Environment", or "HashicorpCloudVault") |
|
|
Secret value (URL, environment variable name, or vault secret name) |
|
|
Title that appears in the Slack message |
|
|
Message template with variable substitution |
Email Notifications
{
"host": "smtp.gmail.com",
"port": 465,
"username": {
"type": "plain",
"value": "[email protected]"
},
"password": {
"type": "environment",
"value": "SMTP_PASSWORD"
},
"message": {
"title": "Alert Subject",
"body": "Alert message for ${transaction.hash}",
},
"sender": "[email protected]",
"recipients": ["[email protected]"]
}
Email Notification Fields
Field | Type | Description |
---|---|---|
|
|
Required - Unique Human-readable name for the notification |
|
|
Must be "email" for email notifications |
|
|
SMTP server hostname |
|
|
SMTP port (defaults to 465) |
|
|
Secret type ("Plain", "Environment", or "HashicorpCloudVault") |
|
|
Secret value (username, environment variable name, or vault secret name) |
|
|
Secret type ("Plain", "Environment", or "HashicorpCloudVault") |
|
|
Secret value (password, environment variable name, or vault secret name) |
|
|
Email subject line |
|
|
Email body template with variable substitution |
|
|
Sender email address |
|
|
List of recipient email addresses |
Webhook Notifications
{
"url": {
"type": "HashicorpCloudVault",
"value": "webhook-url"
},
"method": "POST",
"secret": {
"type": "environment",
"value": "WEBHOOK_SECRET"
},
"headers": {
"Content-Type": "application/json"
},
"message": {
"title": "Alert Title",
"body": "Alert message for ${transaction.hash}"
}
}
Webhook Notification Fields
Field | Type | Description |
---|---|---|
|
|
Required - Unique Human-readable name for the notification |
|
|
Must be "webhook" for webhook notifications |
|
|
Secret type ("Plain", "Environment", or "HashicorpCloudVault") |
|
|
Secret value (URL, environment variable name, or vault secret name) |
|
|
HTTP method (POST, GET, etc.) defaults to POST |
|
|
Secret type ("Plain", "Environment", or "HashicorpCloudVault") |
|
|
Secret value (HMAC secret, environment variable name, or vault secret name) |
|
|
Headers to include in the webhook request |
|
|
Title that appears in the webhook message |
|
|
Message template with variable substitution |
Discord Notifications
{
"discord_url": {
"type": "plain",
"value": "https://discord.com/api/webhooks/123-456-789"
},
"message": {
"title": "Alert Title",
"body": "Alert message for ${transaction.hash}"
}
}
Discord Notification Fields
Field | Type | Description |
---|---|---|
|
|
Required - Unique Human-readable name for the notification |
|
|
Must be "discord" for Discord notifications |
|
|
Secret type ("Plain", "Environment", or "HashicorpCloudVault") |
|
|
Secret value (URL, environment variable name, or vault secret name) |
|
|
Title that appears in the Discord message |
|
|
Message template with variable substitution |
Telegram Notifications
{
"token": {
"type": "HashicorpCloudVault",
"value": "telegram-bot-token"
},
"chat_id": "9876543210",
"message": {
"title": "Alert Title",
"body": "Alert message for ${transaction.hash}"
}
}
Telegram Notification Fields
Field | Type | Description |
---|---|---|
|
|
Required - Unique Human-readable name for the notification |
|
|
Must be "telegram" for Telegram notifications |
|
|
Secret type ("Plain", "Environment", or "HashicorpCloudVault") |
|
|
Secret value (bot token, environment variable name, or vault secret name) |
|
|
Telegram chat ID |
|
|
Whether to disable web preview in Telegram messages (defaults to false) |
|
|
Title that appears in the Telegram message |
|
|
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 |
---|---|---|
|
|
Required - Unique Human-readable name for the notification |
|
|
Must be "script" for Custom Script notifications |
|
|
The language of the script |
|
|
The path to the script |
|
|
The arguments of the script (optional). |
|
|
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
The monitor uses a structured JSON format with nested objects for template variables. The data is flattened into dot notation for template use.
Common Variables
Variable | Description |
---|---|
|
Name of the triggered monitor |
|
Hash of the transaction |
|
Function signature |
|
Event signature |
Message Formatting
Slack, Discord, Telegram, Email and Webhook support Markdown formatting in their message bodies. You can use Markdown syntax to enhance your notifications.
Example Email Notification with Markdown
{
"email_notification": {
"name": "Formatted Alert",
"trigger_type": "email",
"config": {
"host": "smtp.example.com",
"port": 465,
"username": {"type": "plain", "value": "[email protected]"},
"password": {"type": "plain", "value": "password"},
"message": {
"title": "**High Value Transfer Alert**",
"body": "### Transaction Details\n\n* **Amount:** ${events.0.args.value} USDC\n* **From:** `${events.0.args.from}`\n* **To:** `${events.0.args.to}`\n\n> Transaction Hash: ${transaction.hash}\n\n[View on Explorer](https://etherscan.io/tx/${transaction.hash})"
},
"sender": "[email protected]",
"recipients": ["[email protected]"]
}
}
}
Example Slack Notification with Markdown
{
"slack_notification": {
"name": "Formatted Alert",
"trigger_type": "slack",
"config": {
"slack_url": {"type": "plain", "value": "https://hooks.slack.com/services/XXX/YYY/ZZZ"},
"message": {
"title": "*🚨 High Value Transfer Alert*",
"body": "*Transaction Details*\n\n• *Amount:* `${events.0.args.value}` USDC\n• *From:* `${events.0.args.from}`\n• *To:* `${events.0.args.to}`\n\n>Transaction Hash: `${transaction.hash}`\n\n<https://etherscan.io/tx/${transaction.hash}|View on Explorer>"
}
}
}
}
Example Discord Notification with Markdown
{
"discord_notification": {
"name": "Formatted Alert",
"trigger_type": "discord",
"config": {
"discord_url": {"type": "plain", "value": "https://discord.com/api/webhooks/XXX/YYY"},
"message": {
"title": "**🚨 High Value Transfer Alert**",
"body": "# Transaction Details\n\n* **Amount:** `${events.0.args.value}` USDC\n* **From:** `${events.0.args.from}`\n* **To:** `${events.0.args.to}`\n\n>>> Transaction Hash: `${transaction.hash}`\n\n**[View on Explorer](https://etherscan.io/tx/${transaction.hash})"
}
}
}
}
Example Telegram Notification with Markdown
{
"telegram_notification": {
"name": "Formatted Alert",
"trigger_type": "telegram",
"config": {
"token": {"type": "plain", "value": "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZ"},
"chat_id": "9876543210",
"message": {
"title": "*🚨 High Value Transfer Alert*",
"body": "*Transaction Details*\n\n• *Amount:* `${events.0.args.value}` USDC\n• *From:* `${events.0.args.from}`\n• *To:* `${events.0.args.to}`\n\n`Transaction Hash: ${transaction.hash}`\n\n[View on Explorer](https://etherscan.io/tx/${transaction.hash})"
}
}
}
}
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",
"contract_spec": [ ... ]
}
],
"match_conditions": {
"functions": [
{
"signature": "transfer(address,uint256)",
"expression": "value > 1000000"
}
],
"events": [
{
"signature": "Transfer(address,address,uint256)",
"expression": "value > 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"]
}
Available Fields
Field | Type | Description |
---|---|---|
|
|
Required - Unique identifier for this monitor |
|
|
List of network slugs this monitor should watch |
|
|
Whether this monitor is currently paused |
|
|
Contract addresses to monitor with optional ABIs |
|
|
Collection of conditions that can trigger the monitor |
|
|
Collection of filters to apply to monitor matches before executing triggers |
|
|
IDs of triggers to execute when conditions match |
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": "value > 1000000"
}
]
}
Transaction Conditions
Match transaction properties. The available fields and expression syntax depend on the network type (EVM/Stellar)
{
"transactions": [
{
"status": "Success", // Only match successful transactions
"expression": "value > 1500000000000000000" // Match transactions with value greater than 1.5 ETH
}
]
}
Available Transaction Fields (EVM)
Field | Type | Description |
---|---|---|
|
|
Transaction value in wei |
|
|
Sender address (case-insensitive comparison) |
|
|
Recipient address (case-insensitive comparison) |
|
|
Transaction hash |
|
|
Gas price in wei (legacy transactions) |
|
|
EIP-1559 maximum fee per gas |
|
|
EIP-1559 priority fee |
|
|
Gas limit for transaction |
|
|
Sender nonce |
|
|
Hex-encoded input data (e.g., "0xa9059cbb…") |
|
|
Actual gas used (from receipt) |
|
|
Position in block |
Available Transaction Fields (Stellar)
Field | Type | Description |
---|---|---|
|
|
Transaction hash |
|
|
Ledger sequence number where the transaction was included |
|
|
Value associated with the first relevant operation (e.g., payment amount). Defaults to 0 if no relevant operation or value is found. |
|
|
Source account address of the first relevant operation (e.g., payment sender). Case-insensitive comparison. |
|
|
Destination account address of the first relevant operation (e.g., payment recipient or invoked contract). Case-insensitive comparison. |
Expressions
Expressions allow for condition checking of function arguments, event parameters, and transaction fields.
Supported Parameter/Field Types and Basic Operations:
Type | Description | Example Operators | Notes |
---|---|---|---|
|
Integer values (e.g., |
|
Numbers must have digits before and after a decimal point if one is present (e.g., |
|
Blockchain addresses. |
|
Comparisons (e.g., |
|
Text values. Can be single-quoted (e.g., |
|
Quoted strings support |
|
True or false values. |
|
Represented as |
|
A string literal starting with |
|
Treated as a string for comparison purposes (e.g., |
|
Ordered list of items. For Stellar, often a JSON string in config (e.g., |
|
Detailed operations, including indexed access and behavior of |
|
Key-value pairs, typically represented as a JSON string in config (e.g., |
|
Supports dot notation for field access (e.g., |
|
Ordered list, where the parameter’s value can be a CSV string (e.g., |
|
Behavior of |
Logical Operators:
-
AND - All conditions must be true
-
OR - At least one condition must be true
-
() - Parentheses for grouping
-
AND has higher precedence than OR (i.e., AND operations are evaluated before OR operations if not grouped by parentheses)
Variable Naming and Access (Left-hand side of conditions):
The left-hand side (LHS) of a condition specifies the data field or parameter whose value you want to evaluate.
Base Names:
-
These are the direct names of parameters or fields, such as
amount
,from
,status
, or event parameter indices like0
,1
(common in Stellar events). -
Base names can consist of alphanumeric characters (a-z, A-Z, 0-9) and underscores (
_
). -
They can start with a letter, an underscore, or a digit. Starting with a digit is primarily relevant for numerically indexed parameters (e.g., Stellar event parameters).
-
Important: Variable names are case-sensitive during evaluation. The name used in the expression must exactly match the casing of the field name in the source data (e.g., from an ABI or blockchain data structure). For example, if a field is named
TotalValue
in the data, an expression usingtotalvalue
will not find it. -
Variable names cannot be keywords (e.g.,
true
,AND
,OR
,contains
). Keywords themselves are parsed case-insensitively.
Path Accessors (for complex types):
If a base parameter is a complex type like an object, map, or array, you can access its internal data using accessors:
Key Access: Use dot notation (.
) to access properties of an object or map.
-
Examples:
transaction.value
,user.name
,data.0
(if0
is a valid key name as a string). -
Keys typically consist of alphanumeric characters and underscores. They usually start with a letter or underscore, but purely numeric keys (e.g.,
.0
,.123
) are also supported for map-like structures where keys might be strings representing numbers. -
Keys cannot contain hyphens (
-
).
Index Access: Use bracket notation ([]
) to access elements of an array by their zero-based integer index.
-
Examples:
my_array[0]
,log_entries[3]
. -
The index must be a non-negative integer.
Combined Access: You can combine key and index accessors to navigate nested structures.
-
Example:
event.data_array[0].property
(accesses theproperty
field of the first object indata_array
, which is part ofevent
). -
Example:
map.numeric_key_as_string_0[1].name
(accesses thename
property of the second element of an array stored under the key0
inmap
).
String Operations:
Several operators are available for matching patterns and comparing string values. These are particularly useful for EVM transaction input
data, Stellar parameters defined with kind: "string"
, or any other field that contains text.
-
string_param starts_with 'prefix'
:: Checks if the string parameter’s value begins with the specifiedprefix
. Example:transaction.input starts_with '0xa9059cbb'
(checks for ERC20 transfer function selector). -
string_param ends_with 'suffix'
:: Checks if the string parameter’s value ends with the specifiedsuffix
. Example:file_name ends_with '.txt'
-
string_param contains 'substring'
:: Checks if the string parameter’s value contains the specifiedsubstring
anywhere within it. Example:message contains 'error'
-
string_param == 'exact_string'
:: Checks if the string parameter’s value is exactly equal toexact_string
. -
string_param != 'different_string'
:: Checks if the string parameter’s value is not equal todifferent_string
.
Important Notes on String Operations:
-
Operator Keywords: The operator keywords themselves (
starts_with
,ends_with
,contains
,AND
,OR
,true
,false
, comparison symbols like==
,>
) are parsed case-insensitively. For example,CONTAINS
is treated the same ascontains
, andTRUE
is the same astrue
. -
Case-Insensitive Evaluation for String Comparisons: When comparing string data (e.g., from event parameters, transaction fields, or function arguments) with literal string values in your expression, all standard string operations perform a case-insensitive comparison during evaluation.
-
Equality (
==
) and Inequality (!=
) -
Pattern matching (
starts_with
,ends_with
,contains
)
-
-
Variable Name Case Sensitivity: It is important to distinguish this from variable names (the left-hand side of your condition, e.g.,
status
). Variable names are case-sensitive and must exactly match the field names in your source data (ABI, etc.).
Whitespace Handling: Flexible whitespace is generally allowed around operators, parentheses, and keywords for readability. However, whitespace within quoted string literals is significant and preserved.
Operations on Complex Types
Beyond simple primitive types, expressions can also interact with more complex data structures like arrays, objects, and vectors.
EVM Specifics
Array Operations (kind: "array"
)
When an EVM parameter is an array (often represented internally or configured with kind: "array"
and its value being a JSON string representation if manually configured), the following operations are supported:
-
array_param contains 'value'
checks if the string'value'
exists within the array. -
array_param == '["raw_json_array_string"]'
string comparison of the array’s entire JSON string representation against the provided string -
array_param != '["raw_json_array_string"]'
the negation of the above -
array_param[0]
indexed access
Stellar Specifics
Object (kind: "object"
) / Map (kind: "Map"
) Operations
-
object_param.key == 'value'
checks if the object or map has a key namedkey
with the value'value'
. -
object_param.nested_key.another_key > 100
checks if the nested keyanother_key
withinnested_key
has a value greater than 100. -
object_param == '{"raw_json_object_string"}'
checks if the object or map matches the provided JSON string representation. -
object_param != '{"raw_json_object_string"}'
the negation of the above
Array (kind: "array"
) Operations
-
array_param[index]
accesses the element at the specifiedindex
in the array. -
array_param[0] == 'value'
checks if the first element in the array is equal to'value'
. -
array_param[1].property == 'value'
checks if the second element in the array has a property namedproperty
with the value'value'
. -
array_param contains 'value'
checks if the array contains the string'value'
. -
array_param == '["raw_json_array_string"]'
checks if the array matches the provided JSON string representation. -
array_param != '["raw_json_array_string"]'
the negation of the above
Vector (kind: "vec"
) Operations
When a Stellar parameter has kind: "vec"
, its value can be either a CSV string or a JSON array string.
-
vec_param contains 'item'
checks if the vector contains the string'item'
. This works for both CSV and JSON array strings. -
vec_param == 'raw_string_value'
checks if the vector matches the provided raw string value. This works for both CSV and JSON array strings. -
vec_param != 'raw_string_value'
the negation of the above
Event Parameter Access (Stellar)
Stellar event parameters are typically accessed by their numeric index as the base variable name (e.g., 0
, 1
, 2
). If an indexed event parameter is itself a complex type (like an array or map, represented as a JSON string), you can then apply the respective access methods:
-
If event parameter
0
(kind: "Map") is'{"id": 123, "name": "Test"}'
:-
0.id == 123
-
0.name contains 'est'
(case-insensitive)
-
-
If event parameter
1
(kind: "array") is'["alpha", {"val": "beta"}]'
:-
1[0] == 'ALPHA'
(case-insensitive) -
1[1].val == 'Beta'
(case-insensitive) -
1 contains 'beta'
(case-insensitive deep search)
-
EVM Examples
These examples assume common EVM event parameters or transaction fields.
// Numeric
"transaction.value > 1000000000000000000" // Value greater than 1 ETH
"event.amount <= 500"
"block.number == 12345678"
// String (case-insensitive evaluation for '==' and 'contains')
"transaction.to == '0xdeadbeef...'" // Address check (address value comparison itself is case-insensitive)
"event.token_name == 'mytoken'"
"transaction.input contains 'a9059cbb'" // Checks for ERC20 transfer selector
// Boolean
"receipt.status == true" // or simply "receipt.status" if boolean field can be evaluated directly
"event.isFinalized == false"
"transaction.value > 1000 AND event.type == 'Deposit'"
"(receipt.status == true OR event.fallback_triggered == true) AND user.is_whitelisted == false"
"transaction.input starts_with '0xa9059cbb'" // Case-insensitive for the operation
"event.message ends_with 'failed'"
"event.details contains 'critical alert'"
Assume event.ids
is [10, 20, 30]
and event.participants
is [{"user": "Alice", "role": "admin"}, {"user": "Bob", "role": "editor"}]
.
"event.ids[0] == 10"
"event.ids contains '20'" // Checks for string '20' (case-insensitive)
"event.participants contains 'Alice'" // True (deep search, case-insensitive)
"event.participants contains 'editor'" // True (deep search, case-insensitive)
"event.participants == '[{\"user\": \"Alice\", \"role\": \"admin\"}, {\"user\": \"Bob\", \"role\": \"editor\"}]'" // Raw JSON match (case-sensitive for structure and keys)
Stellar Examples
// Numeric
"event.params.amount > 10000000" // Accessing 'amount' field in an object 'params'
"ledger.sequence >= 123456"
// String (case-insensitive evaluation for '==' and 'contains')
"event.params.recipient == 'GBD22...'" // Address check
"event.type == 'payment_processed'"
// Boolean
"transaction.successful == true"
"event.data.is_verified == false"
"event.data.value > 500 AND event.source_account == 'GCA7Z...'"
"(event.type == 'TRANSFER' OR event.type == 'PAYMENT') AND event.params.asset_code == 'XLM'"
"event.contract_id starts_with 'CA23...'"
"event.memo ends_with '_TEST'"
"event.params.description contains 'urgent'"
kind: "object"
) / Map (kind: "Map"
) OperationsAssume event.details
(kind: "Map") is '{"id": 123, "user": {"name": "CHarlie", "status": "Active"}, "tags": ["new"]}'
.
"event.details.id == 123"
"event.details.user.name == 'charlie'" // Case-insensitive string comparison
"event.details.user.status contains 'act'" // Case-insensitive contains
"event.details.tags == '[\"new\"]'" // Raw JSON string match for the 'tags' field
kind: "array"
) OperationsAssume event.items
(kind: "array") is '[{"sku": "A1", "qty": 10}, {"sku": "B2", "qty": 5, "notes":"Rush order"}]'
.
"event.items[0].sku == 'a1'"
"event.items[1].qty < 10"
"event.items contains 'A1'" // Deep search (case-insensitive)
"event.items contains 'rush order'" // Deep search (case-insensitive)
kind: "vec"
) OperationsAssume csv_data
(kind: "vec") is "ALPHA,Bravo,Charlie"
and json_array_data
(kind: "vec") is '["Delta", {"id": "ECHO"}, "Foxtrot"]'
.
"csv_data contains 'bravo'" // Case-insensitive CSV element match
"csv_data == 'ALPHA,Bravo,Charlie'" // Raw string match
"json_array_data contains 'delta'" // Case-insensitive deep search (like array)
"json_array_data contains 'ECHO'" // Case-insensitive deep search (like array)
Assume event parameter 0
is 12345
(u64), 1
(kind: "array") is '["Val1", "VAL2"]'
, and 2
(kind: "Map") is '{"keyA": "dataX", "keyB": 789}'
.
"0 > 10000"
"1[0] == 'val1'"
"1 contains 'val2'"
"2.keyA == 'DATAX'"
"2.keyB < 1000"
With SEP-48 support, Stellar functions can now reference parameters by name (e.g., You can find the contract specification through Stellar contract explorer tool. For example: Stellar DEX Contract Interface |
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 |
---|---|---|
|
String |
The path to the script |
|
String |
The language of the script |
|
Array[String] |
The arguments of the script (optional). |
|
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 can be provided in two ways:
-
For EVM networks: Through the monitor configuration using standard Ethereum ABI format
-
For Stellar networks: Through the monitor configuration using SEP-48 format, or automatically fetched from the chain if not provided
-
-
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
-
Basic startup:
./openzeppelin-monitor
-
With logging to file:
./openzeppelin-monitor --log-file
-
With metrics enabled:
./openzeppelin-monitor --metrics
-
Validate configuration without starting:
./openzeppelin-monitor --check
Docker Execution
-
Start all services:
cargo make docker-compose-up
-
With metrics and monitoring (Prometheus + Grafana):
# Set METRICS_ENABLED=true in .env file, then: docker compose --profile metrics up -d
-
View logs:
docker compose logs -f monitor
-
Stop services:
cargo make docker-compose-down
Command Line Options
Option | Default | Description |
---|---|---|
|
|
Write logs to file instead of stdout |
|
|
Set log level (trace, debug, info, warn, error) |
|
|
Enable metrics server on port 8081 |
|
|
Validate configuration files only |
|
- |
Show all available options |
Testing your configuration
Network Configuration
The validate_network_config.sh
script helps ensure your network configuration is properly set up and operational. The script:
-
Tests the health of all configured RPC endpoints
-
Validates connectivity using network-specific methods
-
Provides clear visual feedback for each endpoint
# Test default networks directory (/config/networks/)
./scripts/validate_network_config.sh
# Test a specific configuration directory
./scripts/validate_network_config.sh -f /path/to/configs
Run this script when setting up new networks, before deploying configuration changes, or when troubleshooting connectivity issues. |
Validating Configuration Files
Before starting the monitor service, you can validate your configuration files using the --check
option:
./openzeppelin-monitor --check
This command will:
-
Parse and validate all configuration files
-
Check for syntax errors
-
Verify references between monitors, networks, and triggers
-
Report any issues without starting the service
It’s recommended to run this check after making changes to any configuration files.
1. Latest Block Mode
This mode processes the most recent blocks across all configured networks.
./openzeppelin-monitor --monitor-path="config/monitors/evm_transfer_usdc.json"
What this does:
-
Runs the "Large Transfer of USDC Token" monitor
-
Targets all networks specified in the configuration
-
Processes only the latest block for each network
-
Sends a notification to all associated channels for every match that is found
2. Specific Block Mode
This mode allows you to analyze a particular block on a specific network, which is useful for debugging specific transactions, verifying monitor behavior on known events, and testing monitor performance on historical data.
./openzeppelin-monitor \
--monitor-path="config/monitors/evm_transfer_usdc.json" \
--network=ethereum_mainnet \
--block=12345678
What this does:
-
Runs the "Large Transfer of USDC Token" monitor
-
Targets only the specified network (
ethereum_mainnet
) -
Processes only the specified block (
12345678
) -
Sends a notification to all associated channels for every match that is found
Specific Block Mode requires both parameters:
|
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
-
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.