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:

basic-monitoring.ts
typescript
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:

production-monitoring.ts
typescript
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:

custom-chains-monitoring.ts
typescript
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:

package.json
json
{
  "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

1

Create a new project

mkdir my-interop-monitor
cd my-interop-monitor
npm init -y
2

Install dependencies

npm install @wakeuplabs/op-interop-alerts-sdk dotenv
npm install -D @types/node tsx typescript
3

Set up environment

cp .env.example .env
# Edit .env with your private keys and Slack webhook
4

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 Code

Ready to Start Monitoring?

These examples provide a solid foundation for monitoring your cross-chain operations. Customize them based on your specific needs and infrastructure.