Real-Time Base Whale Tracking Using GoldRush Streaming API- Part 3

Habeeb Yinka
Content Writer
Learn to build a real-time crypto whale tracking system on Base.

Welcome to Part 3 of our Whale Tracking series. In Part 2, we built a whale-discovery system that identifies the top trading wallets. Now, we're taking it a step further by implementing real-time monitoring and automated trading signals based on whale behaviour patterns. Let’s get started.

Prerequisites:

  • Completed Part 2 (or have the whale discovery code ready)

  • Basic understanding of WebSockets.

Note: We'll be making several changes to the Part 2 code to support real-time features. Don't worry, we'll walk through each change step by step.

Let's get started!

Step 1: Imports and WebSocket Setup

This is the same as what we have in part 2 of this guide.

import { GoldRushClient, StreamingChain } from "@covalenthq/client-sdk"; import WebSocket from "ws"; import * as dotenv from "dotenv"; dotenv.config(); (global as any).WebSocket = WebSocket

What this does:

  • Imports the Covalent GoldRush client for API access.

  • Imports WebSocket for real-time connections.

  • Sets up WebSocket globally for our Node.js environment.

Define TypeScript Interfaces for Type Safety

interface ContractMetadata { contract_address: string; contract_name: string; contract_ticker_symbol: string; contract_decimals: number; } interface WalletData { token: string; address: string; volume: number; transactions_count: number; pnl_realized_usd: number; balance: number; balance_pretty: string; pnl_unrealized_usd: number; contract_metadata: ContractMetadata; } interface QueryResponse { data?: { upnlForToken: WalletData[]; }; error?: boolean; error_message?: string; error_code?: number; } interface TradingSignal { whale_address: string; signal: 'BUY' | 'SELL' | 'HOLD'; pattern: string; confidence: number; reasoning: string; timestamp: string; } interface TransactionAlert { type: 'LARGE_TRANSFER' | 'WHALE_MOVEMENT' | 'CONTRACT_INTERACTION' | 'PATTERN_MATCH'; severity: 'LOW' | 'MEDIUM' | 'HIGH'; message: string; transaction: any; whale_data?: WalletData; }
  • This ensures type safety when working with the API responses.

  • Provides autocomplete and error checking in your IDE.

  • Defines automated trading signals generated by the system.

  • Defines alerts for significant whale transactions.

Initialize the GoldRush Client

const API_KEY = process.env.COVALENT_API_KEY; const client = new GoldRushClient( API_KEY, {}, { onConnecting: () => console.log(" Connecting to Covalent service..."), onOpened: () => console.log(" Connected to Covalent service!"), onClosed: () => console.log(" Disconnected from Covalent service"), onError: (error) => console.error(" Streaming error:", error), } );
  • Replace the API key with your Covalent API key.

  • Event handlers provide connection status feedback.

  • The client manages WebSocket connections automatically.

Step 2: WhaleMonitoringSystem

class WhaleMonitoringSystem { private discoveredWhales: Map<string, WalletData[]> = new Map(); private activeMonitors: Map<string, () => void> = new Map(); private tradingSignals: TradingSignal[] = [];
  • discoveredWhales: Storage of whale addresses per token.

  • activeMonitors: Real-time tracking sessions across blockchain.

  • tradingSignals: Accumulated alpha signals for pattern analysis.

Whale Discovery

async discoverWhales( chainName: string, tokenAddress: string, tokenSymbol?: string ): Promise<WalletData[] | null> { console.log(`\n${"=".repeat(80)}`); console.log(`Discovering Top Trading Wallets`); console.log(`Chain: ${chainName}`); console.log(`Token: ${tokenAddress}`); if (tokenSymbol) console.log(`Symbol: ${tokenSymbol}`); console.log(`${"=".repeat(80)}\n`); return new Promise((resolve) => { let hasResponded = false; const unsubscribe = client.StreamingService.rawQuery( `query { upnlForToken( chain_name: ${chainName} token_address: "${tokenAddress}" ) { token address volume transactions_count pnl_realized_usd balance balance_pretty pnl_unrealized_usd contract_metadata { contract_address contract_name contract_ticker_symbol contract_decimals } } }`, {}, { next: (response: QueryResponse) => { if (hasResponded) return; hasResponded = true; if (response.error) { console.error(`API Error: ${response.error_message}`); unsubscribe(); resolve(null); return; } const wallets = response.data?.upnlForToken; if (!wallets || wallets.length === 0) { console.log("No trading data available for this token\n"); unsubscribe(); resolve(null); return; } console.log(`Found ${wallets.length} trading wallets\n`); this.discoveredWhales.set(tokenAddress, wallets); this.displayWhaleSummary(wallets, tokenSymbol); unsubscribe(); resolve(wallets); }, error: (error) => { if (hasResponded) return; hasResponded = true; console.error("Error fetching whales:", error); unsubscribe(); resolve(null); }, complete: () => { if (!hasResponded) { console.log("Query completed without response"); resolve(null); } } } ); setTimeout(() => { if (!hasResponded) { console.log("Query timeout after 30 seconds"); unsubscribe(); resolve(null); } }, 30000); }); }

This function executes blockchain queries to identify and profile our top trading whales.

  • Establishes a secure connection to the blockchain.

  • upnlForToken Query: Leverages Covalent's unrealized P&L endpoint for 30-day trading data.

  • Whale Profiling: Extracts volume, P&L, balances, and trading patterns.

  •  Timeout: 30-second fail-safe to prevent hanging queries.

  • Storage: Caches whale data for real-time monitoring.

Real-Time Monitoring Engine

startRealTimeMonitoring(chain: StreamingChain = StreamingChain.BASE_MAINNET) { const allWhaleAddresses: string[] = []; this.discoveredWhales.forEach((wallets) => { wallets.slice(0, 10).forEach(wallet => { allWhaleAddresses.push(wallet.address); }); }); if (allWhaleAddresses.length === 0) { console.log("No whales discovered to monitor"); return; } console.log(`\nStarting real-time monitoring for ${allWhaleAddresses.length} whale addresses...`); console.log(`\nMonitored Whale Addresses:`); allWhaleAddresses.forEach((addr, i) => { const whaleData = this.findWhaleData(addr); const symbol = whaleData?.contract_metadata.contract_ticker_symbol || 'Unknown'; console.log(` ${i + 1}. ${addr.substring(0, 10)}...${addr.substring(addr.length - 6)} (${symbol})`); }); console.log(''); const unsubscribe = client.StreamingService.subscribeToWalletActivity( { chain_name: chain, wallet_addresses: allWhaleAddresses, }, { next: (data: any) => { if (Array.isArray(data)) { data.forEach(tx => this.processWhaleTransaction(tx)); } else if (data?.tx_hash) { this.processWhaleTransaction(data); } }, error: (error: any) => { console.error("Monitoring error:", error); }, } ); this.activeMonitors.set(chain, unsubscribe); console.log(`Real-time monitoring active for chain: ${chain}\n`); }
  • Targeted Monitoring: Tracks the top 10 whales per discovered token.

  • Portfolio Overview: Displays all monitored addresses with token associations.

  • Live Subscription: Establishes a real-time WebSocket connection to the blockchain.

  • Instant Processing: Routes incoming transactions to the intelligence engine.

Transaction Processor

private processWhaleTransaction(tx: any): void { // Only process if sender OR receiver is a monitored whale const fromWhale = this.findWhaleData(tx.from_address); const toWhale = this.findWhaleData(tx.to_address); if (!fromWhale && !toWhale) { return; } const alerts = this.analyzeWhaleTransaction(tx, fromWhale, toWhale); // Only show transactions with alerts if (alerts.length > 0) { this.displayTransaction(tx); alerts.forEach(alert => { this.displayAlert(alert); const signal = this.generateSignalFromAlert(alert); if (signal) { this.tradingSignals.push(signal); this.displayTradingSignal(signal); } }); console.log(''); } }

This filters and processes blockchain transactions to extract meaningful whale intelligence.

  • Pattern Analysis: Deep examination of whale-involved transactions.

  • Alert Generation: Creates categorized alerts based on behavior patterns.

  • Signal Creation: Converts alerts into actionable trading signals.

  • Intelligence Storage: Archives signals for historical analysis.

Pattern Analysis Engine

private analyzeWhaleTransaction(tx: any, fromWhale?: WalletData, toWhale?: WalletData): TransactionAlert[] { const alerts: TransactionAlert[] = []; const value = parseFloat(tx.value || 0); // ONLY create SELL signal if FROM is a monitored whale if (tx.decoded_type === 'TRANSFER' && fromWhale && value > 1e10) { alerts.push({ type: 'LARGE_TRANSFER', severity: 'HIGH', message: `WHALE TRANSFER OUT: ${this.formatValue(tx.value)}`, transaction: tx, whale_data: fromWhale }); } if (tx.decoded_type === 'TRANSFER' && toWhale && fromWhale && value > 1e10) { alerts.push({ type: 'WHALE_MOVEMENT', severity: 'HIGH', message: `WHALE TRANSFER IN: ${this.formatValue(tx.value)}`, transaction: tx, whale_data: toWhale }); } if (tx.logs && tx.logs.length > 30 && (fromWhale || toWhale)) { alerts.push({ type: 'CONTRACT_INTERACTION', severity: 'MEDIUM', message: `Major contract interaction: ${tx.logs.length} events`, transaction: tx, whale_data: fromWhale || toWhale }); } return alerts; }

Pattern recognition that identifies meaningful whale movements.

  • Distribution Detection: Large outgoing transfers with potential selling pressure.

  • Accumulation Detection: Whale-to-whale transfers are a potential buying opportunity.

  • DeFi Activity: This monitors multi-event transactions.

Signal Generator

private findWhaleData(address: string): WalletData | undefined { for (const [_, wallets] of this.discoveredWhales.entries()) { const whale = wallets.find(w => w.address.toLowerCase() === address.toLowerCase()); if (whale) return whale; } return undefined; } private generateSignalFromAlert(alert: TransactionAlert): TradingSignal | null { if (!alert.whale_data) return null; let signal: 'BUY' | 'SELL' | 'HOLD' = 'HOLD'; let pattern = 'MONITORING'; let confidence = 0.6; let reasoning = ''; const tx = alert.transaction; // Handle incoming transfers to whales (accumulation) if (alert.type === 'WHALE_MOVEMENT') { signal = 'BUY'; pattern = 'WHALE_ACCUMULATION'; confidence = 0.75; reasoning = `Whale receiving ${alert.whale_data.contract_metadata.contract_ticker_symbol}: ${this.formatValue(tx.value)} incoming`; } // Handle outgoing transfers from whales (distribution) else if (alert.type === 'LARGE_TRANSFER' && tx.decoded_type === 'TRANSFER') { signal = 'SELL'; pattern = 'WHALE_DISTRIBUTION'; confidence = 0.7; reasoning = `Whale distributing ${alert.whale_data.contract_metadata.contract_ticker_symbol}: ${this.formatValue(tx.value)} moved out`; } // Only return signals with meaningful reasoning if (!reasoning) return null; return { whale_address: alert.whale_data.address, signal, pattern, confidence, reasoning, timestamp: new Date().toISOString() }; }

Transforms detected patterns into quantified trading signals with confidence scoring.

private displayWhaleSummary(wallets: WalletData[], tokenSymbol?: string): void { const totalVolume = wallets.reduce((sum, w) => sum + w.volume, 0); const totalTransactions = wallets.reduce((sum, w) => sum + w.transactions_count, 0); const totalRealizedPnL = wallets.reduce((sum, w) => sum + w.pnl_realized_usd, 0); console.log("Summary Statistics:"); console.log(` Total Volume: ${totalVolume.toLocaleString()} tokens`); console.log(` Total Transactions: ${totalTransactions.toLocaleString()}`); console.log(` Total Realized P&L: $${totalRealizedPnL.toLocaleString()}`); console.log(`\n${"-".repeat(80)}\n`); const displayCount = Math.min(wallets.length, 5); console.log(`Top ${displayCount} Whales:\n`); wallets.slice(0, displayCount).forEach((wallet, index) => { const shortAddress = `${wallet.address.slice(0, 8)}...${wallet.address.slice(-6)}`; const symbol = wallet.contract_metadata.contract_ticker_symbol || tokenSymbol || "Unknown"; console.log(`${index + 1}. ${shortAddress}`); console.log(` Balance: ${parseFloat(wallet.balance_pretty).toLocaleString()} ${symbol}`); console.log(` Volume: ${wallet.volume.toLocaleString()} ${symbol}`); console.log(` Realized P&L: $${wallet.pnl_realized_usd.toLocaleString()}`); }); console.log(`\n${"-".repeat(80)}`); console.log("These whales are now being monitored in real-time"); console.log(`${"-".repeat(80)}\n`); } private formatValue(value: any): string { if (!value && value !== 0) return '0'; const num = parseFloat(value); if (isNaN(num)) return String(value); if (num >= 1e18) return `${(num / 1e18).toFixed(2)} ETH`; if (num >= 1e9) return `${(num / 1e9).toFixed(2)}B`; if (num >= 1e6) return `${(num / 1e6).toFixed(2)}M`; if (num >= 1e3) return `${(num / 1e3).toFixed(2)}K`; return num.toLocaleString(); } getRecentSignals(count: number = 5): TradingSignal[] { return this.tradingSignals.slice(-count).reverse(); } stopAllMonitoring(): void { this.activeMonitors.forEach((unsubscribe, chain) => { unsubscribe(); console.log(`Stopped monitoring on ${chain}`); }); this.activeMonitors.clear(); } }

Presents complex blockchain intelligence in clear, actionable formats.

  • Whale Identification: Instant recognition of tracked whales.

  • Severity Indicators: Quick assessment of alert importance.

  • Confidence Scoring: Quantified the reliability of each signal using a confidence score.

Step 3: Token Configuration

const TOKEN_EXAMPLES = { BASE_MAINNET: { DEGEN: { address: "0x4ed4E862860beD51a9570b96d89aF5E1B0Efefed", symbol: "DEGEN" }, AXR: { address: "0x58Db197E91Bc8Cf1587F75850683e4bd0730e6BF", symbol: "AXR" } }, };

Pre-configured token watchlist for immediate intelligence gathering. For this guide, we'll use the AXR and Degen tokens as examples, but you can substitute any tokens you prefer.

Step 4: Main Execution

const main = async (): Promise<void> => { console.log("Starting Advanced Whale Monitoring System\n"); const monitor = new WhaleMonitoringSystem(); try { console.log("Phase 1: Whale Discovery\n"); await monitor.discoverWhales( "BASE_MAINNET", TOKEN_EXAMPLES.BASE_MAINNET.DEGEN.address, TOKEN_EXAMPLES.BASE_MAINNET.DEGEN.symbol ); await monitor.discoverWhales( "BASE_MAINNET", TOKEN_EXAMPLES.BASE_MAINNET.AXR.address, TOKEN_EXAMPLES.BASE_MAINNET.AXR.symbol ); console.log("\nPhase 2: Starting Real-time Monitoring\n"); monitor.startRealTimeMonitoring(StreamingChain.BASE_MAINNET); const monitoringDuration = 10 * 60 * 1000; // 10 minutes console.log(`Monitoring will run for ${monitoringDuration / 60000} minutes...`); console.log("Press Ctrl+C to stop early\n"); const signalInterval = setInterval(() => { const recentSignals = monitor.getRecentSignals(5); if (recentSignals.length > 0) { console.log(`\n${"=".repeat(80)}`); console.log(`Trading Signals Summary (last 5):`); console.log(`${"=".repeat(80)}`); // Count signal types const buys = recentSignals.filter(s => s.signal === 'BUY').length; const sells = recentSignals.filter(s => s.signal === 'SELL').length; const holds = recentSignals.filter(s => s.signal === 'HOLD').length; console.log(`BUY Signals: ${buys} | SELL Signals: ${sells} | HOLD Signals: ${holds}\n`); recentSignals.forEach((signal, idx) => { const signalType = signal.signal === 'BUY' ? '[BUY]' : signal.signal === 'SELL' ? '[SELL]' : '[HOLD]'; console.log(`${idx + 1}. ${signalType} ${signal.pattern} (${(signal.confidence * 100).toFixed(0)}%)`); console.log(` ${signal.reasoning}`); console.log(` Whale: ${signal.whale_address.substring(0, 10)}...${signal.whale_address.substring(signal.whale_address.length - 6)}\n`); }); } }, 60000); setTimeout(async () => { clearInterval(signalInterval); console.log(`\n${"=".repeat(80)}`); console.log("Monitoring Session Complete"); console.log(`${"=".repeat(80)}`); const allSignals = monitor.getRecentSignals(20); if (allSignals.length > 0) { console.log(`\nGenerated ${allSignals.length} trading signals total:\n`); allSignals.forEach(signal => { console.log(` ${signal.signal.padEnd(4)} | ${signal.pattern.padEnd(20)} | ${signal.whale_address.substring(0, 16)}...`); }); } monitor.stopAllMonitoring(); await client.StreamingService.disconnect(); process.exit(0); }, monitoringDuration); process.on('SIGINT', async () => { clearInterval(signalInterval); console.log(`\nStopping monitoring...`); const signals = monitor.getRecentSignals(10); if (signals.length > 0) { console.log(`\nFinal Trading Signals (last 10):\n`); signals.forEach(signal => { console.log(` ${signal.signal} - ${signal.pattern} - ${signal.reasoning}`); }); } monitor.stopAllMonitoring(); await client.StreamingService.disconnect(); process.exit(0); }); } catch (error) { console.error("Fatal error:", error); monitor.stopAllMonitoring(); await client.StreamingService.disconnect(); process.exit(1); } }; if (require.main === module) { main().catch(console.error); } export { WhaleMonitoringSystem, TOKEN_EXAMPLES };
  • Activation: Secure startup with connection validation.

  • Intelligence Gathering: Whale discovery across multiple tokens.

  • Live Activation: Real-time monitoring establishment.

  •  Live Alpha Dashboard: Periodic signal summaries with statistics.

Step 5: Running the Whale Tracker

Open your terminal and run:

npx ts-node whale-tracker.ts

You should see output similar to this:

In this tutorial, we built a real-time tracker to monitor cryptocurrency "whales" wallets holding massive amounts of tokens. We configured it to track 20 specific whale addresses on the Base network and successfully caught serious action in real time. Our tracker alerted us that one major AXR whale began offloading over 4.3 million tokens, triggering a high-confidence SELL signal. This guide helps you to spot these major market moves early and make informed decisions, all built using the GoldRush Streaming API.

Happy coding!

Get Started

Get started with GoldRush API in minutes. Sign up for a free API key and start building.

Support

Explore multiple support options! From FAQs for self-help to real-time interactions on Discord.

Contact Sales

Interested in our professional or enterprise plans? Contact our sales team to learn more.