Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 27 additions & 10 deletions src/utils/alerts.js
Original file line number Diff line number Diff line change
@@ -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: []
Expand Down Expand Up @@ -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);
}
Expand All @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -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;
}
}
Expand All @@ -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);

Expand All @@ -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);
}
Expand Down Expand Up @@ -222,6 +235,10 @@ class Alerts {
}
}

getPricePairs() {
return this.alertConfigs.priceDeltas.map(([pair]) => pair);
}

async checkPriceDelta(pair, currentPrice, timestamp) {
if (!currentPrice) {
return;
Expand Down
40 changes: 33 additions & 7 deletions src/utils/oracleprices.js
Original file line number Diff line number Diff line change
Expand Up @@ -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++) {
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
Expand All @@ -399,11 +399,37 @@ 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);
this.byDivergence.clear();

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);
}
}
}