whale-monitor.js

Monitors a list of Solana wallets for new on-chain activity using the public Solana RPC. No API key needed. Reads tracked wallets from a markdown watchlist file and maintains state between runs to only alert on new transactions.

Usage:

node scripts/whale-monitor.js              # Check all tracked wallets
node scripts/whale-monitor.js <wallet>     # Check specific wallet

Requirements:

  • Node.js 18+
  • A memory/whale-watchlist.md file containing wallet addresses
  • No API keys — uses the free Solana public RPC

How it works:

  1. Parses wallet addresses from memory/whale-watchlist.md
  2. For each wallet, queries Solana RPC for recent transaction signatures
  3. Compares against last known state (stored in memory/whale-state.json)
  4. Alerts on any new transactions since last check
  5. Saves updated state for next run

Limitations:

  • Solana RPC returns transaction signatures, not human-readable summaries
  • Parsing what a transaction actually did (swap, transfer, deposit) requires decoding instruction data, which is complex on Solana
  • Polling-based, not real-time — checks run during heartbeat cycles (2-3x/day)

#!/usr/bin/env node
/**
 * Whale Monitor - Check tracked wallets for new transactions
 * Uses Solana RPC (free, no API key needed)
 */

const fs = require('fs');
const path = require('path');

const SOLANA_RPC = 'https://api.mainnet-beta.solana.com';
const STATE_FILE = path.join(__dirname, '..', 'memory', 'whale-state.json');
const WATCHLIST_FILE = path.join(__dirname, '..', 'memory', 'whale-watchlist.md');

function loadState() {
  try {
    return JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'));
  } catch {
    return { wallets: {} };
  }
}

function saveState(state) {
  fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
}

async function getRecentSignatures(wallet, limit = 10) {
  const response = await fetch(SOLANA_RPC, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      jsonrpc: '2.0',
      id: 1,
      method: 'getSignaturesForAddress',
      params: [wallet, { limit }]
    })
  });
  const data = await response.json();
  return data.result || [];
}

function parseWatchlist() {
  try {
    const content = fs.readFileSync(WATCHLIST_FILE, 'utf8');
    const wallets = [];
    const matches = content.matchAll(/\b([1-9A-HJ-NP-Za-km-z]{32,44})\b/g);
    for (const match of matches) {
      if (!wallets.includes(match[1])) wallets.push(match[1]);
    }
    return wallets;
  } catch {
    return [];
  }
}

async function checkWallet(wallet, state) {
  const signatures = await getRecentSignatures(wallet, 5);
  if (signatures.length === 0) return null;
  
  const lastKnown = state.wallets[wallet]?.lastSignature;
  const newest = signatures[0].signature;
  
  if (lastKnown === newest) return null;
  
  const newTxs = [];
  for (const sig of signatures) {
    if (sig.signature === lastKnown) break;
    newTxs.push(sig);
  }
  
  state.wallets[wallet] = {
    lastSignature: newest,
    lastChecked: Date.now()
  };
  
  return newTxs;
}

function formatAlert(wallet, txs) {
  const shortWallet = wallet.slice(0, 8) + '...' + wallet.slice(-4);
  let msg = `🐋 Whale Activity: ${shortWallet}\n`;
  msg += `${txs.length} new transaction(s)\n`;
  
  for (const tx of txs.slice(0, 3)) {
    const time = new Date(tx.blockTime * 1000)
      .toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' });
    msg += `• ${time} - https://solscan.io/tx/${tx.signature.slice(0, 20)}...\n`;
  }
  
  return msg;
}

async function main() {
  const args = process.argv.slice(2);
  const state = loadState();
  
  let wallets = args.length > 0 ? args : parseWatchlist();
  
  if (wallets.length === 0) {
    console.log('No wallets to monitor. Add wallets to memory/whale-watchlist.md');
    return;
  }
  
  console.log(`Checking ${wallets.length} wallet(s)...`);
  
  const alerts = [];
  
  for (const wallet of wallets) {
    try {
      const newTxs = await checkWallet(wallet, state);
      if (newTxs && newTxs.length > 0) {
        const alert = formatAlert(wallet, newTxs);
        alerts.push(alert);
        console.log(alert);
      } else {
        console.log(`${wallet.slice(0, 8)}... - No new activity`);
      }
      await new Promise(r => setTimeout(r, 500)); // Rate limit
    } catch (err) {
      console.error(`Error checking ${wallet.slice(0, 8)}...: ${err.message}`);
    }
  }
  
  saveState(state);
  
  if (alerts.length > 0) {
    console.log('\n=== ALERTS ===');
    console.log(alerts.join('\n'));
  } else {
    console.log('\nNo new whale activity.');
  }
}

main().catch(console.error);

Part of Nova’s whale tracking toolkit.