diff --git a/src/utils/alerts.js b/src/utils/alerts.js index 20fdfe9..dcd6008 100644 --- a/src/utils/alerts.js +++ b/src/utils/alerts.js @@ -1,12 +1,13 @@ import { metrics } from '../metrics.js'; import { endpoints } from "../endpoints.js"; -import { slackAlertWebhook, slackAlertHF, slackAlertRate, slackAlertPriceDelta } from '../config.js'; +import { slackAlertWebhooks, slackAlertHF, slackAlertRate, slackAlertPriceDelta } from '../config.js'; class Alerts { constructor() { this.activeAlerts = new Map(); this.alertHistory = []; this.alertConfigs = { + webhooks: [], hf: [], rate: [], priceDeltas: [] @@ -61,6 +62,10 @@ class Alerts { async loadConfiguration() { try { + if (slackAlertWebhooks) { + this.alertConfigs.webhooks = JSON.parse(slackAlertWebhooks); + } + if (slackAlertHF) { this.alertConfigs.hf = JSON.parse(slackAlertHF); } @@ -75,7 +80,7 @@ class Alerts { } console.log('Alert configuration loaded:', { - webhook: !!slackAlertWebhook, + webhook: this.alertConfigs.webhooks.length, hf: this.alertConfigs.hf.length, rate: this.alertConfigs.rate.length, priceDeltas: this.alertConfigs.priceDeltas.length @@ -104,18 +109,26 @@ class Alerts { return parseInt(value) * multipliers[unit]; } - async sendWebhook(message) { - if (!slackAlertWebhook) return false; - + async sendWebhooks(message) { + const results = await Promise.all(this.alertConfigs.webhooks.map(webhookUrl => this.sendWebhook(webhookUrl, message))); + const success = results.every(result => !!result); + + if (success) { + this.metrics.webhook_notifications_total.inc({ success: 'true' }); + } else { + this.metrics.webhook_notifications_total.inc({ success: 'false' }); + } + } + + async sendWebhook(webhookUrl, message) { try { - const response = await fetch(slackAlertWebhook, { + const response = await fetch(webhookUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: message }) }); const success = response.ok; - this.metrics.webhook_notifications_total.inc({ success: success.toString() }); if (!success) { console.error('Webhook notification failed:', response.status, response.statusText); @@ -124,7 +137,7 @@ class Alerts { return success; } catch (error) { console.error('Failed to send webhook notification:', error); - this.metrics.webhook_notifications_total.inc({ success: 'false' }); + return false; } } @@ -148,7 +161,7 @@ class Alerts { this.metrics.alert_triggers_total.inc({ type, state }); - await this.sendWebhook(`🚨 **ALERT TRIGGERED** - ${message}`); + await this.sendWebhooks(`🚨 **ALERT TRIGGERED** - ${message}`); this.addToHistory(type, key, state, message); @@ -161,7 +174,7 @@ class Alerts { this.metrics.alert_triggers_total.inc({ type, state }); - await this.sendWebhook(`✅ **ALERT RESOLVED** - ${message}`); + await this.sendWebhooks(`✅ **ALERT RESOLVED** - ${message}`); this.addToHistory(type, key, state, message); } @@ -222,6 +235,10 @@ class Alerts { } } + getPricePairs() { + return this.alertConfigs.priceDeltas.map(([pair]) => pair); + } + async checkPriceDelta(pair, currentPrice, timestamp) { if (!currentPrice) { return; diff --git a/src/utils/oracleprices.js b/src/utils/oracleprices.js index 0e6bb7d..3e16e81 100644 --- a/src/utils/oracleprices.js +++ b/src/utils/oracleprices.js @@ -137,6 +137,7 @@ export default class OraclePrices { // Get highest block number from the results const blockNumber = Math.max(...blockNumbers.map(num => parseInt(num))); + const alerts = getAlerts(); // Process each oracle price for (let i = 0; i < pairs.length; i++) { @@ -184,6 +185,8 @@ export default class OraclePrices { quoteAssetId }; + await this.checkPriceDelta(alerts, key, value, timestamp); + // Alert if divergence exceeds threshold await this.checkAndAlertDivergence(baseAssetId, quoteAssetId, value, spotPrice, divergence); } @@ -358,6 +361,9 @@ export default class OraclePrices { const start = performance.now(); const keys = Object.keys(this.prices); + const alerts = getAlerts(); + + const extraKeys = alerts.getPricePairs().filter(key => !keys.includes(key)); // Refresh all stored price pairs for (const key of keys) { @@ -382,13 +388,7 @@ export default class OraclePrices { updated, }; - try { - // Check for price delta alerts - const alerts = getAlerts(); - await alerts.checkPriceDelta(key, spotPrice, updated); - } catch (e) { - console.error(`Failed to check price delta for ${key}:`, e); - } + await this.checkPriceDelta(alerts, key, spotPrice, updated); // Check if we need to alert on divergence await this.checkAndAlertDivergence(data.baseAssetId, data.quoteAssetId, data.oraclePrice, spotPrice, divergence); @@ -399,6 +399,24 @@ export default class OraclePrices { } } + // Additional checks for price deltas alerts + for (const key of extraKeys) { + try { + const [baseAsset, quoteAsset] = key.split('/'); + const baseAssetId = this.getAssetIdFromSymbol(baseAsset); + const quoteAssetId = this.getAssetIdFromSymbol(quoteAsset); + + const spotPrice = await this.getSpotPrice(baseAssetId, quoteAssetId); + + if (spotPrice !== null) { + const updated = Date.now(); + await this.checkPriceDelta(alerts, key, spotPrice, updated); + } + } catch { + console.error(`Failed to get spot price for ${key}:`, e); + } + } + this.lastGlobalUpdate = blockNumber; this.metrics.last_global_update_block.set(blockNumber); this.metrics.last_update_block.set(blockNumber); @@ -406,4 +424,12 @@ export default class OraclePrices { console.log(`updated prices of ${keys.length} pairs in ${(performance.now() - start).toFixed(2)}ms`); } + + async checkPriceDelta(alerts, key, spotPrice, updated) { + try { + await alerts.checkPriceDelta(key, spotPrice, updated); + } catch (e) { + console.error(`Failed to check price delta for ${key}:`, e); + } + } }