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

Habeeb Yinka
Content Writer
Learn how to track whale wallets on the Base blockchain using Covalent's GoldRush Streaming API.

While traditional market analysis reacts to price movements after they occur, on-chain analysis provides a unique leading-indicator perspective. This guide explores how to track the capital flows of the market's most influential participants, including whales, using Covalent's GoldRush Streaming API on the Base blockchain. If you haven’t checked part 1 of this guide, you can check it out here.

What You Will Build

A whale discovery system that:

  • Identifies top trading wallets for any token on the Base network.

  • Analyses trading volume, profit/loss metrics, and wallet behaviour.

  • Provides real-time monitoring capabilities for whale activity

What You Will Need

  • Node.js v18+ and npm

  • Basic knowledge of TypeScript and WebSocket connections

  • Covalent GoldRush API key

  • Basic understanding of EVM chains (Base)

Step 1: Setting Up the Development Environment

Create a new directory and set up a Node.js project:

mkdir whale-tracker cd whale-tracker npm init -y

Install the required dependencies:

npm install @covalenthq/client-sdk ws dotenv npm install typescript ts-node @types/node --save-dev

Initialize TypeScript configuration:

npx tsc --init

Create your main file:

touch whale-tracker.ts

Step 2: Understanding the Core Components

Let's break down the whale tracking step by step. Open your whale-tracker.ts file and follow along:

Step 2.1: Import Dependencies and WebSocket Setup

import { GoldRushClient } 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.

Step 2.2: 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; }
  • Ensures type safety when working with the API responses.

  • Provides autocomplete and error checking in your IDE.

Step 2.3: 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 own from Covalent.

  • Event handlers provide connection status feedback.

  • The client manages WebSocket connections automatically.

Step 3: The Whale Discovery Function

Step 3.1: Function Definition and Setup

const getTopTradingWallets = async (   chainName: string,   tokenAddress: string,   tokenSymbol?: string ): Promise<WalletData[] | null> => {   console.log("\n" + "=".repeat(80));   console.log("Querying Top Trading Wallets");   console.log("Chain: " + chainName);   console.log("Token: " + tokenAddress);   if (tokenSymbol) console.log("Symbol: " + tokenSymbol);   console.log("=".repeat(80) + "\n");

This function takes three parameters:

  • chainName: The blockchain network (e.g., "BASE_MAINNET")

  • tokenAddress: The contract address of the token to analyze

  • tokenSymbol: Optional human-readable symbol for display

Step 3.2: GraphQL Query Execution

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         }       }     }`,     {},     {
  • Uses Covalent's upnlForToken endpoint

  • Retrieves 30 days of trading data for the specified token

  • Returns wallet addresses, volumes, P&L, and metadata

Step 3.3: Response Handling and Error Management

next: (response: QueryResponse) => {   if (hasResponded) return;   hasResponded = true;   // Check for API errors   if (response.error) {     console.error("API Error: " + response.error_message);     console.log("Error Code: " + response.error_code);     unsubscribe();     resolve(null);     return;   }   const wallets = response.data?.upnlForToken;   if (!wallets || wallets.length === 0) {     console.log("No trading data available for this token");     console.log("\nPossible reasons:");     console.log("  • Token has no recent trading activity (last 30 days)");     console.log("  • Token is not indexed on this chain yet");     console.log("  • Feature is in beta and not fully available");     console.log("\nNote: Currently works best with established tokens\n");     unsubscribe();     resolve(null);     return;   }

Step 3.4: Data Modelling

console.log("Found " + wallets.length + " trading wallets\n"); // Calculate totals 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); const totalUnrealizedPnL = wallets.reduce((sum, w) => sum + w.pnl_unrealized_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(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })); console.log("   Total Unrealized P&L: $" + totalUnrealizedPnL.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })); console.log("\n" + "-".repeat(80) + "\n");

What we're calculating:

  • Total Volume: Sum of all tokens traded by identified wallets

  • Total Transactions: Combined trade count across all wallets

  • Realized P&L: Actual profits/losses from completed trades

  • Unrealized P&L: Paper gains/losses from current holdings

Step 3.5: Display Top Wallets

const displayCount = Math.min(wallets.length, 10); console.log("Top " + displayCount + " Wallets by Volume:\n"); wallets.slice(0, displayCount).forEach((wallet, index) => {   const totalPnL = wallet.pnl_realized_usd + wallet.pnl_unrealized_usd;   const shortAddress = wallet.address.slice(0, 8) + "..." + wallet.address.slice(-6);   const tokenSymbol = wallet.contract_metadata.contract_ticker_symbol || "Unknown";   console.log((index + 1) + ". " + shortAddress);   console.log("   Token: " + tokenSymbol);   console.log("   Volume: " + wallet.volume.toLocaleString() + " " + tokenSymbol);   console.log("   Transactions: " + wallet.transactions_count.toLocaleString());   console.log("   Balance: " + parseFloat(wallet.balance_pretty).toLocaleString() + " " + tokenSymbol);   console.log("   Realized P&L: $" + wallet.pnl_realized_usd.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }));   console.log("   Unrealized P&L: $" + wallet.pnl_unrealized_usd.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }));   console.log("   Total P&L: $" + totalPnL.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }));   console.log(); });

Key wallet metrics displayed:

  • Trading volume and transaction frequency

  • Current token holdings

  • Profit and loss breakdown

Step 3.6: Export Whale Addresses for Monitoring

if (wallets.length > 10) {   console.log("... and " + (wallets.length - 10) + " more wallets\n"); } // Export whale addresses for monitoring console.log("-".repeat(80)); console.log("Whale Addresses for Real-time Monitoring:\n"); wallets.slice(0, 10).forEach((wallet, index) => {   console.log((index + 1) + ". " + wallet.address); }); console.log("\n" + "-".repeat(80)); console.log("Copy these addresses to use with Wallet Activity Stream"); console.log("-".repeat(80) + "\n"); unsubscribe(); resolve(wallets);

Step 3.7: Error and Timeout Handling

error: (error) => {   if (hasResponded) return;   hasResponded = true;   console.error("Error fetching top trading wallets:", error);   unsubscribe();   resolve(null); }, complete: () => {   if (!hasResponded) {     console.log("Query completed without response");     resolve(null);   } } // Auto-timeout after 30 seconds setTimeout(() => {   if (!hasResponded) {     console.log("Query timeout after 30 seconds");     unsubscribe();     resolve(null);   } }, 30000);

Step 4: Token Configuration

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

Step 5: Main Execution Function

const main = async (): Promise<void> => {   console.log("Starting Whale Discovery Analysis...\n");   try {     console.log("Analyzing Specific Token on Base");     const degenWhales = await getTopTradingWallets(       "BASE_MAINNET",       TOKEN_EXAMPLES.BASE_MAINNET.DEGEN.address,       TOKEN_EXAMPLES.BASE_MAINNET.DEGEN.symbol     );     if (degenWhales) {       console.log("Successfully retrieved Degen whale data!");     }   } catch (error) {     console.error("Fatal error:", error);   } finally {     setTimeout(async () => {       await client.StreamingService.disconnect();       console.log("\n" + "=".repeat(80));       console.log("Analysis complete! Disconnected from Covalent service.");       console.log("=".repeat(80) + "\n");       process.exit(0);     }, 1000);   } }; // Run the main function main().catch(console.error); // Export for use in other modules export {   getTopTradingWallets,   TOKEN_EXAMPLES,   type WalletData };

Step 6: Running the Whale Tracker

Open your terminal and run:

npx ts-node whale-tracker.ts

You should see output similar to this:

What's Next?

In Part 3, we'll expand this foundation to include:

  • Automated alert system for significant whale movements

  • Portfolio correlation analysis across multiple digital assets

  • Predictive modeling based on historical whale behavior patterns

Happy building, and see you in Part 3, where we'll bring these whale insights to life with real-time monitoring and insights!

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.