From 0f65ea8cae505c6e5748fcc5fcbe792771daf9b1 Mon Sep 17 00:00:00 2001 From: Danswar <48102227+Danswar@users.noreply.github.com> Date: Tue, 9 Jun 2026 12:15:24 -0300 Subject: [PATCH] chore: remove noisy balance alerts and the dead Telegram integration (#198) * chore(monitoring): remove noisy balance threshold alerts Remove the BalanceAlertService cron and its threshold config, which posted frequent LOW/HIGH wallet balance alerts to Telegram. The thresholds rarely cleared, so the alerts were constant noise rather than actionable signals. Also drop the now-unused TelegramModule import from MonitoringModule (BalanceAlertService was its only consumer). * chore(telegram): remove unused Telegram integration With the balance alerts gone, TelegramService had no remaining callers and TelegramModule was imported nowhere. Remove the integration module, the service, and the now-dead telegram config block (TELEGRAM_BOT_TOKEN / TELEGRAM_CHAT_ID are no longer read). --- src/config/balance-thresholds.config.ts | 68 ---------- src/config/config.ts | 5 - .../telegram/services/telegram.service.ts | 50 ------- src/integration/telegram/telegram.module.ts | 10 -- .../monitoring/monitoring.module.ts | 4 - .../services/balance-alert.service.ts | 123 ------------------ 6 files changed, 260 deletions(-) delete mode 100644 src/config/balance-thresholds.config.ts delete mode 100644 src/integration/telegram/services/telegram.service.ts delete mode 100644 src/integration/telegram/telegram.module.ts delete mode 100644 src/subdomains/monitoring/services/balance-alert.service.ts diff --git a/src/config/balance-thresholds.config.ts b/src/config/balance-thresholds.config.ts deleted file mode 100644 index 327527944..000000000 --- a/src/config/balance-thresholds.config.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { Blockchain } from 'src/shared/enums/blockchain.enum'; -import { Direction } from 'src/subdomains/boltz/dto/boltz.dto'; - -export interface BalanceThreshold { - blockchain: Blockchain; - asset: string; - minBalance: number; - maxBalance: number; - direction?: Direction; -} - -const PRD_BALANCE_THRESHOLDS: BalanceThreshold[] = [ - // Bitcoin - { blockchain: Blockchain.BITCOIN, asset: 'BTC', minBalance: 0.1, maxBalance: 1 }, - - // Lightning (Onchain, Outgoing and Incoming Channels) - { blockchain: Blockchain.LIGHTNING, asset: 'BTC', minBalance: 0.1, maxBalance: 1 }, - { blockchain: Blockchain.LIGHTNING, asset: 'BTC', minBalance: 1, maxBalance: 5, direction: Direction.OUTGOING }, - { blockchain: Blockchain.LIGHTNING, asset: 'BTC', minBalance: 1, maxBalance: 5, direction: Direction.INCOMING }, - - // Citrea - { blockchain: Blockchain.CITREA, asset: 'cBTC', minBalance: 0.1, maxBalance: 1 }, - { blockchain: Blockchain.CITREA, asset: 'JUSD', minBalance: 1000, maxBalance: 100000 }, - - // Ethereum - { blockchain: Blockchain.ETHEREUM, asset: 'ETH', minBalance: 0.001, maxBalance: 0.1 }, - { blockchain: Blockchain.ETHEREUM, asset: 'USDC', minBalance: 1000, maxBalance: 100000 }, - { blockchain: Blockchain.ETHEREUM, asset: 'USDT', minBalance: 1000, maxBalance: 100000 }, - { blockchain: Blockchain.ETHEREUM, asset: 'WBTC', minBalance: 0, maxBalance: 1 }, - - // Polygon - { blockchain: Blockchain.POLYGON, asset: 'POL', minBalance: 1, maxBalance: 100 }, - { blockchain: Blockchain.POLYGON, asset: 'USDT', minBalance: 1000, maxBalance: 100000 }, -]; - -// DEV wallets carry test funds (single-digit USDT, milli-cBTC), so the PRD -// minimums fire every cron tick. Lower minBalance below the DEV funding -// levels so no LOW alert fires; maxBalance unchanged. -// -// Keep this list in sync with PRD_BALANCE_THRESHOLDS above — adding a new -// asset to PRD without adding it here means the new asset's alerting path -// is never exercised on DEV before it hits production. -const DEV_BALANCE_THRESHOLDS: BalanceThreshold[] = [ - // Bitcoin - { blockchain: Blockchain.BITCOIN, asset: 'BTC', minBalance: 0, maxBalance: 1 }, - - // Lightning (Onchain, Outgoing and Incoming Channels) - { blockchain: Blockchain.LIGHTNING, asset: 'BTC', minBalance: 0, maxBalance: 1 }, - { blockchain: Blockchain.LIGHTNING, asset: 'BTC', minBalance: 0, maxBalance: 5, direction: Direction.OUTGOING }, - { blockchain: Blockchain.LIGHTNING, asset: 'BTC', minBalance: 0, maxBalance: 5, direction: Direction.INCOMING }, - - // Citrea - { blockchain: Blockchain.CITREA, asset: 'cBTC', minBalance: 0, maxBalance: 1 }, - { blockchain: Blockchain.CITREA, asset: 'JUSD', minBalance: 0, maxBalance: 100000 }, - - // Ethereum - { blockchain: Blockchain.ETHEREUM, asset: 'ETH', minBalance: 0, maxBalance: 0.1 }, - { blockchain: Blockchain.ETHEREUM, asset: 'USDC', minBalance: 0, maxBalance: 100000 }, - { blockchain: Blockchain.ETHEREUM, asset: 'USDT', minBalance: 0, maxBalance: 100000 }, - { blockchain: Blockchain.ETHEREUM, asset: 'WBTC', minBalance: 0, maxBalance: 1 }, - - // Polygon - { blockchain: Blockchain.POLYGON, asset: 'POL', minBalance: 0, maxBalance: 100 }, - { blockchain: Blockchain.POLYGON, asset: 'USDT', minBalance: 0, maxBalance: 100000 }, -]; - -export const BALANCE_THRESHOLDS: BalanceThreshold[] = - process.env.ENVIRONMENT === 'prd' ? PRD_BALANCE_THRESHOLDS : DEV_BALANCE_THRESHOLDS; diff --git a/src/config/config.ts b/src/config/config.ts index 39759bc9d..f8626ed30 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -223,11 +223,6 @@ export class Configuration { evmWalletAddress: process.env.BOLTZ_WALLET_ADDRESS ?? '', }; - telegram = { - botToken: process.env.TELEGRAM_BOT_TOKEN ?? '', - chatId: process.env.TELEGRAM_CHAT_ID ?? '', - }; - // --- GETTERS --- // get baseUrl(): string { return this.environment === Environment.LOC diff --git a/src/integration/telegram/services/telegram.service.ts b/src/integration/telegram/services/telegram.service.ts deleted file mode 100644 index 5d591b31b..000000000 --- a/src/integration/telegram/services/telegram.service.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { Config } from 'src/config/config'; -import { HttpService } from 'src/shared/services/http.service'; -import { LightningLogger } from 'src/shared/services/lightning-logger'; - -interface TelegramSendMessageResponse { - ok: boolean; - result?: { - message_id: number; - }; - description?: string; -} - -@Injectable() -export class TelegramService { - private readonly logger = new LightningLogger(TelegramService); - private readonly baseUrl = 'https://api.telegram.org'; - - constructor(private readonly httpService: HttpService) {} - - async sendMessage(message: string): Promise { - if (!Config.telegram.botToken || !Config.telegram.chatId) { - this.logger.info('Telegram not configured, skipping message'); - return false; - } - - try { - const url = `${this.baseUrl}/bot${Config.telegram.botToken}/sendMessage`; - const response = await this.httpService.post( - url, - { - chat_id: Config.telegram.chatId, - text: message, - parse_mode: 'HTML', - }, - { tryCount: 5, retryDelay: 2000 }, - ); - - if (!response.ok) { - this.logger.error(`Telegram API error: ${response.description}`); - return false; - } - - return true; - } catch (e) { - this.logger.error('Failed to send Telegram message', e); - return false; - } - } -} diff --git a/src/integration/telegram/telegram.module.ts b/src/integration/telegram/telegram.module.ts deleted file mode 100644 index ca05fc8c3..000000000 --- a/src/integration/telegram/telegram.module.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Module } from '@nestjs/common'; -import { SharedModule } from 'src/shared/shared.module'; -import { TelegramService } from './services/telegram.service'; - -@Module({ - imports: [SharedModule], - providers: [TelegramService], - exports: [TelegramService], -}) -export class TelegramModule {} diff --git a/src/subdomains/monitoring/monitoring.module.ts b/src/subdomains/monitoring/monitoring.module.ts index 0a688eef7..09d0954c2 100644 --- a/src/subdomains/monitoring/monitoring.module.ts +++ b/src/subdomains/monitoring/monitoring.module.ts @@ -2,7 +2,6 @@ import { forwardRef, Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { BlockchainModule } from 'src/integration/blockchain/blockchain.module'; import { EvmRegistryModule } from 'src/integration/blockchain/shared/evm/registry/evm-registry.module'; -import { TelegramModule } from 'src/integration/telegram/telegram.module'; import { SharedModule } from 'src/shared/shared.module'; import { AlchemyWebhookModule } from '../alchemy/alchemy-webhook.module'; import { BoltzModule } from '../boltz/boltz.module'; @@ -16,7 +15,6 @@ import { MonitoringEntity } from './entities/monitoring.entity'; import { MonitoringBalanceRepository } from './repositories/monitoring-balance.repository'; import { MonitoringEvmBalanceRepository } from './repositories/monitoring-evm-balance.repository'; import { MonitoringRepository } from './repositories/monitoring.repository'; -import { BalanceAlertService } from './services/balance-alert.service'; import { MonitoringEvmService } from './services/monitoring-evm.service'; import { MonitoringService } from './services/monitoring.service'; @@ -31,7 +29,6 @@ import { MonitoringService } from './services/monitoring.service'; EvmRegistryModule, BoltzModule, AlchemyWebhookModule, - TelegramModule, ], controllers: [MonitoringController], providers: [ @@ -40,7 +37,6 @@ import { MonitoringService } from './services/monitoring.service'; MonitoringEvmBalanceRepository, MonitoringService, MonitoringEvmService, - BalanceAlertService, ], exports: [MonitoringService, MonitoringEvmService], }) diff --git a/src/subdomains/monitoring/services/balance-alert.service.ts b/src/subdomains/monitoring/services/balance-alert.service.ts deleted file mode 100644 index 7fbdbac2b..000000000 --- a/src/subdomains/monitoring/services/balance-alert.service.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { Cron, CronExpression } from '@nestjs/schedule'; -import { BALANCE_THRESHOLDS, BalanceThreshold } from 'src/config/balance-thresholds.config'; -import { Config, Process } from 'src/config/config'; -import { TelegramService } from 'src/integration/telegram/services/telegram.service'; -import { LightningLogger } from 'src/shared/services/lightning-logger'; -import { Lock } from 'src/shared/utils/lock'; -import { BalanceDto } from 'src/subdomains/boltz/dto/boltz.dto'; -import { BoltzBalanceService } from 'src/subdomains/boltz/services/boltz-balance.service'; - -enum AlertType { - LOW = 'LOW', - HIGH = 'HIGH', -} - -@Injectable() -export class BalanceAlertService { - private readonly logger = new LightningLogger(BalanceAlertService); - - private readonly alertState = new Map(); - - constructor( - private readonly telegramService: TelegramService, - private readonly boltzBalanceService: BoltzBalanceService, - ) {} - - @Cron(CronExpression.EVERY_5_MINUTES) - @Lock(1800) - async processBalanceAlerts(): Promise { - if (Config.processDisabled(Process.MONITORING)) return; - - try { - const balances = await this.boltzBalanceService.getWalletBalance(); - await this.checkAndAlert(balances); - } catch (e) { - this.logger.error('Error processing balance alerts', e); - } - } - - async checkAndAlert(balances: BalanceDto[]): Promise { - for (const threshold of BALANCE_THRESHOLDS) { - const balance = balances.find( - (b) => - b.blockchain === threshold.blockchain && - b.asset === threshold.asset && - b.direction === threshold.direction, - ); - - if (!balance) continue; - - await this.checkThreshold(threshold, balance.balance, AlertType.LOW); - await this.checkThreshold(threshold, balance.balance, AlertType.HIGH); - } - } - - private async checkThreshold( - threshold: BalanceThreshold, - balance: number, - alertType: AlertType, - ): Promise { - const directionSuffix = threshold.direction ? `:${threshold.direction}` : ''; - const key = `${threshold.blockchain}:${threshold.asset}${directionSuffix}:${alertType}`; - const isActive = this.alertState.get(key) ?? false; - - const thresholdValue = alertType === AlertType.LOW ? threshold.minBalance : threshold.maxBalance; - const isViolation = - alertType === AlertType.LOW ? balance < thresholdValue : balance > thresholdValue; - const isRecovered = - alertType === AlertType.LOW ? balance >= thresholdValue : balance <= thresholdValue; - - if (isViolation && !isActive) { - const sent = await this.sendBalanceAlert(alertType, threshold, balance, thresholdValue); - - if (sent) { - this.alertState.set(key, true); - this.logger.info(`Sent ${alertType} alert for ${threshold.asset} on ${threshold.blockchain}`); - } - } else if (isRecovered && isActive) { - await this.sendRecoveryMessage(threshold, balance); - this.alertState.set(key, false); - this.logger.info(`Cleared ${alertType} alert for ${threshold.asset} on ${threshold.blockchain}`); - } - } - - private async sendBalanceAlert( - alertType: AlertType, - threshold: BalanceThreshold, - balance: number, - thresholdValue: number, - ): Promise { - const description = - alertType === AlertType.LOW - ? 'Balance is below minimum threshold!' - : 'Balance exceeds maximum threshold!'; - - const directionInfo = threshold.direction ? ` (${threshold.direction} channels)` : ''; - - return this.telegramService.sendMessage(` -🔴 Balance Alert: ${alertType} - -Asset: ${threshold.asset} -Chain: ${threshold.blockchain}${directionInfo} -Balance: ${balance.toFixed(6)} -Threshold: ${thresholdValue.toFixed(6)} - -${description} -`); - } - - private async sendRecoveryMessage(threshold: BalanceThreshold, balance: number): Promise { - const directionInfo = threshold.direction ? ` (${threshold.direction} channels)` : ''; - - await this.telegramService.sendMessage(` -✅ Balance Recovered - -Asset: ${threshold.asset} -Chain: ${threshold.blockchain}${directionInfo} -Balance: ${balance.toFixed(6)} - -Balance is back within normal range. -`); - } -}