DEV Community

TateLyman
TateLyman

Posted on

solana wallet monitoring — track any wallet in real time with javascript

every degen trader watches wallets. the smart money wallets, the insider wallets, the wallets that always seem to buy tokens right before they pump. you can build your own wallet tracker in about 100 lines of javascript.

here's how.

what you need

  • node.js (v18+)
  • @solana/web3.js — the official solana SDK
  • a solana RPC endpoint (helius, quicknode, or the public one for testing)
npm init -y
npm install @solana/web3.js
Enter fullscreen mode Exit fullscreen mode

connecting to solana

const { Connection, PublicKey } = require('@solana/web3.js');

// public RPC works for testing. for production use helius or quicknode
const RPC = process.env.RPC_URL || 'https://api.mainnet-beta.solana.com';
const connection = new Connection(RPC, 'confirmed');
Enter fullscreen mode Exit fullscreen mode

the public RPC has rate limits. if you're tracking wallets seriously, get a paid endpoint. helius free tier gives you 10 requests/second which is plenty for a personal tracker.

method 1: polling for new transactions

the simplest approach. check for new transactions every few seconds.

const trackedWallets = [
  'YOUR_WALLET_ADDRESS_HERE',
  // add more wallets
];

const lastSeen = {};

async function checkWallet(address) {
  try {
    const pubkey = new PublicKey(address);
    const sigs = await connection.getSignaturesForAddress(pubkey, { limit: 5 });

    if (!lastSeen[address]) {
      lastSeen[address] = sigs[0]?.signature;
      console.log(`tracking ${address.slice(0, 8)}... (latest: ${sigs[0]?.signature.slice(0, 16)})`);
      return;
    }

    const newTxs = [];
    for (const sig of sigs) {
      if (sig.signature === lastSeen[address]) break;
      newTxs.push(sig);
    }

    if (newTxs.length > 0) {
      lastSeen[address] = newTxs[0].signature;
      for (const tx of newTxs) {
        console.log(`\n🔔 NEW TX from ${address.slice(0, 8)}...`);
        console.log(`   sig: ${tx.signature}`);
        console.log(`   time: ${new Date(tx.blockTime * 1000).toISOString()}`);
        await parseTx(tx.signature);
      }
    }
  } catch (e) {
    console.error(`error checking ${address.slice(0, 8)}: ${e.message}`);
  }
}

async function monitor() {
  console.log(`monitoring ${trackedWallets.length} wallets...\n`);
  while (true) {
    for (const wallet of trackedWallets) {
      await checkWallet(wallet);
    }
    await new Promise(r => setTimeout(r, 5000)); // check every 5s
  }
}
Enter fullscreen mode Exit fullscreen mode

method 2: websocket subscriptions

faster than polling. you get notified instantly when a transaction hits.

function subscribeToWallet(address) {
  const pubkey = new PublicKey(address);

  connection.onAccountChange(pubkey, (accountInfo, context) => {
    console.log(`\n⚡ account change detected: ${address.slice(0, 8)}...`);
    console.log(`   slot: ${context.slot}`);
    console.log(`   lamports: ${accountInfo.lamports / 1e9} SOL`);
  });

  // for transaction-level detail:
  connection.onLogs(pubkey, (logs, context) => {
    console.log(`\n📝 new logs for ${address.slice(0, 8)}...`);
    console.log(`   sig: ${logs.signature}`);
    if (logs.err) {
      console.log(`   ❌ transaction failed`);
    } else {
      console.log(`   ✅ transaction succeeded`);
      // parse the logs for token transfers, swaps, etc
      for (const log of logs.logs) {
        if (log.includes('Transfer')) console.log(`   ${log}`);
      }
    }
  }, 'confirmed');

  console.log(`subscribed to ${address.slice(0, 8)}...`);
}

// subscribe to all wallets
trackedWallets.forEach(subscribeToWallet);
Enter fullscreen mode Exit fullscreen mode

websockets are great but they can disconnect. you need reconnection logic:

connection._rpcWebSocket.on('close', () => {
  console.log('websocket disconnected, reconnecting...');
  setTimeout(() => {
    trackedWallets.forEach(subscribeToWallet);
  }, 3000);
});
Enter fullscreen mode Exit fullscreen mode

parsing transactions

raw transaction data is ugly. here's how to make sense of it:

async function parseTx(signature) {
  try {
    const tx = await connection.getParsedTransaction(signature, {
      maxSupportedTransactionVersion: 0
    });

    if (!tx) return;

    const instructions = tx.transaction.message.instructions;

    for (const ix of instructions) {
      // token transfers
      if (ix.parsed?.type === 'transfer' || ix.parsed?.type === 'transferChecked') {
        const info = ix.parsed.info;
        console.log(`   💸 transfer: ${info.amount || info.tokenAmount?.uiAmount} tokens`);
        console.log(`      from: ${info.source?.slice(0, 8)}...`);
        console.log(`      to: ${info.destination?.slice(0, 8)}...`);
      }

      // SOL transfers
      if (ix.parsed?.type === 'transfer' && ix.program === 'system') {
        const sol = ix.parsed.info.lamports / 1e9;
        console.log(`   💰 SOL transfer: ${sol} SOL`);
      }

      // DEX swaps (raydium, jupiter, etc)
      if (ix.programId?.toString() === 'JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4' ||
          ix.programId?.toString() === 'JUP4Fb2cqiRUcaTHdrPC8h2gNsA2ETXiPDD33WcGuJB') {
        console.log(`   🔄 Jupiter swap detected`);
      }
    }

    // check token balance changes
    if (tx.meta?.preTokenBalances && tx.meta?.postTokenBalances) {
      const pre = tx.meta.preTokenBalances;
      const post = tx.meta.postTokenBalances;

      for (const postBal of post) {
        const preBal = pre.find(p => p.accountIndex === postBal.accountIndex);
        const before = preBal?.uiTokenAmount?.uiAmount || 0;
        const after = postBal.uiTokenAmount?.uiAmount || 0;
        const diff = after - before;

        if (diff !== 0) {
          console.log(`   token: ${postBal.mint?.slice(0, 8)}... | change: ${diff > 0 ? '+' : ''}${diff}`);
        }
      }
    }
  } catch (e) {
    console.error(`   parse error: ${e.message}`);
  }
}
Enter fullscreen mode Exit fullscreen mode

adding notifications

watching a terminal is boring. send alerts to telegram or discord.

async function sendTelegramAlert(message) {
  const BOT_TOKEN = process.env.TG_BOT_TOKEN;
  const CHAT_ID = process.env.TG_CHAT_ID;

  const url = `https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`;
  const payload = JSON.stringify({
    chat_id: CHAT_ID,
    text: message,
    parse_mode: 'HTML'
  });

  return new Promise((resolve) => {
    const req = require('https').request(url, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' }
    }, resolve);
    req.write(payload);
    req.end();
  });
}

// in your checkWallet function, replace console.log with:
await sendTelegramAlert(`🔔 <b>Wallet Activity</b>\n${address.slice(0,8)}...\nTx: ${tx.signature}`);
Enter fullscreen mode Exit fullscreen mode

putting it all together

the full script with polling, parsing, and optional telegram alerts is about 150 lines. runs on any VPS, raspberry pi, or your laptop.

for something more polished, i built a solana DeFi toolkit that includes wallet monitoring, token balance snapshots, transaction parsing, and a bunch of other scripts you'd normally have to write from scratch. saves a few hours of setup.

if you want real-time wallet tracking with copy-trading, auto-buy triggers, and whale alerts built in, @solscanitbot on telegram does all of that — i built it to scratch the same itch that led me to write this tutorial.

gotchas

  • RPC rate limits: the public endpoint will throttle you fast. budget $50/mo for a decent RPC if you're serious
  • websocket drops: they happen. always have reconnection logic
  • transaction versioning: always set maxSupportedTransactionVersion: 0 or you'll miss transactions
  • token accounts: wallets don't hold tokens directly — they have associated token accounts. use getTokenAccountsByOwner to find them

solana's RPC API is honestly pretty good once you get past the initial learning curve. the hard part isn't the code — it's knowing which wallets are worth watching.

Top comments (0)