Skip to main content

Documentation Index

Fetch the complete documentation index at: https://www.helius.dev/docs/llms.txt

Use this file to discover all available pages before exploring further.

Stream every Pump.fun account update and transaction in real time using the LaserStream SDK. This page walks through a complete TypeScript script you can run end-to-end: it prints each transaction as it lands, reports rolling stats (throughput, unique fee payers, total fees in SOL) once a minute, and stays connected across reconnects automatically. It’s a useful starting point for anything that needs continuous Pump.fun visibility — trading bots, analytics dashboards, alerting pipelines, or just exploring the data shape so you can build something more specialised on top.
Prerequisites: A Helius API key (Business or Professional plan for Mainnet LaserStream) and Node.js 18+. Familiarity with Account Subscriptions and Transaction Monitoring helps but isn’t required.

Setup

Create a fresh project, install the SDK + bs58 (used to decode transaction signatures), and set your API key:
mkdir pump-monitor && cd pump-monitor
npm init -y
npm install helius-laserstream bs58
npm install --save-dev typescript ts-node @types/node
npx tsc --init
export HELIUS_API_KEY=your-key-here
Pick the endpoint closest to where this script will run — see the LaserStream regions.

The script

Save this as index.ts, then run npx ts-node index.ts:
import { subscribe, CommitmentLevel, LaserstreamConfig, SubscribeRequest } from 'helius-laserstream';
import bs58 from 'bs58';

const PUMP_PROGRAM_ID = '6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P';

// Rolling stats across the session.
const stats = {
  startedAt: Date.now(),
  transactions: 0,
  successes: 0,
  failures: 0,
  uniquePayers: new Set<string>(),
  totalFeesLamports: 0,
  accountUpdates: 0,
};

async function handleUpdate(data: any) {
  if (data.transaction) {
    const tx = data.transaction.transaction;
    if (!tx) return;

    stats.transactions++;
    const failed = !!tx.meta?.err;
    failed ? stats.failures++ : stats.successes++;

    // tx.meta.fee is a u64 string — coerce before adding.
    stats.totalFeesLamports += Number(tx.meta?.fee ?? 0);

    // Fee payer is the first account in the message.
    const firstKey = tx.transaction?.message?.accountKeys?.[0];
    if (firstKey) {
      const payer = typeof firstKey === 'string' ? firstKey : bs58.encode(firstKey);
      stats.uniquePayers.add(payer);
    }

    // tx.signature is a Buffer in the SDK.
    const sig = bs58.encode(tx.signature);
    const slot = data.transaction.slot;
    const flag = failed ? '❌' : '✅';
    console.log(`${flag}  ${sig.slice(0, 12)}…  slot ${slot}  fee ${tx.meta?.fee} lamports`);
  }

  if (data.account) {
    stats.accountUpdates++;
    const acct = data.account.account;
    const pubkey = typeof acct.pubkey === 'string' ? acct.pubkey : bs58.encode(acct.pubkey);
    console.log(`📋  account ${pubkey}  ${acct.data?.length ?? 0} bytes`);
  }
}

function printReport() {
  const minutes = (Date.now() - stats.startedAt) / 60_000;
  console.log('\n📊  Pump.fun activity report');
  console.log(`   Runtime:           ${minutes.toFixed(1)} min`);
  console.log(`   Transactions:      ${stats.transactions} (${stats.successes} ok, ${stats.failures} failed)`);
  console.log(`   Throughput:        ${(stats.transactions / Math.max(minutes, 0.0001)).toFixed(1)} tx/min`);
  console.log(`   Unique fee payers: ${stats.uniquePayers.size}`);
  console.log(`   Total fees:        ${(stats.totalFeesLamports / 1e9).toFixed(4)} SOL`);
  console.log(`   Account updates:   ${stats.accountUpdates}\n`);
}

async function main() {
  const config: LaserstreamConfig = {
    apiKey: process.env.HELIUS_API_KEY ?? 'YOUR_API_KEY',
    endpoint: 'https://laserstream-mainnet-ewr.helius-rpc.com', // pick the region closest to you
  };

  const subscriptionRequest: SubscribeRequest = {
    accounts: {
      'pump-accounts': {
        account: [],
        owner: [PUMP_PROGRAM_ID],
        filters: [],
      },
    },
    transactions: {
      'pump-transactions': {
        accountInclude: [PUMP_PROGRAM_ID],
        accountExclude: [],
        accountRequired: [],
        vote: false,
        failed: false,
      },
    },
    commitment: CommitmentLevel.CONFIRMED,
    slots: {},
    transactionsStatus: {},
    blocks: {},
    blocksMeta: {},
    entry: {},
    accountsDataSlice: [],
  };

  console.log('🚀  Streaming Pump.fun activity. Press Ctrl+C to stop.\n');

  const reportTimer = setInterval(printReport, 60_000);
  process.on('SIGINT', () => {
    clearInterval(reportTimer);
    printReport();
    process.exit(0);
  });

  await subscribe(config, subscriptionRequest, handleUpdate, async (error) => {
    console.error('Stream error:', error);
  });
}

main().catch(console.error);

What the script does

One subscription, two filter blocks

A single subscribe(...) call carries two named filter groups:
  • The accounts filter receives every account owned by the Pump.fun program (bonding curves, mint state, etc.) every time their data changes.
  • The transactions filter receives every transaction that interacts with the Pump.fun program. The vote: false, failed: false flags drop votes and failed transactions before they reach your handler.
Both arrive over a single gRPC stream, so the SDK only has one connection to manage. If it drops, the SDK reconnects automatically and you keep receiving updates from both filters.

Reading the raw fields

A couple of values in the payload need a small conversion before they’re easy to use:
  • tx.signature is a Buffer. Run it through bs58.encode(...) to get the base58 signature you’d see in an explorer or in a getTransaction response.
  • tx.meta.fee is a u64 stored as a string so values above Number.MAX_SAFE_INTEGER don’t lose precision in JavaScript. Wrap it in Number(...) before doing arithmetic. The same string-for-u64 convention applies to several block-level fields too — see Slot & Block Monitoring for the full data shape.

Rolling stats

A setInterval prints a snapshot every 60 seconds: total transactions, successes versus failures, throughput, unique fee payers, and total fees in SOL. Press Ctrl+C for one last snapshot before the script exits.

Extending the script

A few directions to take this further:
  • Decode instruction discriminators to label trades as buy / sell / create — see Decoding Transaction Data for the parsing pattern.
  • Persist the rolling stats to a database (Postgres, Redis, ClickHouse) instead of in-memory Set / counters.
  • Alert on thresholds — large fee payers, sudden volume spikes, new bonding-curve accounts.
  • Replay missed activity on startup by setting fromSlot on SubscribeRequest (up to 24 hours back — see Historical Replay).
  • Combine with Slot & Block Monitoring for block-level context.

Next Steps

Transaction Monitoring

The general transaction-filtering patterns this script uses.

Decoding Transaction Data

Parse the binary transaction payload into readable Solana transactions.

Account Subscriptions

Watch specific account state changes with filters.

Historical Replay

Replay up to 24h of past Pump.fun activity from a starting slot.