In Web3, every millisecond matters.
A wallet can be drained faster than you can refresh your block explorer. A single node lag can cascade into a network-wide outage. On Solana — where thousands of transactions fly by every second — visibility isn’t optional, it’s survival.
Traditional monitoring tools built for Web2 simply can’t keep up. They rely on slow, fixed-interval checks and miss what really matters — the spikes, surges, and subtle anomalies that hint at exploits or performance breakdowns.
That’s where protocol observability steps in. It’s the art (and science) of watching everything that happens at the node, contract, and network levels — in real time. Think of it as your blockchain’s nervous system, firing alerts the moment something feels off.
Now imagine pairing that observability with a streaming API like Goldrush — capable of capturing second-by-second on-chain data from Solana. You don’t just monitor; you predict and prevent. From sudden balance drops to suspicious bursts of wallet activity, you’ll see anomalies before they become incidents.
This guide will show you how.
We’ll explore how to build a real-time monitoring and anomaly detection pipeline on Solana using Goldrush, combining threshold-based alerts, statistical logic, and machine learning — so your dApp isn’t just reactive… it’s resilient.
Core Concepts: What Makes Solana Observability Different
Observability on Solana isn’t just about “watching blocks roll in.”
It’s about making sense of chaos — thousands of transactions, validator messages, and contract calls happening every second — and turning that noise into signal.
Here’s what you really need to track to keep your dApp or wallet healthy:
Transaction Throughput – Measures how many transactions the network can process per second. A sudden dip? You might be facing congestion or RPC throttling.
Block Finality – Tells you when a block becomes irreversible. If it starts lagging, your confirmations are no longer reliable.
Network Latency – Tracks how long it takes data to travel between nodes. Higher latency means slower updates and frustrated users.
Smart Contract Events – Reveal everything from token transfers to liquidity changes. Unusual spikes can indicate exploits or bots at work.
Node Health – Validator uptime and sync status directly affect your protocol’s stability — one faulty node can ripple across the chain.
All this data has to come from somewhere.
You can tap into Solana nodes and RPC endpoints for raw, ground-truth information — but for real-time insights, you need streaming APIs like Goldrush.
Goldrush doesn’t wait for you to ask; it pushes live data — wallet balances, DEX trades, validator health, token flows — as they happen. Traditional monitoring tools poll data every few minutes, missing events that occur between checks. But Solana moves too fast for that. Transactions settle in under a second — by the time a log file updates, the exploit is already over.
Streaming APIs change that game completely.
They deliver continuous, high-frequency data that lets you:
Detect flash loan attacks the instant they start
Catch transaction spikes or gas anomalies in seconds
Identify failed contract calls that hint at bugs before they cascade
In short: traditional monitoring looks backward; streaming observability looks forward.
That’s what makes it essential for Web3 developers building on Solana.
By implementing anomaly detection, teams gain several competitive advantages: security threats are identified within seconds rather than hours, reducing exposure windows. User trust increases through transparent, real-time visibility. Operational efficiency improves as alerts are actionable and specific, not noisy false positives. Compliance becomes easier when audit trails capture every anomaly with timestamps and context
Begin by installing Node.js (version 16+) and npm. Visit nodejs.org and download the LTS version. Verify installation:
node --version
npm --version
2.1) Create a new directory for your project:
mkdir solana-anomaly-detector
cd solana-anomaly-detector
What this does: Creates a new folder for your project and enters it. All your files will be organized here.
2.2) Initialize npm and install dependencies:
npm init -y
npm install dotenv @covalenthq/client-sdk chalk
What this does:
npm init -y
creates a package.json file (package configuration file) with default settings
npm install dotenv
installs the dotenv package to safely load environment variables from .env file
npm install @covalenthq/client-sdk
installs Covalent's official Goldrush SDK for streaming blockchain data
npm install chalk
installs the chalk package for colored terminal output (makes logs easier to read)
2.3) Create a .env file in the root directory:
GOLDRUSH_KEY=your_api_key_here
What this does: Stores your Goldrush Streamin API key securely in a .env file. This file is ignored by Git (never committed) so your credentials stay private.
Obtain your Goldrush API key from goldrush.dev by registering as a developer. This key authenticates your requests to the Goldrush Streaming API.
Organize your project with this directory structure:
solana-anomaly-detector/
├── .env
├── .gitignore
├── package.json
├── package-lock.json
├── goldrush-cli.js
Before we dive into the actual code, it’s worth aligning on what you’re actually streaming, how GoldRush delivers it, and what “good” looks like in production.
What “OHLCV” means in this context
OHLCV stands for Open, High, Low, Close, Volume—a candlestick that aggregates many swaps into a single bar. On Base, these bars are derived from DEX pools (e.g., Uniswap v3). Each bar represents a fixed window (e.g., 1 minute) for a specific pool/pair contract.
GoldRush’s OHLCV Pairs stream expects a pool/pair contract address, not a token address. The pool address is typically listed on the DEX pool page, and it's a good idea to keep a small map of token addresses/decimals (such as WETH, USDC, or any other token you’re working with) for later trading logic.
How the stream behaves
When you connect, you choose an interval and a timeframe. That initial backfill warms up your model, so you don’t start cold. Then, you switch to the live bar when you’re ready.
The GoldRush SDK multiplexes multiple subscriptions over a single WebSocket, allowing you you add Token Balances or Wallet Activity streams later without opening new sockets. Keep the event handler light—do heavy work off-thread if you scale up.
Interval = the size of each candle (e.g., ONE_MINUTE).
Timeframe = how many historical bars to send immediately on connect (e.g., one_hour gives ~60 bars).
// add .env file in the root directory
GOLDRUSH_KEY=your_api_key
// Package.json
{
"name": "sol_detection",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node goldrush-cli.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"@covalenthq/client-sdk": "^2.3.4",
"chalk": "^4.1.2",
"dotenv": "^17.2.3",
"graphql-ws": "^6.0.6",
"ws": "^8.18.3"
}
}
Create goldrush-cli.js in the root folder
require('dotenv').config();
const {
GoldRushClient,
StreamingChain,
StreamingInterval,
StreamingTimeframe,
} = require('@covalenthq/client-sdk');
let chalk;
We start by loading environment variables using dotenv
to securely manage the API key.
Next, we import the Covalent GoldRush SDK — which provides the GoldRushClient
for connecting to the streaming API.
We’ll also use Chalk later to add colors and formatting to our terminal output.
(async () => {
chalk = (await import('chalk')).default;
const apiKey = process.env.GOLDRUSH_KEY;
if (!apiKey) {
console.error("ERROR: GOLDRUSH_KEY not found in .env file");
process.exit(1);
}
const client = new GoldRushClient(apiKey, {}, {
onConnecting: () => console.log(chalk.blue("🔗 Connecting to GoldRush streaming service...")),
onOpened: () => {
console.clear();
displayHeader();
console.log(chalk.green("✓ Connected to streaming service!\n"));
console.log(chalk.yellow("📡 Monitoring Wrapped SOL OHLCV data for anomalies...\n"));
},
onClosed: () => {
console.log(chalk.yellow("✓ Disconnected from streaming service"));
process.exit(0);
},
onError: (error) => {
console.error(chalk.red("✗ Streaming error:"), error);
},
});
Here, we:
Load chalk
dynamically to avoid import issues.
Retrieve the GOLDRUSH_KEY
from .env
.
Initialize the GoldRushClient with event handlers for:
Connecting
Opened
Closed
Error
Each event logs a visual status message in the console, improving observability.
const priceHistory = [];
const MAX_HISTORY = 50;
const tokenAddress = "3ucNos4NbumPLZNWztqGHNFFgkHeRMBQAVemeeomsUxv"; // USDC on Sol
function displayHeader() {
console.log(chalk.cyan.bold('═══════════════════════════════════════════════════════════════'));
console.log(chalk.cyan.bold(' 🚀 SOLANA TOKEN OHLCV ANOMALY DETECTION - GOLDRUSH'));
console.log(chalk.cyan.bold('═══════════════════════════════════════════════════════════════'));
console.log(chalk.yellow.bold(' ⚠️ BETA STREAM - May have limited availability'));
console.log(chalk.gray(' Contact [email protected] for assistance'));
console.log(chalk.cyan.bold('═══════════════════════════════════════════════════════════════'));
}
function formatPrice(price) {
if (!price || price === 0) return '0.000000';
const num = parseFloat(price);
return num.toFixed(6);
}
function formatVolume(volume) {
if (!volume || volume === 0) return '0.0000e+0';
const num = parseFloat(volume);
return num.toExponential(4);
}
We configure:
priceHistory
to store recent candles.
MAX_HISTORY
as the sliding window size.
tokenAddress
for USDC on Solana.
Then, we define helper functions for:
Displaying the fancy header.
Formatting numbers for prices and volumes in a readable way.
function calculateStats(history) {
if (history.length < 2) return null;
const closes = history.map(c => parseFloat(c.close) || 0).filter(p => p > 0);
const volumes = history.map(v => parseFloat(v.volume) || 0).filter(v => v > 0);
if (closes.length < 2) return null;
const avgClose = closes.reduce((a, b) => a + b) / closes.length;
const avgVolume = volumes.reduce((a, b) => a + b, 0) / volumes.length;
const stdDev = Math.sqrt(closes.reduce((sq, n) => sq + Math.pow(n - avgClose, 2), 0) / closes.length);
return { avgClose, avgVolume, stdDev };
}
function detectAnomalies(candle, history) {
const anomalies = [];
if (history.length < 3) return anomalies;
const stats = calculateStats(history);
if (!stats) return anomalies;
const currentPrice = parseFloat(candle.close) || 0;
const currentVolume = parseFloat(candle.volume) || 0;
const currentHigh = parseFloat(candle.high) || 0;
const currentLow = parseFloat(candle.low) || 0;
// Detect price & volume spikes, and volatility
const priceChange = ((currentPrice - stats.avgClose) / stats.avgClose) * 100;
if (Math.abs(priceChange) > stats.stdDev * 2) {
anomalies.push({
type: 'PRICE_SPIKE',
severity: Math.abs(priceChange) > stats.stdDev * 3 ? 'CRITICAL' : 'HIGH',
value: priceChange.toFixed(2) + '%',
details: `Price: ${formatPrice(currentPrice)} vs Avg: ${formatPrice(stats.avgClose)}`
});
}
if (stats.avgVolume > 0 && currentVolume / stats.avgVolume > 2) {
anomalies.push({
type: 'VOLUME_SPIKE',
severity: currentVolume / stats.avgVolume > 5 ? 'CRITICAL' : 'HIGH',
value: (currentVolume / stats.avgVolume).toFixed(2) + 'x',
details: `Volume: ${formatVolume(currentVolume)} vs Avg: ${formatVolume(stats.avgVolume)}`
});
}
const spread = currentHigh - currentLow;
const lastCandle = history[history.length - 1];
const avgSpread = parseFloat(lastCandle.high) - parseFloat(lastCandle.low);
if (spread > avgSpread * 3) {
anomalies.push({
type: 'HIGH_VOLATILITY',
severity: 'MEDIUM',
value: (spread / avgSpread).toFixed(2) + 'x',
details: `Spread: ${formatPrice(spread)} vs Avg: ${formatPrice(avgSpread)}`
});
}
return anomalies;
}
These functions:
Compute average price, volume, and standard deviation.
Detect price spikes, volume surges, and volatility patterns based on deviation thresholds.
This forms the core “anomaly detection engine” of your script.
function displayStreamStatus() {
const uptime = Math.floor((Date.now() - streamStartTime) / 1000);
console.log(chalk.cyan('┌─ STREAM STATUS ───────────────────────────────────────────┐'));
console.log(chalk.white(` Status: ${chalk.green('🟢 ACTIVE')}`));
console.log(chalk.white(` Uptime: ${chalk.blue(`${Math.floor(uptime / 60)}m ${uptime % 60}s`)}`));
console.log(chalk.white(` Data Count: ${chalk.yellow(dataCount)}`));
console.log(chalk.cyan('└──────────────────────────────────────────────────────────┘'));
}
function displayCandle(candle, anomalies) {
const timestamp = new Date(candle.timestamp).toLocaleString();
const tokenName = candle.base_token?.contract_ticker_symbol || 'Unknown';
console.log(chalk.yellow(`📊 Token: ${tokenName}`));
console.log(chalk.yellow(`⏰ Timestamp: ${timestamp}`));
console.log('');
console.log(chalk.cyan('┌─ PRICE DATA (OHLCV) ─────────────────────────────────────┐'));
console.log(chalk.white(` Open: ${chalk.blue(formatPrice(candle.open))}`));
console.log(chalk.white(` High: ${chalk.green(formatPrice(candle.high))}`));
console.log(chalk.white(` Low: ${chalk.red(formatPrice(candle.low))}`));
console.log(chalk.white(` Close: ${chalk.yellow(formatPrice(candle.close))}`));
console.log(chalk.cyan('└──────────────────────────────────────────────────────────┘'));
console.log('');
if (anomalies.length > 0) {
console.log(chalk.red.bold('⚠️ ANOMALIES DETECTED'));
anomalies.forEach(a =>
console.log(chalk.red(`[${a.severity}] ${a.type}: ${a.value} → ${a.details}`))
);
} else {
console.log(chalk.green('✓ No anomalies detected - Normal price action'));
}
}
These functions control how the live stream data and detected anomalies are displayed — making it look like a clean, interactive dashboard.
function displayWaitingScreen() {
console.log(chalk.yellow('⏳ Waiting for OHLCV data...'));
console.log(chalk.gray('Stream is active and monitoring for anomalies.'));
}
const timeoutId = setTimeout(() => {
displayWaitingScreen();
heartbeatInterval = setInterval(() => displayWaitingScreen(), 10000);
}, 5000);
If the stream is idle (no data yet), this part ensures users know the script is still alive — by showing a heartbeat message every 10 seconds.
const unsubscribe = client.StreamingService.subscribeToOHLCVTokens(
{
chain_name: StreamingChain.SOLANA_MAINNET,
token_addresses: [tokenAddress],
interval: StreamingInterval.ONE_MINUTE,
timeframe: StreamingTimeframe.ONE_HOUR,
},
{
next: (data) => {
dataReceived = true;
clearTimeout(timeoutId);
lastDataReceived = Date.now();
dataCount++;
let candles = data?.items || [data];
candles.forEach(candle => {
priceHistory.push(candle);
if (priceHistory.length > MAX_HISTORY) priceHistory.shift();
const anomalies = detectAnomalies(candle, priceHistory.slice(0, -1));
displayCandle(candle, anomalies);
});
},
error: (err) => console.error(chalk.red('✗ Subscription error:'), err),
complete: () => console.log(chalk.green('✓ Stream completed')),
}
);
This is where the real-time streaming magic happens.
You subscribe to the Solana mainnet OHLCV feed for the selected token and process every new candle to detect anomalies instantly.
process.on('SIGINT', async () => {
console.log("\n" + chalk.yellow("🛑 Shutting down..."));
if (heartbeatInterval) clearInterval(heartbeatInterval);
unsubscribe();
await client.StreamingService.disconnect();
process.exit(0);
});
})();
Finally, we handle user termination (Ctrl+C
) gracefully — unsubscribing from the stream, clearing timers, and closing the client cleanly.
This Node.js application monitors Solana token prices in real time using the GoldRush Streaming API.
It continuously tracks Open, High, Low, Close, Volume (OHLCV) price data and automatically detects trading anomalies that could indicate market manipulation, pump-and-dump schemes, or unusual trading activity.
Connection opens → “Connecting…” message appears
Stream starts → Waiting for first OHLCV data
Data arrives → Raw JSON logged for debugging
Candle data extracted → Parsed from response
History updated → Adds latest candle (max 50)
Stats calculated → Average and deviation computed
Anomalies detected → Price, volume, or volatility alerts triggered
Output displayed → Beautifully formatted with color-coded alerts
Loop continues → Repeats for each new candle every minute
Each incoming update displays:
Token name and symbol
Timestamp of the data
OHLCV (Open, High, Low, Close, Volume) values
Volume in USD
Current price and quote rate
Token metadata (name, symbol, decimals)
Any detected anomalies with severity levels
Real-time anomaly detection transforms Web3 security from reactive to proactive. By integrating Goldrush Streaming APIs and granular monitoring, developers gain millisecond-level visibility into wallet activity—enabling instant threat detection and response.
But this is just the beginning. In Part 2, we’ll dive into building a production-grade observability and anomaly detection system powered by Goldrush on Solana. You’ll learn how to:
Define your monitoring scope (smart contracts, validators, or network-level events)
Stream live Solana data using low-latency WebSocket connections
Architect a scalable ingestion pipeline for real-time analytics
Implement threshold-based and ML-powered detection logic
….. And much more , lets dive dive into this in the next part .