Custom Scripts
OpenZeppelin Monitor allows you to implement custom scripts for additional filtering of monitor matches and custom notification handling.
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. |
Custom Filter Scripts
Custom filter scripts allow you to apply additional conditions to matches detected by the monitor. This helps you refine the alerts you receive based on criteria specific to your use case.
Implementation Guide
-
Create a script in one of the supported languages:
-
Bash
-
Python
-
JavaScript
-
-
Your script will receive a JSON object with the following structure:
// Example for EVM MATCH
{
"args": ["--verbose"],
"monitor_match": {
"EVM": {
"matched_on": {
"events": [],
"functions": [
{
"expression": null,
"signature": "transfer(address,uint256)"
}
],
"transactions": [
{
"expression": null,
"status": "Success"
}
]
},
"matched_on_args": {
"events": null,
"functions": [
{
"args": [
{
"indexed": false,
"kind": "address",
"name": "to",
"value": "0x94d953b148d4d7143028f397de3a65a1800f97b3"
},
{
"indexed": false,
"kind": "uint256",
"name": "value",
"value": "434924400"
}
],
"hex_signature": "a9059cbb",
"signature": "transfer(address,uint256)"
}
]
},
"monitor": {
"addresses": [
{
"abi": null,
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}
],
"match_conditions": {
"events": [
{
"expression": "value > 10000000000",
"signature": "Transfer(address,address,uint256)"
}
],
"functions": [
{
"expression": null,
"signature": "transfer(address,uint256)"
}
],
"transactions": [
{
"expression": null,
"status": "Success"
}
]
},
"name": "Large Transfer of USDC Token",
"networks": ["ethereum_mainnet"],
"paused": false,
"trigger_conditions": [
{
"arguments": ["--verbose"],
"language": "Bash",
"script_path": "./config/filters/evm_filter_block_number.sh",
"timeout_ms": 1000
}
],
"triggers": ["evm_large_transfer_usdc_script"]
},
"receipt": {
"blockHash": "0x...",
"blockNumber": "0x...",
"contractAddress": null,
"cumulativeGasUsed": "0x...",
"effectiveGasPrice": "0x...",
"from": "0x...",
"gasUsed": "0xb068",
"status": "0x1",
"to": "0x...",
"transactionHash": "0x...",
"transactionIndex": "0x1fc",
"type": "0x2"
},
"transaction": {
"accessList": [],
"blockHash": "0x...",
"blockNumber": "0x1506545",
"chainId": "0x1",
"from": "0x...",
"gas": "0x7a120",
"gasPrice": "0x...",
"hash": "0x...",
"maxFeePerGas": "0x...",
"maxPriorityFeePerGas": "0x...",
"nonce": "0x14779f",
"to": "0x...",
"transactionIndex": "0x...",
"type": "0x2",
"value": "0x0"
}
}
}
}
// Example for STELLAR MATCH
{
"args": ["--verbose"],
"monitor_match": {
"Stellar": {
"monitor": {
"name": "Large Swap By Dex",
"networks": ["stellar_mainnet"],
"paused": false,
"addresses": [
{
"address": "GCXYK...",
"abi": null
}
],
"match_conditions": {
"functions": [
{
"signature": "swap(Address,U32,U32,U128,U128)",
"expression": "4 > 1000000000"
}
],
"events": [],
"transactions": []
},
"trigger_conditions": [
{
"arguments": ["--verbose"],
"language": "Bash",
"script_path": "./config/filters/stellar_filter_block_number.sh",
"timeout_ms": 1000
}
],
"triggers": ["stellar_large_transfer_usdc_script"]
},
"transaction": {
"status": "SUCCESS",
"txHash": "2b5a0c...",
"applicationOrder": 3,
"feeBump": false,
"envelopeXdr": "AAAAAA...",
"envelopeJson": {
"type": "ENVELOPE_TYPE_TX",
"tx": {/* transaction details */}
},
"resultXdr": "AAAAAA...",
"resultJson": {/* result details */},
"resultMetaXdr": "AAAAAA...",
"resultMetaJson": {/* metadata details */},
"diagnosticEventsXdr": ["AAAAAA..."],
"diagnosticEventsJson": [{/* event details */}],
"ledger": 123456,
"createdAt": 1679644800,
"decoded": {
"envelope": {/* decoded envelope */},
"result": {/* decoded result */},
"meta": {/* decoded metadata */}
}
},
"ledger": {
"hash": "abc1...",
"sequence": 123456,
"ledgerCloseTime": "2024-03-20T10:00:00Z",
"headerXdr": "AAAAAA...",
"headerJson": {/* header details */},
"metadataXdr": "AAAAAA...",
"metadataJSON": {/* metadata details */}
},
"matched_on": {
"functions": [
{
"signature": "swap(Address,U32,U32,U128,U128)",
"expression": "4 > 1000000000"
}
],
"events": [],
"transactions": []
},
"matched_on_args": {
"functions": [],
"events": null
}
}
}
}
Script Output Requirements
-
Your script should print a boolean value indicating whether the match should be filtered.
-
Print
true
if the match should be filtered out (not trigger an alert). -
Print
false
if the match should be processed (trigger an alert). -
Only the last printed line will be considered for evaluation.
Example Filter Script (Bash)
#!/bin/bash
main() {
# Read JSON input from stdin
input_json=$(cat)
# Parse arguments from the input JSON and initialize verbose flag
verbose=false
args=$(echo "$input_json" | jq -r '.args[]? // empty')
if [ ! -z "$args" ]; then
while IFS= read -r arg; do
if [ "$arg" = "--verbose" ]; then
verbose=true
echo "Verbose mode enabled"
fi
done <<< "$args"
fi
# Extract the monitor match data from the input
monitor_data=$(echo "$input_json" | jq -r '.monitor_match')
if [ "$verbose" = true ]; then
echo "Input JSON received:"
fi
# Extract blockNumber from the EVM receipt or transaction
block_number_hex=$(echo "$monitor_data" | jq -r '.EVM.transaction.blockNumber' || echo "")
# Validate that block_number_hex is not empty
if [ -z "$block_number_hex" ]; then
echo "Invalid JSON or missing blockNumber"
echo "false"
exit 1
fi
# Remove 0x prefix if present and clean the string
block_number_hex=$(echo "$block_number_hex" | tr -d '\n' | tr -d ' ')
block_number_hex=${block_number_hex#0x}
if [ "$verbose" = true ]; then
echo "Extracted block number (hex): $block_number_hex"
fi
# Convert hex to decimal with error checking
if ! block_number=$(printf "%d" $((16#${block_number_hex})) 2>/dev/null); then
echo "Failed to convert hex to decimal"
echo "false"
exit 1
fi
if [ "$verbose" = true ]; then
echo "Converted block number (decimal): $block_number"
fi
# Check if even or odd using modulo
is_even=$((block_number % 2))
if [ $is_even -eq 0 ]; then
echo "Block number $block_number is even"
echo "Verbose mode: $verbose"
echo "true"
exit 0
else
echo "Block number $block_number is odd"
echo "Verbose mode: $verbose"
echo "false"
exit 0
fi
}
# Call main function
main
This example script filters EVM transactions based on their block number:
-
Returns
true
(filter out) for transactions in even-numbered blocks -
Returns
false
(allow) for transactions in odd-numbered blocks -
Accepts a
--verbose
flag for detailed logging
Integration
Integrate your custom filter script with the monitor by following the configuration guidelines.
Trigger conditions are executed sequentially based on their position in the trigger conditions array. Every filter must return |
Custom Notification Scripts
Custom notification scripts allow you to define how alerts are delivered when specific conditions are met. This can include sending alerts to different channels or formatting notifications in a particular way.
Implementation Guide
-
Create a script in one of the supported languages:
-
Bash
-
Python
-
JavaScript
-
-
Your script will receive the same JSON input format as filter scripts
Script Output Requirements
-
A non-zero exit code indicates an error occurred
-
Error messages should be written to
stderr
-
A zero exit code indicates successful execution
Example Notification Script (Bash)
#!/bin/bash
main() {
# Read JSON input from stdin
input_json=$(cat)
# Parse arguments from the input JSON and initialize verbose flag
verbose=false
args=$(echo "$input_json" | jq -r '.args[]? // empty')
if [ ! -z "$args" ]; then
while IFS= read -r arg; do
if [ "$arg" = "--verbose" ]; then
verbose=true
echo "Verbose mode enabled"
fi
done <<< "$args"
fi
# Extract the monitor match data from the input
monitor_data=$(echo "$input_json" | jq -r '.monitor_match')
# Validate input
if [ -z "$input_json" ]; then
echo "No input JSON provided"
exit 1
fi
# Validate JSON structure
if ! echo "$input_json" | jq . >/dev/null 2>&1; then
echo "Invalid JSON input"
exit 1
fi
if [ "$verbose" = true ]; then
echo "Input JSON received:"
echo "$input_json" | jq '.'
echo "Monitor match data:"
echo "$monitor_data" | jq '.'
fi
# Process args if they exist
args_data=$(echo "$input_json" | jq -r '.args')
if [ "$args_data" != "null" ]; then
echo "Args: $args_data"
fi
# If we made it here, everything worked
echo "Verbose mode: $verbose"
# return a non zero exit code and an error message
echo "Error: This is a test error" >&2
exit 1
}
# Call main function
main
This example demonstrates how to:
-
Process the input JSON data
-
Handle verbose mode for debugging
-
Return error messages via
stderr
-
Set appropriate exit codes
Integration
Integrate your custom notification script with the triggers by following the configuration guidelines.
Performance Considerations
-
File descriptor limits: Each script execution requires file descriptors for
stdin
,stdout
, andstderr
-
Ensure your system allows at least 2,048 open file descriptors
-
Check your current limit on Unix-based systems with
ulimit -n
-
Temporarily increase the limit with
ulimit -n 2048
-
For permanent changes, modify
/etc/security/limits.conf
or equivalent for your system
-
-
Script timeout: Configure appropriate timeout values in your trigger conditions to prevent long-running scripts from blocking the pipeline
-
The
timeout_ms
parameter controls how long a script can run before being terminated
-
-
Resource usage: Complex scripts may consume significant CPU or memory resources
-
Consider optimizing resource-intensive operations in your scripts
-
Monitor system performance during high-volume periods
-
-
Script reloading: Since scripts are loaded at startup, any modifications to script files require restarting the monitor to take effect