Overview

Account monitoring lets you track balance changes, data modifications, ownership transfers, and account creation/deletion events across Solana in real-time. This guide covers filtering strategies and implementation patterns for different use cases.
Prerequisites: This guide assumes you’ve completed the Yellowstone gRPC Quickstart and have a working stream setup.

Account Filtering Options

Monitor individual accounts by public keyUse this when you know exactly which accounts to watch:
const subscribeRequest: SubscribeRequest = {
  accounts: {
    accountSubscribe: {
      account: [
        "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC mint
        "So11111111111111111111111111111111111111112"   // Wrapped SOL
      ],
      owner: [],
      filters: []
    }
  },
  commitment: CommitmentLevel.CONFIRMED
};
Best for: Monitoring specific token mints, known wallets, or critical program accounts

Data Slicing

Optimize bandwidth by requesting only specific byte ranges from account data:
// Only get the balance portion of token accounts (bytes 64-72)
const subscribeRequest: SubscribeRequest = {
  accounts: {
    accountSubscribe: {
      owner: ["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"],
      filters: [{ dataSize: 165 }]
    }
  },
  accountsDataSlice: [
    { offset: 64, length: 8 } // Token balance (u64)
  ],
  commitment: CommitmentLevel.CONFIRMED
};

Practical Examples

Example 1: Monitor Large Token Holders

Track USDC accounts with significant balances:
import { StreamManager } from './stream-manager'; // From quickstart guide

async function monitorLargeUSDCHolders() {
  const streamManager = new StreamManager(
    "your-grpc-endpoint",
    "your-api-key",
    handleLargeHolderUpdate
  );

  const subscribeRequest: SubscribeRequest = {
    accounts: {
      accountSubscribe: {
        owner: ["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"],
        filters: [
          { dataSize: 165 }, // Token account size
          { 
            memcmp: { 
              offset: 0, 
              bytes: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" // USDC mint
            } 
          }
        ]
      }
    },
    accountsDataSlice: [
      { offset: 32, length: 32 }, // Owner
      { offset: 64, length: 8 }   // Balance
    ],
    commitment: CommitmentLevel.CONFIRMED
  };

  await streamManager.connect(subscribeRequest);
}

function handleLargeHolderUpdate(data: any): void {
  if (data.account) {
    const account = data.account.account;
    
    // Parse token account data
    if (account.data && account.data.length >= 8) {
      const balanceBuffer = Buffer.from(account.data.slice(64, 72), 'base64');
      const balance = balanceBuffer.readBigUInt64LE();
      const balanceInUSDC = Number(balance) / 1e6; // USDC has 6 decimals
      
      // Only log accounts with > 100,000 USDC
      if (balanceInUSDC > 100000) {
        console.log(`🐋 Large USDC Holder Update:`);
        console.log(`  Account: ${account.pubkey}`);
        console.log(`  Balance: ${balanceInUSDC.toLocaleString()} USDC`);
        console.log(`  Slot: ${data.account.slot}`);
      }
    }
  }
}

Example 2: Track Program Account Changes

Monitor all accounts owned by a specific program:
async function monitorProgramAccounts() {
  const PROGRAM_ID = "YourProgramId"; // Replace with actual program ID
  
  const streamManager = new StreamManager(
    "your-grpc-endpoint",
    "your-api-key",
    handleProgramAccountUpdate
  );

  const subscribeRequest: SubscribeRequest = {
    accounts: {
      accountSubscribe: {
        owner: [PROGRAM_ID],
        filters: []
      }
    },
    commitment: CommitmentLevel.CONFIRMED
  };

  await streamManager.connect(subscribeRequest);
}

function handleProgramAccountUpdate(data: any): void {
  if (data.account) {
    const account = data.account.account;
    console.log(`📋 Program Account Update:`);
    console.log(`  Account: ${account.pubkey}`);
    console.log(`  Owner: ${account.owner}`);
    console.log(`  Lamports: ${account.lamports}`);
    console.log(`  Data Length: ${account.data?.length || 0} bytes`);
    console.log(`  Executable: ${account.executable}`);
    console.log(`  Rent Epoch: ${account.rentEpoch}`);
  }
}

Example 3: New Account Creation Monitoring

Track when new accounts are created:
async function monitorNewAccounts() {
  const streamManager = new StreamManager(
    "your-grpc-endpoint",
    "your-api-key",
    handleNewAccountCreation
  );

  const subscribeRequest: SubscribeRequest = {
    accounts: {
      accountSubscribe: {
        owner: ["11111111111111111111111111111111"], // System Program
        filters: []
      }
    },
    commitment: CommitmentLevel.CONFIRMED
  };

  await streamManager.connect(subscribeRequest);
}

function handleNewAccountCreation(data: any): void {
  if (data.account && data.account.account.lamports === 0) {
    // New account creation typically starts with 0 lamports
    const account = data.account.account;
    console.log(`🆕 New Account Created:`);
    console.log(`  Account: ${account.pubkey}`);
    console.log(`  Owner: ${account.owner}`);
    console.log(`  Slot: ${data.account.slot}`);
  }
}

Filter Logic Reference

Understanding how filters combine:
Account-level filters (AND logic):
  • account AND owner AND filters must all match if specified
Within arrays (OR logic):
  • Any account in account array matches
  • Any owner in owner array matches
Within filters array (AND logic):
  • All dataSize and memcmp filters must match
Example:
{
  account: ["A", "B"],      // Match account A OR B
  owner: ["X", "Y"],        // AND owned by X OR Y  
  filters: [
    { dataSize: 100 },      // AND data size is 100
    { memcmp: {...} }       // AND memcmp matches
  ]
}
Token accounts for specific mint:
{
  owner: ["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"],
  filters: [
    { dataSize: 165 },
    { memcmp: { offset: 0, bytes: "MINT_ADDRESS" } }
  ]
}
SPL token accounts with minimum balance:
{
  owner: ["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"],
  filters: [
    { dataSize: 165 },
    { memcmp: { offset: 64, bytes: "MINIMUM_BALANCE_BYTES" } }
  ]
}
Program-derived accounts:
{
  owner: ["YOUR_PROGRAM_ID"],
  filters: [
    { dataSize: 200 }, // Your account size
    { memcmp: { offset: 8, bytes: "DISCRIMINATOR" } }
  ]
}

Performance Considerations

Bandwidth Optimization

Use data slicing to request only needed bytesApply strict filters to reduce unnecessary updatesChoose appropriate commitment levels for your use case

Scale Management

Start with specific accounts before using owner filtersMonitor subscription volume and adjust filters as neededImplement backpressure handling for high-volume streams

Error Handling

Common account monitoring errors and solutions:
Error: Receiving too much data or hitting rate limitsSolution: Add more specific filters:
  • Use dataSize to match exact account types
  • Add memcmp filters for specific data patterns
  • Consider using accountsDataSlice to reduce bandwidth
Error: Stream connects but no account updates appearSolution:
  • Verify account addresses are correct
  • Check if accounts actually change frequently
  • Try PROCESSED commitment for more frequent updates
  • Test with a known active account first

Next Steps