Розробка системи алертів на on-chain события
Моніторинг on-chain в реальному часі — завдання, яка здається простою до моменту реалізації. Підписався на события контракту через WebSocket, отримав лог — відправив повідомлення. На практиці: WebSocket обривається, ноди лагають, одна подія приходить двічі, важливе повідомлення йде в спам. Нормальна система алертів — це не «обертка над getLogs», це окремий інфраструктурний проект з чергами, дедупликацією та механізмами доставки.
Типи подій та джерела
Contract events (logs) — найпоширеніші: Transfer, Swap, Deposit, Liquidation, Mint. Декодуються через ABI з raw topics + data.
Large transactions — трансфери вище порогового значення в USD. Потребує конвертації через price feed (Chainlink, CoinGecko API) на момент события.
Address activity — будь-яка транзакція з/на відслідковуваний адрес (whale watching, моніторинг портфеля).
MEV events — sandwich attacks, arbitrage, flashloans. Детектуються через аналіз паттернів у рамках одного блоку.
Protocol health — health factor на Aave/Compound нижче порога, utilization rate вище 90%, падання TVL більше X%.
NFT события — mint, sale, transfer конкретних колекцій.
Архітектура
Ingestion layer
Два варіанти отримання подій:
WebSocket subscriptions — мінімальна latency (< 1 сек від блоку). Проблема: при реконнекті пропускаються блоки. Потребує catch-up механізму:
class BlockListener {
private lastProcessedBlock: number;
async start(wsUrl: string) {
// Спочатку обробляємо пропущені блоки
const current = await this.rpc.getBlockNumber();
for (let b = this.lastProcessedBlock + 1; b <= current; b++) {
await this.processBlock(b);
}
// Підписуємось на нові
const wsClient = createPublicClient({
transport: webSocket(wsUrl),
});
wsClient.watchBlockNumber({
onBlockNumber: async (blockNum) => {
await this.processBlock(Number(blockNum));
this.lastProcessedBlock = Number(blockNum);
},
});
}
}
Polling — кожні N секунд робимо eth_getLogs за останні блоки. Менш ефективно, але надійніше. Для систем з SLA — комбінація: WS для швидкості, polling як fallback.
Event processing pipeline
[WS / Polling] → [Raw Event Queue] → [Decoder] → [Enricher] → [Rule Engine] → [Alert Queue] → [Delivery]
Decoder — ABI-декодинг raw logs. Для невідомих контрактів — спроба знайти ABI через Etherscan API або 4byte.directory.
Enricher — обогащення даних: USD-вартість через price feed, labels (біржа? whale? відомий протокол?), entity resolution (кілька адрес одного суб'єкту).
Rule Engine — перевірка умов алертів проти обогащеної события.
Rule Engine
Гнучка система правил — ключовий компонент. Правила повинні бути конфігурованими без деплоя кода:
interface AlertRule {
id: string;
name: string;
enabled: boolean;
// Фільтри джерела
chains: string[]; // ['ethereum', 'arbitrum']
contracts?: string[]; // адреси контрактів
event_signatures?: string[]; // keccak256 сигнатури
// Умови
conditions: Condition[];
condition_logic: 'AND' | 'OR';
// Доставка
channels: DeliveryChannel[];
cooldown_seconds: number; // anti-spam
}
interface Condition {
field: string; // 'value_usd', 'from_label', 'health_factor'
operator: 'gt' | 'lt' | 'eq' | 'contains' | 'in';
value: any;
}
Приклад правила для whale alert:
{
"name": "Large ETH Transfer",
"chains": ["ethereum"],
"event_signatures": ["0xddf252ad..."],
"conditions": [
{ "field": "value_usd", "operator": "gt", "value": 1000000 },
{ "field": "token_symbol", "operator": "eq", "value": "ETH" }
],
"condition_logic": "AND",
"channels": ["telegram_main", "webhook_trading_desk"],
"cooldown_seconds": 60
}
Дедупликація
При кількох нодах або при catch-up одна подія може прийти кілька разів. Дедупликація по (transaction_hash, log_index) в Redis:
async function processEvent(event: DecodedEvent): Promise<boolean> {
const key = `processed:${event.txHash}:${event.logIndex}`;
const isNew = await redis.set(key, '1', 'EX', 86400, 'NX');
// NX = тільки якщо не існує, EX = TTL 24 години
return isNew !== null;
}
Канали доставки
Telegram — найпопулярніший для крипто-алертів. Telegram Bot API, форматування через MarkdownV2:
async function sendTelegramAlert(
botToken: string,
chatId: string,
event: EnrichedEvent,
) {
const text = formatAlertMessage(event);
await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, {
method: 'POST',
body: JSON.stringify({
chat_id: chatId,
text,
parse_mode: 'MarkdownV2',
disable_web_page_preview: true,
}),
});
}
Важливо: Telegram має rate limit — 30 повідомлень/сек на бота, 1 повідомлення/сек у один чат. При високочастотних событіях потребує батчизації або агрегації.
Webhook — HTTP POST на довільний endpoint. Використовується для інтеграції з торговими ботами, внутрішніми системами. Повинен мати retry з exponential backoff.
Email — для важливих событій з низькою частотою. SendGrid / AWS SES.
Discord — через webhook або Discord Bot API.
PagerDuty — для критичних подій що вимагають негайної реакції (наприклад, ліквідація позиції фонду).
Anti-spam та агрегація
Проблема: при flash crash або крупному событі генеруються сотні алертів за хвилину. Рішення:
- Cooldown per rule — не слати повторний алерт по тому самому правилу N секунд
- Digest mode — агрегувати события за 5/60 хвилин та відправляти сводку
- Threshold batching — «відбулося 47 ліквідацій на Aave за останні 10 хвилин, загальна сума $2.3M»
Моніторинг самої системи
Система алертів повинна мониторити себе:
- Block lag — відставання від head chain. Алерт якщо > 10 блоків
- Processing queue depth — зростання черги = вузьке місце
- Delivery failures — невдалі спроби відправки в кожен канал
- Rule match rate — аномальний зріст = можливо правило занадто широке
# Prometheus метрики
alert_system_block_lag_gauge
alert_system_queue_depth_gauge
alert_system_delivery_total{channel, status}
alert_system_rule_matches_total{rule_id}
Стек та сроки
| Компонент | Технологія |
|---|---|
| Ingestion | TypeScript + viem / ethers.js |
| Queue | Redis Streams / BullMQ |
| Enrichment | Chainlink price feeds, Etherscan Labels API |
| Rule engine | JSON-конфігурованний, зберігання в PostgreSQL |
| Delivery | Telegram Bot, webhooks, Discord |
| Monitoring | Prometheus + Grafana |
Базова система (5-10 типів подій, Telegram + webhook, одна мережа): 2-3 тижні. Мультичейн, складні складові правила, кастомний UI для управління алертами — 4-6 тижнів.







