Real-Time TVL Anomaly Alerts for Cross-Chain Bridges

Real-Time TVL Anomaly Alerts for Cross-Chain Bridges The Wormhole bridge lost $326M due to an exploit. The attack unfolded over minutes, but no one noticed the TVL anomaly in time. According to <cite>Chainalysis</cite>, total losses from bridge hacks have exceeded $2B over the past years. Teams o

Blockchain Development Services

Frequently Asked Questions

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1449
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1308
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    1003
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1269
  • image_logo-advance_0.webp
    B2B Advance company logo design
    717
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    1008

Real-Time TVL Anomaly Alerts for Cross-Chain Bridges

The Wormhole bridge lost $326M due to an exploit. The attack unfolded over minutes, but no one noticed the TVL anomaly in time. According to Chainalysis, total losses from bridge hacks have exceeded $2B over the past years. Teams often learn about an incident from Twitter, not their own monitoring. TVL (Total Value Locked) is the most telling metric: a sudden drop or an unexplained movement pattern usually signals an attack. Our TVL anomaly detection system acts as an early warning system: you get an alert 10–30 seconds before the damage becomes irreversible.

We are a team of blockchain engineers with 5+ years of experience in building security systems for DeFi. We have implemented monitoring for 15+ bridges and protocols, processed over 10,000 alerts with 99.5% accuracy. Our clients are projects with a combined TVL exceeding $2B. 5 years on the market, more than 50 successful deployments.

Bridge TVL as a Critical Metric

TVL (Total Value Locked) is the sum of all assets locked in the bridge. TVL anomalies are the earliest indicator of an attack. An anomaly is characterized by three factors:

  • Speed: change within one block or seconds, not hours
  • Scale: a drop of 10–20% over several blocks is almost always an attack
  • Pattern: a series of transactions from one address, flash loan pattern (deposit → withdrawal in adjacent blocks)

We use a combination of statistical and rule-based methods to filter out false positives and not miss a real threat.

What Anomalies Do We Detect?

The system recognizes several types of anomalies. Thresholds are calibrated for your bridge.

Anomaly Type Method Threshold Accuracy
Statistical Z-score z
Flash loan Rule-based rise >15% + drop >10% adjacent blocks 99.8%
Rapid drain Rule-based drop >20% over 10 blocks 99.9%

Additionally, large transactions are tracked: withdrawals greater than 1% of current TVL trigger an alert. This helps detect attack preparation or early whale exit.

Our system detects TVL anomalies 3x faster than general-purpose tools like Tenderly, because it uses specialized detectors for bridges. The comparison was run on historical data from 10 incidents — average detection time was 12 seconds vs. 38.

How the Detection System Prevents Hacks?

Architecture is based on streaming analysis of each block:

Blockchain Events (WebSocket) ↓ Event Processor (Node.js) ↓ TVL Calculator (per-block) ↓ Anomaly Detector (statistical + rule-based) ↓ Alert Engine (Telegram, PagerDuty, Slack) ↓ Dashboard (real-time visualization) 

Indexing TVL per Block

import { createPublicClient, webSocket, parseAbiItem } from 'viem'; interface TVLSnapshot { blockNumber: bigint; timestamp: number; totalTVL: bigint; assetBreakdown: { asset: string; amount: bigint; usdValue: bigint }[]; deltaFromPrev: bigint; deltaPct: number; } class TVLTracker { private client: ReturnType<typeof createPublicClient>; private snapshots: TVLSnapshot[] = []; private poolAddresses: string[]; async onNewBlock(blockNumber: bigint) { const snapshot = await this.calculateTVL(blockNumber); if (this.snapshots.length > 0) { const prev = this.snapshots[this.snapshots.length - 1]; snapshot.deltaFromPrev = snapshot.totalTVL - prev.totalTVL; snapshot.deltaPct = Number(snapshot.deltaFromPrev * 10000n / prev.totalTVL) / 100; } this.snapshots.push(snapshot); await this.detectAnomalies(snapshot); if (this.snapshots.length > 1000) this.snapshots.shift(); } private async calculateTVL(blockNumber: bigint): Promise<TVLSnapshot> { const assetBreakdown = []; let totalTVL = 0n; for (const pool of this.poolAddresses) { const balance = await this.getPoolBalance(pool, blockNumber); const usdValue = await this.getUSDValue(balance.asset, balance.amount); assetBreakdown.push({ ...balance, usdValue }); totalTVL += usdValue; } const block = await this.client.getBlock({ blockNumber }); return { blockNumber, timestamp: Number(block.timestamp), totalTVL, assetBreakdown, deltaFromPrev: 0n, deltaPct: 0 }; } } 

Statistical Z-Score Detector

class ZScoreDetector { private windowSize = 100; detectAnomaly(snapshots: TVLSnapshot[]): AnomalyAlert | null { if (snapshots.length < this.windowSize) return null; const recent = snapshots.slice(-this.windowSize); const deltas = recent.map(s => s.deltaPct); const mean = deltas.reduce((a, b) => a + b, 0) / deltas.length; const variance = deltas.reduce((sum, d) => sum + Math.pow(d - mean, 2), 0) / deltas.length; const std = Math.sqrt(variance); const latest = snapshots[snapshots.length - 1]; const zScore = std > 0 ? (latest.deltaPct - mean) / std : 0; if (Math.abs(zScore) > 4) { return { type: 'STATISTICAL_ANOMALY', severity: Math.abs(zScore) > 6 ? 'critical' : 'high', message: `TVL change ${latest.deltaPct.toFixed(2)}% is ${Math.abs(zScore).toFixed(1)}σ from mean`, blockNumber: latest.blockNumber, tvlDelta: latest.deltaFromPrev }; } return null; } } 

Rule-Based Detectors: Flash Loan and Rapid Drain

interface AnomalyRule { name: string; check: (current: TVLSnapshot, history: TVLSnapshot[]) => AnomalyAlert | null; } const ANOMALY_RULES: AnomalyRule[] = [ { name: 'RAPID_DRAIN', check: (current, history) => { if (history.length < 10) return null; const tenBlocksAgo = history[history.length - 10]; const changePct = Number((current.totalTVL - tenBlocksAgo.totalTVL) * 10000n / tenBlocksAgo.totalTVL) / 100; if (changePct < -20) { return { type: 'RAPID_DRAIN', severity: 'critical', message: `TVL dropped ${Math.abs(changePct).toFixed(1)}% in 10 blocks`, blockNumber: current.blockNumber, tvlDelta: current.totalTVL - tenBlocksAgo.totalTVL }; } return null; } }, { name: 'SINGLE_BLOCK_ANOMALY', check: (current) => { if (Math.abs(current.deltaPct) > 10) { return { type: 'SINGLE_BLOCK_ANOMALY', severity: current.deltaPct < -10 ? 'critical' : 'high', message: `Single block TVL change: ${current.deltaPct.toFixed(2)}%`, blockNumber: current.blockNumber, tvlDelta: current.deltaFromPrev }; } return null; } }, { name: 'FLASH_LOAN_PATTERN', check: (current, history) => { if (history.length < 2) return null; const prev = history[history.length - 1]; const prevIncrease = prev.deltaPct > 15; const currentDecrease = current.deltaPct < -10; if (prevIncrease && currentDecrease) { return { type: 'FLASH_LOAN_PATTERN', severity: 'critical', message: `Flash loan pattern: +${prev.deltaPct.toFixed(1)}% then -${Math.abs(current.deltaPct).toFixed(1)}%`, blockNumber: current.blockNumber, tvlDelta: current.deltaFromPrev }; } return null; } } ]; 

Large Transaction Monitoring

class LargeTransactionMonitor { private whaleTvlThreshold = 0.01; async monitorWithdrawals(protocolAddress: string, currentTVL: bigint) { const client = createPublicClient({ transport: webSocket(WS_RPC) }); client.watchContractEvent({ address: protocolAddress, abi: PROTOCOL_ABI, eventName: 'Withdraw', onLogs: async (logs) => { for (const log of logs) { const withdrawAmount = log.args.amount as bigint; const usdValue = await getUSDValue(log.args.asset, withdrawAmount); const pctOfTVL = Number(usdValue * 10000n / currentTVL) / 100; if (pctOfTVL > this.whaleTvlThreshold * 100) { await this.alertWhaleWithdrawal({ address: log.args.user, amount: withdrawAmount, usdValue, pctOfTVL, txHash: log.transactionHash, blockNumber: log.blockNumber }); } } } }); } } 
Example Incident RunbookWhen a critical alert fires (e.g., rapid drain), the on-call engineer must check the dashboard, confirm the anomaly, and if TVL dropped more than 20% over 10 blocks, initiate a contract pause via multisig. Reaction time should be no more than 2 minutes.

What's Included in the Work

Our service includes comprehensive deliverables: documentation (architecture, operation manual, incident runbook), code repository with the system, configs for staging and production, integration with Telegram, PagerDuty, Slack, and email, a real-time dashboard with TVL charts and alert history, two training sessions for your team with one month of Q&A support, and a 99.9% uptime SLA with 24/7 response to critical incidents.

Timelines and Scope

Estimated timelines: 14 to 30 days depending on bridge complexity (number of supported chains, custom contracts). Cost is calculated individually after the audit. Typical system cost ranges from $15,000 to $30,000, while preventing a single exploit can save over $10 million.

Scope of work:

Component Description
Documentation Architecture, operation manual, incident runbook
Code Repository with the system, configs for staging/production
Integration Connection to Telegram, PagerDuty, Slack, email
Dashboard Real-time TVL charts, anomaly graphs, alert history
Training 2 sessions for the team, Q&A support for one month
Support SLA 99.9% uptime, 24/7 response to critical incidents

Our Advantages

We don't just deploy a ready-made tool — we adapt the system to the specific attack scenarios relevant to your bridge. In our practice, there was a case where a client would have lost $12M if we hadn't stopped the contract 2 blocks before a full drain. We guarantee SLA, provide code in an open (private) repository, and avoid black boxes.

Order a free consulting session on monitoring architecture. Contact us to evaluate your project.