Skip to main content
Learn how to stream and parse Pump.fun account data using Yellowstone gRPC. This guide demonstrates efficient account monitoring for trading bots, analytics platforms, and real-time DeFi applications.

Overview

For any Solana dApp, trading bot, or real-time analytics platform, efficient account data access is crucial. Yellowstone gRPC streaming offers a high-performance, low-latency alternative to traditional RPC polling and WebSockets.
What you’ll learn:
  • Stream all Pump.fun account updates in real-time
  • Decode account data using the program’s IDL
  • Track bonding curve and global account changes
  • Build production-ready account monitoring systems

Installation

npm install @triton-one/yellowstone-grpc @coral-xyz/anchor

Complete Working Example

Here’s a production-ready Pump.fun account monitor with bonding curve tracking:
const Client = require("@triton-one/yellowstone-grpc").default;
const { CommitmentLevel } = require("@triton-one/yellowstone-grpc");
const { BorshAccountsCoder } = require("@coral-xyz/anchor");
const fs = require('fs');

const PUMP_PROGRAM_ID = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P";

// Load Pump.fun IDL
const pumpFunIdl = JSON.parse(
  fs.readFileSync('./idl/pump_0.1.0.json', 'utf8')
);
const accountCoder = new BorshAccountsCoder(pumpFunIdl);

// Initialize and connect to Yellowstone gRPC
const getClient = async () => {
  let client = false;
  try {
    client = new Client(
      process.env.GRPC_ENDPOINT || "https://grpc.solanatracker.io",
      process.env.GRPC_API_KEY,
      {
        "grpc.max_receive_message_length": 100 * 1024 * 1024,
      }
    );
    const version = await client.getVersion();
    if (version) {
      console.log("Connected to Yellowstone gRPC! Version:", version);
      return client;
    }
  } catch (e) {
    console.error("Failed to connect:", e);
  }
  if (!client) {
    throw new Error("Failed to connect!");
  }
};

// Track account statistics
const stats = {
  totalUpdates: 0,
  bondingCurves: new Set(),
  completedCurves: 0,
  lastReportTime: Date.now()
};

// Handle bonding curve updates
function handleBondingCurve(data, account) {
  stats.bondingCurves.add(account.pubkey);
  
  console.log(`\n[Bonding Curve Update]`);
  console.log(`  Address: ${account.pubkey}`);
  console.log(`  Virtual Token Reserves: ${data.virtualTokenReserves}`);
  console.log(`  Virtual Sol Reserves: ${data.virtualSolReserves}`);
  console.log(`  Real Token Reserves: ${data.realTokenReserves}`);
  console.log(`  Real Sol Reserves: ${data.realSolReserves}`);
  console.log(`  Token Total Supply: ${data.tokenTotalSupply}`);
  console.log(`  Complete: ${data.complete}`);
  
  // Calculate current price
  const price = Number(data.virtualSolReserves) / Number(data.virtualTokenReserves);
  console.log(`  Current Price: ${price.toFixed(9)} SOL/token`);
  
  if (data.complete && !stats.bondingCurves.has(account.pubkey + '_completed')) {
    stats.completedCurves++;
    stats.bondingCurves.add(account.pubkey + '_completed');
    
    console.log(`\n🎉 BONDING CURVE COMPLETED!`);
    console.log(`  Address: ${account.pubkey}`);
    console.log(`  Final Sol Reserves: ${(Number(data.realSolReserves) / 1e9).toFixed(4)} SOL`);
    console.log(`  Token Supply: ${data.tokenTotalSupply}`);
  }
}

// Handle global account updates
function handleGlobalAccount(data, account) {
  console.log(`\n[Global Configuration Update]`);
  console.log(`  Address: ${account.pubkey}`);
  console.log(`  Fee Recipient: ${data.feeRecipient}`);
  console.log(`  Initial Virtual Token Reserves: ${data.initialVirtualTokenReserves}`);
  console.log(`  Initial Virtual Sol Reserves: ${data.initialVirtualSolReserves}`);
}

(async () => {
  const client = await getClient();
  const stream = await client.subscribe();

  // Handle stream lifecycle
  const streamClosed = new Promise((resolve, reject) => {
    stream.on("error", (error) => {
      console.error("Stream error:", error);
    });
    stream.on("end", () => {
      console.log("Stream ended");
      resolve();
    });
    stream.on("close", () => {
      console.log("Stream closed");
      resolve();
    });
  });

  // Handle incoming account updates
  stream.on("data", (data) => {
    if (data?.account) {
      stats.totalUpdates++;
      const accountData = data.account.account;
      
      try {
        // Decode account data
        const decodedData = accountCoder.decodeAny(accountData.data);
        
        console.log(`\n[Account Update #${stats.totalUpdates}]`);
        console.log(`  Address: ${accountData.pubkey}`);
        console.log(`  Type: ${decodedData.discriminator}`);
        console.log(`  Slot: ${data.account.slot}`);
        
        // Process based on account type
        if (decodedData.discriminator === 'BondingCurve') {
          handleBondingCurve(decodedData, accountData);
        } else if (decodedData.discriminator === 'Global') {
          handleGlobalAccount(decodedData, accountData);
        }
      } catch (error) {
        // Ignore decoding errors for unknown account types
      }
      
      // Report statistics every 100 updates
      if (stats.totalUpdates % 100 === 0) {
        const now = Date.now();
        const elapsed = (now - stats.lastReportTime) / 1000;
        const rate = 100 / elapsed;

        console.log(`\n=== Statistics ===`);
        console.log(`  Total Updates: ${stats.totalUpdates}`);
        console.log(`  Update Rate: ${rate.toFixed(2)}/sec`);
        console.log(`  Bonding Curves Tracked: ${stats.bondingCurves.size}`);
        console.log(`  Completed Curves: ${stats.completedCurves}`);

        stats.lastReportTime = now;
      }
    }
  });

  // Subscribe to Pump.fun accounts
  const request = {
    accounts: {
      pumpfun: {
        account: [],
        owner: [PUMP_PROGRAM_ID], // Stream all accounts owned by Pump.fun
        filters: []
      }
    },
    slots: {},
    transactions: {},
    transactionsStatus: {},
    entry: {},
    blocks: {},
    blocksMeta: {},
    accountsDataSlice: [],
    ping: undefined,
    commitment: CommitmentLevel.PROCESSED, // Fastest updates
  };

  // Send subscribe request
  await new Promise((resolve, reject) => {
    stream.write(request, (err) => {
      if (err === null || err === undefined) {
        console.log("✅ Monitoring Pump.fun accounts...\n");
        resolve();
      } else {
        reject(err);
      }
    });
  }).catch((reason) => {
    console.error("Subscribe failed:", reason);
    throw reason;
  });

  await streamClosed;
})();

// Graceful shutdown
process.on('SIGINT', () => {
  console.log('\n\nShutting down gracefully...');
  console.log(`Final Stats - Updates: ${stats.totalUpdates}, Curves: ${stats.bondingCurves.size}, Completed: ${stats.completedCurves}`);
  process.exit(0);
});

Pump.fun Account Types

Bonding Curve Account

The bonding curve account contains the token economics and liquidity information:
{
  virtualTokenReserves: bigint,  // Virtual token reserves for pricing
  virtualSolReserves: bigint,    // Virtual SOL reserves for pricing
  realTokenReserves: bigint,     // Actual tokens in the curve
  realSolReserves: bigint,       // Actual SOL in the curve
  tokenTotalSupply: bigint,      // Total token supply
  complete: boolean              // Whether bonding curve is complete
}
Use cases:
  • Calculate current token price
  • Monitor liquidity changes
  • Detect when bonding curve completes
  • Track token supply distribution

Global Account

The global account contains program-wide configuration:
{
  feeRecipient: string,                    // Address receiving fees
  initialVirtualTokenReserves: bigint,     // Initial virtual token reserves
  initialVirtualSolReserves: bigint,       // Initial virtual SOL reserves
  initialRealTokenReserves: bigint,        // Initial real token reserves
  tokenTotalSupply: bigint,                // Standard token total supply
  feeBasisPoints: number                   // Fee in basis points
}

Advanced Examples

Example 2: Track Specific Bonding Curves

Monitor only specific bonding curve addresses:
const BONDING_CURVES = [
  "BondingCurve1...",
  "BondingCurve2...",
  "BondingCurve3..."
];

const request = {
  accounts: {
    specificCurves: {
      account: BONDING_CURVES, // Monitor specific accounts
      owner: [],
      filters: []
    }
  },
  // ... other fields
  commitment: CommitmentLevel.CONFIRMED,
};

Example 3: Filter for Active Bonding Curves

Filter for incomplete bonding curves only:
const request = {
  accounts: {
    activeBondingCurves: {
      account: [],
      owner: [PUMP_PROGRAM_ID],
      filters: [
        { dataSize: 120 }, // Bonding curve account size
        {
          memcmp: {
            offset: 108, // Offset to 'complete' field
            bytes: Buffer.from([0]).toString('base64') // Filter for incomplete
          }
        }
      ]
    }
  },
  // ... other fields
  commitment: CommitmentLevel.CONFIRMED,
};

Example 4: Price Tracking

Track price movements across all bonding curves:
const priceHistory = new Map(); // curve address -> array of prices

function trackPrice(address, virtualSol, virtualToken) {
  const price = Number(virtualSol) / Number(virtualToken);
  
  if (!priceHistory.has(address)) {
    priceHistory.set(address, []);
  }
  
  const history = priceHistory.get(address);
  history.push({ price, timestamp: Date.now() });
  
  // Keep only last 100 prices
  if (history.length > 100) {
    history.shift();
  }
  
  // Calculate price change
  if (history.length > 1) {
    const oldPrice = history[0].price;
    const priceChange = ((price - oldPrice) / oldPrice) * 100;
    
    if (Math.abs(priceChange) > 50) { // 50% change
      console.log(`\n🚨 PRICE ALERT`);
      console.log(`  Curve: ${address}`);
      console.log(`  Change: ${priceChange.toFixed(2)}%`);
      console.log(`  Current Price: ${price.toFixed(9)} SOL`);
    }
  }
}

// Call in handleBondingCurve:
trackPrice(
  account.pubkey,
  data.virtualSolReserves,
  data.virtualTokenReserves
);

Example 5: Liquidity Monitoring

Track liquidity changes:
const liquidityTracker = new Map(); // curve -> last liquidity

function trackLiquidity(address, realSol) {
  const liquidity = Number(realSol) / 1e9; // Convert to SOL
  const previous = liquidityTracker.get(address);
  
  if (previous) {
    const change = liquidity - previous;
    const percentChange = (change / previous) * 100;
    
    if (Math.abs(percentChange) > 10) { // 10% change
      console.log(`\n💧 LIQUIDITY CHANGE`);
      console.log(`  Curve: ${address}`);
      console.log(`  Current: ${liquidity.toFixed(4)} SOL`);
      console.log(`  Change: ${change > 0 ? '+' : ''}${change.toFixed(4)} SOL (${percentChange.toFixed(1)}%)`);
    }
  }
  
  liquidityTracker.set(address, liquidity);
}

Dynamic Subscription Management

Modify your subscription on-the-fly without restarting:
async function addCurveToMonitor(stream, bondingCurveAddress) {
  await new Promise((resolve, reject) => {
    stream.write({
      accounts: {
        newCurve: {
          account: [bondingCurveAddress],
          owner: [],
          filters: []
        }
      }
    }, (err) => {
      if (err === null || err === undefined) {
        console.log(`Added ${bondingCurveAddress} to monitoring`);
        resolve();
      } else {
        reject(err);
      }
    });
  });
}

Performance Tips

Use PROCESSED Commitment

Get updates faster with CommitmentLevel.PROCESSED for time-sensitive applications

Filter Efficiently

Use dataSize and memcmp filters to reduce bandwidth and processing load

Batch Processing

Process account updates in batches for better throughput

Cache Account State

Maintain local cache of account states to detect changes efficiently

Common Use Cases

  • Trading Bot
  • Analytics Dashboard
  • Alert System
function handleBondingCurve(data, account) {
  // Calculate current price
  const price = Number(data.virtualSolReserves) / Number(data.virtualTokenReserves);
  
  // Check if price is favorable
  if (price < TARGET_PRICE && !data.complete) {
    executeBuy(account.pubkey, AMOUNT);
  }
}

IDL File

Download the Pump.fun IDL and save it as idl/pump_0.1.0.json:
{
  "version": "0.1.0",
  "name": "pump",
  "accounts": [
    {
      "name": "BondingCurve",
      "type": {
        "kind": "struct",
        "fields": [
          { "name": "virtualTokenReserves", "type": "u64" },
          { "name": "virtualSolReserves", "type": "u64" },
          { "name": "realTokenReserves", "type": "u64" },
          { "name": "realSolReserves", "type": "u64" },
          { "name": "tokenTotalSupply", "type": "u64" },
          { "name": "complete", "type": "bool" }
        ]
      }
    },
    {
      "name": "Global",
      "type": {
        "kind": "struct",
        "fields": [
          { "name": "feeRecipient", "type": "publicKey" },
          { "name": "initialVirtualTokenReserves", "type": "u64" },
          { "name": "initialVirtualSolReserves", "type": "u64" }
        ]
      }
    }
  ]
}

Resources

I