From dd454707473035986e8ebcab7b2cf06997c36eda Mon Sep 17 00:00:00 2001 From: Roman Yarlykov Date: Wed, 27 Aug 2025 16:28:40 +0500 Subject: [PATCH] fix: operator slack alerting --- src/managers/RelayerBalanceManager.ts | 60 +++++++++++++++++++++++++++ src/utils/initializeManagers.ts | 3 ++ 2 files changed, 63 insertions(+) diff --git a/src/managers/RelayerBalanceManager.ts b/src/managers/RelayerBalanceManager.ts index 8653ffc..54d1f15 100644 --- a/src/managers/RelayerBalanceManager.ts +++ b/src/managers/RelayerBalanceManager.ts @@ -6,15 +6,23 @@ import type { IViemClientManager, LoggerInterface, } from '@concero/operator-utils'; +import { WebClient } from '@slack/web-api'; +import { formatUnits } from 'viem'; interface RelayerBalanceManagerConfig extends BalanceManagerConfig { defaultMinBalance: bigint; minBalances?: Record; + slackChannelId: string; + slackBotToken: string; + slackIntervalMs: number; } export class RelayerBalanceManager extends BalanceManager { private readonly defaultMinBalance: bigint; private readonly minBalances: Record; + private readonly slackChannelId: string; + private readonly slackBotToken: string; + private readonly slackIntervalMs: number; private constructor( logger: LoggerInterface, @@ -25,6 +33,9 @@ export class RelayerBalanceManager extends BalanceManager { super(logger, viemClientManager, txReader, config); this.defaultMinBalance = config.defaultMinBalance; this.minBalances = config.minBalances ?? {}; + this.slackChannelId = config.slackChannelId || ''; + this.slackBotToken = config.slackBotToken || ''; + this.slackIntervalMs = config.slackIntervalMs || 60 * 60 * 1000; } public static createInstance( @@ -46,6 +57,9 @@ export class RelayerBalanceManager extends BalanceManager { // Wait for initial balances to be populated before starting watchers await this.initializeBalances(); this.beginWatching(); + + await this.checkAndNotifyInsufficientBalance(); + this.startPeriodicBalanceChecks(); } /** @@ -89,4 +103,50 @@ export class RelayerBalanceManager extends BalanceManager { private registerNativeTokenWatch(network: ConceroNetwork): void { this.registerToken(network, 'NATIVE', '0x0000000000000000000000000000000000000000' as any); } + + private startPeriodicBalanceChecks(): void { + setInterval(async () => { + try { + await this.checkAndNotifyInsufficientBalance(); + } catch (error) { + this.logger.error('Error during periodic balance check:', error); + } + }, this.slackIntervalMs); + } + + private async checkAndNotifyInsufficientBalance() { + for (const network of this.activeNetworks) { + const currentBalance = this.getNativeBalance(network.name); + const minRequired = this.getMinBalanceForNetwork(network.name); + + if (currentBalance < minRequired) { + const message = `RelayerBalanceManager: \n Insufficient gas on ${network.name} (chain ID: ${network.id}). \n Minimum required: ${formatUnits(minRequired, 18)}, \n Actual balance: ${formatUnits(currentBalance, 18)}`; + + await this.notifyInSlackAboutMinBalance(message); + } + } + } + + private async notifyInSlackAboutMinBalance(message: string) { + try { + if (!this.slackChannelId || !this.slackBotToken) { + this.logger.warn('Slack channel ID or bot token is not set'); + return; + } + const webClient = new WebClient(this.slackBotToken); + + const res = await webClient.chat.postMessage({ + channel: this.slackChannelId, + text: message, + }); + + if (!res.ok) { + this.logger.error(`Failed to send message to slack: ${res.error}`); + } else { + this.logger.debug(`Slack notification sent successfully: ${message}`); + } + } catch (error) { + this.logger.error('Error sending Slack notification:', error); + } + } } diff --git a/src/utils/initializeManagers.ts b/src/utils/initializeManagers.ts index 5bef88c..e2dd9ed 100644 --- a/src/utils/initializeManagers.ts +++ b/src/utils/initializeManagers.ts @@ -166,6 +166,9 @@ export async function initializeManagers(): Promise { defaultMinBalance: globalConfig.BALANCE_MANAGER.DEFAULT_MIN_BALANCE, minBalances: globalConfig.BALANCE_MANAGER.MIN_BALANCES, pollingIntervalMs: globalConfig.BALANCE_MANAGER.POLLING_INTERVAL_MS, + slackChannelId: globalConfig.NOTIFICATIONS.SLACK.MONITORING_SYSTEM_CHANNEL_ID || '', + slackBotToken: globalConfig.NOTIFICATIONS.SLACK.BOT_TOKEN || '', + slackIntervalMs: globalConfig.NOTIFICATIONS.INTERVAL, }, );