Examples
Complete examples showing how to use all modules of the OP Interop Alerts SDK together for comprehensive cross-chain monitoring and alerting.
Example Overview
These examples demonstrate real-world usage patterns combining tracking, metrics generation, and intelligent alerting. Each example is complete and ready to run.
Basic Monitoring
Simple setup with console logging and basic metrics
Production Monitoring
Full production setup with Slack alerts and custom rules
Advanced Analytics
Custom metrics and multi-channel alerting
Basic Monitoring Setup
A simple example that demonstrates the core functionality with console output:
import {
startTracking,
generateMetrics,
TrackingResult,
InteropMetrics,
hasConsecutiveFailures,
getConsecutiveFailureCount
} from '@wakeuplabs/op-interop-alerts-sdk';
import { chainsInfoMock } from '@wakeuplabs/op-interop-alerts-sdk/config';
// Configuration
const pksInfo = {
origin: process.env.ORIGIN_PRIVATE_KEY as `0x${string}`,
destination: process.env.DESTINATION_PRIVATE_KEY as `0x${string}`,
};
const TRACKING_INTERVAL_MINUTES = 10;
const METRICS_THRESHOLD = 3; // Generate metrics after 3 data points
// Storage for tracking results
const trackingResults: TrackingResult[] = [];
// Tracking callback with basic logging
const trackingCallback = (result: TrackingResult) => {
const timestamp = result.timestamp.toISOString();
console.log(`\n=== [${timestamp}] TRACKING CYCLE ===`);
if (result.success && result.data) {
const { sentMessage, relayMessage } = result.data;
const latency = relayMessage.localTimestamp.getTime() - sentMessage.localTimestamp.getTime();
console.log('ā
Cross-chain message successful');
console.log(` Latency: ${(latency / 1000).toFixed(2)}s`);
console.log(` Send Gas: ${sentMessage.gasUsed.toString()}`);
console.log(` Relay Gas: ${relayMessage.gasUsed.toString()}`);
console.log(` Send Tx: ${sentMessage.transactionHash}`);
console.log(` Relay Tx: ${relayMessage.transactionHash}`);
} else {
console.log('ā Cross-chain message failed');
if (result.error) {
console.log(` Error: ${result.error.error.message}`);
}
}
// Store result for metrics
trackingResults.push(result);
console.log(` Total results: ${trackingResults.length}`);
// Check for consecutive failures
if (hasConsecutiveFailures(trackingResults, 3)) {
const failureCount = getConsecutiveFailureCount(trackingResults);
console.log(`ā ļø WARNING: ${failureCount} consecutive failures detected!`);
}
// Generate metrics when we have enough data
if (trackingResults.length >= METRICS_THRESHOLD) {
displayMetrics();
}
console.log('=== END CYCLE ===\n');
};
function displayMetrics() {
try {
const metrics: InteropMetrics = generateMetrics(trackingResults);
console.log('\nš === METRICS SUMMARY ===');
console.log(`System Status: ${metrics.status.interopStatus} (${metrics.status.healthLevel})`);
console.log(`Success Rate: ${metrics.coreMetrics.throughput.successRate.toFixed(1)}%`);
console.log(`Average Latency: ${(metrics.coreMetrics.latency.averageLatencyMs / 1000).toFixed(1)}s`);
console.log(`P95 Latency: ${(metrics.coreMetrics.latency.p95LatencyMs / 1000).toFixed(1)}s`);
console.log(`Messages/Hour: ${metrics.coreMetrics.throughput.messagesPerHour.toFixed(1)}`);
if (metrics.health.alerts.length > 0) {
console.log('\nšØ Health Alerts:');
metrics.health.alerts.forEach((alert, i) => {
console.log(` ${i + 1}. [${alert.level}] ${alert.message}`);
});
}
if (metrics.health.recommendations.length > 0) {
console.log('\nš” Recommendations:');
metrics.health.recommendations.forEach((rec, i) => {
console.log(` ${i + 1}. ${rec}`);
});
}
console.log('=== END METRICS ===\n');
} catch (error) {
console.error('ā Error generating metrics:', error);
}
}
// Start monitoring
async function main() {
console.log('š Starting OP Interop Basic Monitoring');
console.log(`š Tracking interval: ${TRACKING_INTERVAL_MINUTES} minutes`);
console.log(`š Metrics threshold: ${METRICS_THRESHOLD} data points\n`);
try {
await startTracking(
chainsInfoMock,
pksInfo,
trackingCallback,
TRACKING_INTERVAL_MINUTES
);
} catch (error) {
console.error('ā Monitoring failed:', error);
process.exit(1);
}
}
// Handle graceful shutdown
process.on('SIGINT', () => {
console.log('\nš Shutting down monitoring...');
if (trackingResults.length > 0) {
console.log(`š Final metrics from ${trackingResults.length} data points:`);
displayMetrics();
}
process.exit(0);
});
main().catch(console.error);
Production Monitoring with Slack Alerts
A production-ready example with Slack notifications and comprehensive alerting:
import {
startTracking,
generateMetrics,
processAlerts,
createAlertContext,
createSimpleNotificationCallback,
DEFAULT_ALERT_RULES,
TrackingResult,
InteropMetrics,
AlertNotification,
NotificationChannel
} from '@wakeuplabs/op-interop-alerts-sdk';
import { chainsInfoMock } from '@wakeuplabs/op-interop-alerts-sdk/config';
// Configuration
const pksInfo = {
origin: process.env.ORIGIN_PRIVATE_KEY as `0x${string}`,
destination: process.env.DESTINATION_PRIVATE_KEY as `0x${string}`,
};
const TRACKING_INTERVAL_MINUTES = parseInt(process.env.TRACKING_INTERVAL_MINUTES || "5");
const STATUS_REPORT_INTERVAL = 12; // Send status every 12 iterations (1 hour if 5min intervals)
// Slack configuration
const slackConfig = {
webhookUrl: process.env.SLACK_WEBHOOK_URL,
channel: process.env.SLACK_CHANNEL || '#interop-alerts',
username: process.env.SLACK_USERNAME || 'OP Interop Monitor',
iconEmoji: process.env.SLACK_ICON_EMOJI || ':zap:'
};
// Storage and counters
const trackingResults: TrackingResult[] = [];
let iterationCount = 0;
const startTime = Date.now();
// Slack notification helper
async function sendSlackMessage(
message: string,
color: 'good' | 'warning' | 'danger' = 'good'
): Promise<boolean> {
if (!slackConfig.webhookUrl) {
console.log('š± Slack not configured, logging message:', message);
return false;
}
try {
const payload = {
username: slackConfig.username,
icon_emoji: slackConfig.iconEmoji,
channel: slackConfig.channel,
attachments: [{
color,
text: message,
footer: 'OP Interop Alerts',
ts: Math.floor(Date.now() / 1000)
}]
};
const response = await fetch(slackConfig.webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
return response.ok;
} catch (error) {
console.error('ā Error sending Slack message:', error);
return false;
}
}
// Alert notification callback
const alertNotificationCallback = createSimpleNotificationCallback({
[NotificationChannel.SLACK]: async (notification: AlertNotification) => {
const { alert, rule, context } = notification;
console.log(`šØ ALERT TRIGGERED: [${alert.severity}] ${alert.title}`);
let slackMessage = `šØ *${alert.title}*\n`;
slackMessage += `*Severity:* ${alert.severity}\n`;
slackMessage += `*Category:* ${alert.category}\n`;
slackMessage += `*Message:* ${alert.message}\n`;
// Add key metrics
const metrics = context.metrics;
slackMessage += `\n*š Current Status:*\n`;
slackMessage += `⢠System: ${metrics.status.interopStatus} (${metrics.status.healthLevel})\n`;
slackMessage += `⢠Success Rate: ${metrics.coreMetrics.throughput.successRate.toFixed(1)}%\n`;
slackMessage += `⢠Avg Latency: ${(metrics.coreMetrics.latency.averageLatencyMs / 1000).toFixed(1)}s\n`;
const color = alert.severity === 'CRITICAL' ? 'danger' : 'warning';
const success = await sendSlackMessage(slackMessage, color);
if (success) {
console.log('ā
Alert sent to Slack');
} else {
console.log('ā Failed to send alert to Slack');
}
}
});
// Status report function
async function sendStatusReport(metrics: InteropMetrics, iteration: number) {
const uptime = Math.floor((Date.now() - startTime) / 1000 / 60); // minutes
let statusMessage = `ā
*OP Interop Status: Healthy*\n`;
statusMessage += `*Iteration:* ${iteration}\n`;
statusMessage += `*Uptime:* ${uptime} minutes\n\n`;
statusMessage += `*š Current Metrics:*\n`;
statusMessage += `⢠Status: ${metrics.status.interopStatus} (${metrics.status.healthLevel})\n`;
statusMessage += `⢠Success Rate: ${metrics.coreMetrics.throughput.successRate.toFixed(1)}%\n`;
statusMessage += `⢠Avg Latency: ${(metrics.coreMetrics.latency.averageLatencyMs / 1000).toFixed(1)}s\n`;
statusMessage += `⢠P95 Latency: ${(metrics.coreMetrics.latency.p95LatencyMs / 1000).toFixed(1)}s\n`;
statusMessage += `⢠Messages/Hour: ${metrics.coreMetrics.throughput.messagesPerHour.toFixed(1)}\n`;
statusMessage += `⢠Total Messages: ${metrics.coreMetrics.throughput.totalMessages}`;
const success = await sendSlackMessage(statusMessage, 'good');
console.log(success ? 'ā
Status report sent to Slack' : 'ā Failed to send status report');
}
// Alert processing function
async function processAlertsForMetrics(metrics: InteropMetrics, trackingData: TrackingResult[]) {
try {
const alertContext = createAlertContext(
metrics,
trackingData,
undefined,
60 * 60 * 1000 // 1 hour time window
);
const alertResults = await processAlerts(
DEFAULT_ALERT_RULES,
alertContext,
alertNotificationCallback
);
const triggeredAlerts = alertResults.filter(result => result.triggered);
console.log(`š Alert processing: ${alertResults.length} rules, ${triggeredAlerts.length} triggered`);
if (triggeredAlerts.length > 0) {
console.log('šØ Triggered alerts:');
triggeredAlerts.forEach((result, index) => {
console.log(` ${index + 1}. [${result.alert?.severity}] ${result.rule.name}`);
});
}
return triggeredAlerts.length === 0;
} catch (error) {
console.error('ā Error processing alerts:', error);
return false;
}
}
// Main tracking callback
const trackingCallback = async (result: TrackingResult) => {
console.log(`\n=== [${result.timestamp.toISOString()}] PRODUCTION MONITORING ===`);
if (result.success && result.data) {
const { sentMessage, relayMessage } = result.data;
const latency = relayMessage.localTimestamp.getTime() - sentMessage.localTimestamp.getTime();
console.log(`ā
Cross-chain operation successful (${(latency / 1000).toFixed(2)}s latency)`);
} else {
console.log(`ā Cross-chain operation failed: ${result.error?.error.message}`);
}
trackingResults.push(result);
// Generate metrics and process alerts
if (trackingResults.length >= 1) {
const metrics = generateMetrics(trackingResults);
console.log(`š System: ${metrics.status.interopStatus} | Health: ${metrics.status.healthLevel}`);
const systemHealthy = await processAlertsForMetrics(metrics, trackingResults);
// Send periodic status reports when system is healthy
if (systemHealthy) {
iterationCount++;
if (iterationCount === 1 || iterationCount % STATUS_REPORT_INTERVAL === 0) {
console.log(`š” Sending status report (iteration ${iterationCount})`);
await sendStatusReport(metrics, iterationCount);
}
} else {
console.log('ā ļø Status report skipped due to active alerts');
}
}
console.log('=== END MONITORING CYCLE ===\n');
};
// Startup notification
async function sendStartupNotification() {
const message = `š *OP Interop Monitor Started*\n` +
`*Interval:* ${TRACKING_INTERVAL_MINUTES} minutes\n` +
`*Origin:* Chain ${chainsInfoMock.chainOrigin.chainId}\n` +
`*Destination:* Chain ${chainsInfoMock.chainDestination.chainId}\n` +
`*Status Reports:* Every ${STATUS_REPORT_INTERVAL} cycles`;
await sendSlackMessage(message, 'good');
}
// Main function
async function main() {
console.log('š Starting OP Interop Production Monitoring');
console.log(`š Configuration:`);
console.log(` - Tracking interval: ${TRACKING_INTERVAL_MINUTES} minutes`);
console.log(` - Status reports: Every ${STATUS_REPORT_INTERVAL} cycles`);
console.log(` - Slack alerts: ${slackConfig.webhookUrl ? 'Enabled' : 'Disabled'}`);
console.log(` - Origin chain: ${chainsInfoMock.chainOrigin.chainId}`);
console.log(` - Destination chain: ${chainsInfoMock.chainDestination.chainId}\n`);
await sendStartupNotification();
try {
await startTracking(
chainsInfoMock,
pksInfo,
trackingCallback,
TRACKING_INTERVAL_MINUTES
);
} catch (error) {
console.error('ā Production monitoring failed:', error);
await sendSlackMessage(`šØ *Monitor Crashed*\n${error}`, 'danger');
process.exit(1);
}
}
// Graceful shutdown
process.on('SIGINT', async () => {
console.log('\nš Shutting down production monitor...');
await sendSlackMessage('š *OP Interop Monitor Stopped*', 'warning');
process.exit(0);
});
main().catch(console.error);
Custom Chain Configuration
Example showing how to monitor custom chains instead of the default mock configuration:
import {
startTracking,
generateMetrics,
TrackingResult,
ChainsInfo
} from '@wakeuplabs/op-interop-alerts-sdk';
// Custom chain configuration for OP Sepolia <-> Base Sepolia
const customChainsInfo: ChainsInfo = {
chainOrigin: {
chainId: 11155420, // OP Sepolia
rpcUrl: 'https://sepolia.optimism.io',
l2ToL2CrossDomainMessengerAddress: '0x4200000000000000000000000000000000000023',
messageReceiverAddress: '0x...' // Your deployed MessageReceiver contract
},
chainDestination: {
chainId: 84532, // Base Sepolia
rpcUrl: 'https://sepolia.base.org',
l2ToL2CrossDomainMessengerAddress: '0x4200000000000000000000000000000000000023',
messageReceiverAddress: '0x...' // Your deployed MessageReceiver contract
}
};
const pksInfo = {
origin: process.env.ORIGIN_PRIVATE_KEY as `0x${string}`,
destination: process.env.DESTINATION_PRIVATE_KEY as `0x${string}`,
};
const trackingResults: TrackingResult[] = [];
const trackingCallback = (result: TrackingResult) => {
console.log(`\n=== Custom Chains Monitoring ===`);
console.log(`Time: ${result.timestamp.toISOString()}`);
console.log(`OP Sepolia -> Base Sepolia: ${result.success ? 'ā
' : 'ā'}`);
if (result.success && result.data) {
const latency = result.data.relayMessage.localTimestamp.getTime() -
result.data.sentMessage.localTimestamp.getTime();
console.log(`Latency: ${(latency / 1000).toFixed(2)}s`);
}
trackingResults.push(result);
// Generate metrics every 5 results
if (trackingResults.length % 5 === 0) {
const metrics = generateMetrics(trackingResults);
console.log(`\nš Metrics Summary (last ${trackingResults.length} results):`);
console.log(` Success Rate: ${metrics.coreMetrics.throughput.successRate.toFixed(1)}%`);
console.log(` Avg Latency: ${(metrics.coreMetrics.latency.averageLatencyMs / 1000).toFixed(1)}s`);
console.log(` System Status: ${metrics.status.interopStatus}`);
}
};
async function main() {
console.log('š Starting Custom Chains Monitoring');
console.log(`š Monitoring: OP Sepolia (${customChainsInfo.chainOrigin.chainId}) -> Base Sepolia (${customChainsInfo.chainDestination.chainId})`);
try {
await startTracking(customChainsInfo, pksInfo, trackingCallback, 8);
} catch (error) {
console.error('ā Custom chains monitoring failed:', error);
}
}
main().catch(console.error);
Package Configuration
Example `package.json` and scripts for your monitoring project:
{
"name": "my-interop-monitor",
"version": "1.0.0",
"description": "Cross-chain monitoring for OP Superchain",
"main": "dist/index.js",
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc",
"start": "node dist/index.js",
"start:basic": "tsx src/basic-monitoring.ts",
"start:production": "tsx src/production-monitoring.ts",
"start:custom": "tsx src/custom-chains-monitoring.ts",
"test": "tsx src/test-installation.ts"
},
"dependencies": {
"@wakeuplabs/op-interop-alerts-sdk": "^0.3.0",
"dotenv": "^16.0.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"tsx": "^4.0.0",
"typescript": "^5.0.0"
},
"engines": {
"node": ">=18.0.0"
}
}
Running the Examples
Follow these steps to run the examples in your own project:
Step-by-step Setup
Create a new project
mkdir my-interop-monitor
cd my-interop-monitor
npm init -y
Install dependencies
npm install @wakeuplabs/op-interop-alerts-sdk dotenv
npm install -D @types/node tsx typescript
Set up environment
cp .env.example .env
# Edit .env with your private keys and Slack webhook
Copy and run examples
# Basic monitoring
npm run start:basic
# Production monitoring with Slack
npm run start:production
# Custom chains monitoring
npm run start:custom
Complete Example Repository
Find the complete working example with all the code shown above in our repository:
View Example CodeReady to Start Monitoring?
These examples provide a solid foundation for monitoring your cross-chain operations. Customize them based on your specific needs and infrastructure.