How to track Solana DEX pools in real time: Complete guide

Written by:

Olha Diachuk

15

min read

Date:

October 22, 2026

Updated on:

October 22, 2026

To track Solana DEX pools in real time, first fetch an on-chain snapshot of the pools relevant to your strategy, then maintain that state from account-update streams. To enrich data faster and at lower costs, RPC Fast Quark accelerates eligible, filtered getProgramAccounts discovery requests; Yellowstone gRPC supplies ongoing updates, while periodic reconciliation repairs state after disconnects.

This snapshot-plus-stream architecture gives trading bots a consistent starting set of pool accounts without placing repeated expensive scans on the decision path. It also separates two different latency problems: finding pools and observing subsequent changes.

TL;DR

  • Discovery is not monitoring. getProgramAccounts returns a snapshot; it does not push later swaps, liquidity changes, or newly created pools.
  • Quark is selective. Eligible mainnet DeFi queries use indexed account paths, but unsupported programs and filters do not get the same acceleration.
  • Stream the account state. Standard WebSocket subscriptions work for smaller sets; Yellowstone gRPC is a stronger backend option when program-wide event volume and filtering matter.
  • Recover explicitly. A restarted stream alone doesn't prove no events were missed. Reconcile current pool state against a fresh snapshot or targeted account reads.

Benchmark an eligible Quark pool lookup before choosing a streaming plan:

Start RPC Fast for free

Why DEX pool tracking changed everything

Pool state determines the route, obtainable output, estimated price impact, and viability of a DeFi trade. For an arbitrage engine, a quote calculated against stale state is not merely less accurate: it creates false opportunities, increases rejected transactions, and burns fee and infrastructure budget.

Solana amplifies this problem because trading activity changes multiple venues within a short execution window. A bot tracking Raydium, Orca, Meteora, and PumpSwap must distinguish a newly discovered market from an existing market whose price or available liquidity has moved. The required data path is different in each case.

The familiar approach—polling each pool or repeating a large program scan—is a poor default. A one-second polling interval already imposes up to roughly one second of observation delay before networking and decoding; broad queries also consume backend CPU, memory, bandwidth, and request quotas. Tightening the poll interval increases load without solving gaps or providing earlier transaction visibility.

The operational consequence: the discovery mechanism belongs outside the time-critical decision loop. Real-time Solana pool tracking needs a baseline, a continuous change feed, and a recovery path.

How Solana DEX pools work

Source: Best Solana DEX Platforms 2026 

What is a pool

A decentralized exchange pool is a collection of program-owned accounts implementing a liquidity and swap model. Users trade against the pool's assets or concentrated liquidity positions rather than an exchange-operated order book. Each DEX program defines its own account layouts, fees, pricing formulas, and auxiliary state.

Solana liquidity pool tracking therefore starts with the program ID and account type, not with a token pair alone. A SOL/USDC pair might have several pools on the same protocol, distinguished by fee tier, tick spacing, or deployment. The same pair across different protocols is a separate set of accounts and execution rules.

Pool state on-chain

Solana account data is stored as bytes under a program owner. getProgramAccounts enumerates matching accounts belonging to a program and supports dataSize and memcmp filters. getAccountInfo reads one known address; getMultipleAccounts reads several. These operations provide current state, not a historical trade feed. 

For concentrated-liquidity pools such as Orca Whirlpools, the pool account includes current sqrt_price, liquidity, the current tick, token mints, and vault addresses. Tick arrays store liquidity changes at price boundaries. For bin-based liquidity, such as Meteora DLMM, the active bin and neighboring bin states matter. Raydium's constant-product and CLMM designs likewise require different decoders.

Source: Orca official website

A generic parser that assumes every DEX stores reserveA / reserveB as its executable price is wrong. Protocol-specific state defines whether the result is a spot reference, an estimated quote, or actual executable output for a trade size.

How price changes happen

In a constant-product pool, trading changes token reserves and shifts the marginal price along the invariant x × y = k, before accounting for fees and protocol-specific accounting. In concentrated liquidity, swaps move the active sqrt_price and potentially cross ticks. In a bin-based pool, available liquidity and the active bin change as a swap traverses bin ranges.

Thus, Solana pool price tracking involves more than reading vault balances. For Orca, the mid-price derived from sqrtPriceX64 helps with monitoring; a tradable quote also depends on liquidity across ticks, swap size, fees, token decimals, and Token-2022 behavior. A large apparent price discrepancy is not an arbitrage unless both legs remain profitable after execution effects.

Pool components to track

Component Why it matters Typical source
Pool account and owner Identify the protocol and layout Program-owned account
Mint A/mint B, decimals Normalize amounts and orientation Pool state + token mint accounts
Current price or active tick/bin Detect immediate state movement Pool account
Vault balances/virtual reserves Estimate depth and inventory SPL token accounts + protocol accounting
Tick arrays/bin arrays Quote price impact beyond the current level Auxiliary program accounts
Fees and status flags Determine whether a route is executable Pool configuration
Slot and commitment Measure freshness and rollback exposure RPC context/stream update

Liquidity pool changes are not interchangeable with DEX trade detection. Deposits, withdrawals, fees, parameter changes, and swaps update different subsets of state. If the use case requires classifying individual trades, add transaction or log decoding instead of inferring every price movement is a swap.

Methods to track pools in real time

Method 1: RPC Direct

Solana RPC pool tracking uses HTTP requests such as getProgramAccounts, getAccountInfo, and getMultipleAccounts. Its strength is deterministic state retrieval: you ask for a defined account set and receive a response at a declared commitment level. It is ideal for startup snapshots, repairs, audits, and occasional price checks.

The weakness is continuous observation. Repeating getProgramAccounts across an entire DEX is especially expensive, and unfiltered calls become less predictable as the account universe grows. Even a fast indexed lookup is still a response to a request, not an account-change notification.

Use direct RPC to monitor Solana liquidity pools when the monitored set is small, the refresh interval is relaxed, or the objective is correcting a local materialized view. For large production bots, put direct reads behind a cache and a scheduled reconciliation process.

Method 2: WebSocket APIs

WebSocket subscriptions replace polling with server-pushed changes. accountSubscribe watches one account, while programSubscribe watches accounts owned by a program with optional filters. The notification includes a context slot and current account data. Solana accountSubscribe and Solana programSubscribe.

Solana WebSocket subscriptions: Complete guide for wallets, explorers, indexers, and monitoring systems
Market insights
Written by:
Date:
19 Oct 26
15
min read

WebSocket pool updates Solana is a common integration path for dashboards, alerting services, or a modest watchlist. RPC subscriptions Solana still require reconnect logic and explicit re-subscription; events missed while disconnected are not magically restored. For Account subscription Solana workflows, each update must be decoded against the correct protocol layout.

At scale, the problem is subscription management, message fanout, and client backpressure. If one process tracks thousands of accounts across several programs, a structured backend stream is generally easier to partition and consume than a growing collection of individual subscriptions.

Method 3: Indexer Services

An indexer maintains a derived database or API representation of pool accounts and transactions. It helps query an entire historical market universe, filter by liquidity, and answer analytics questions that ordinary RPC does not index efficiently.

Solana indexer tracking reduces the amount of custom account decoding and database work, but moves the freshness question to the indexer's ingest, queue, processing, and serving layers. Aggregated price feeds may also conceal which exact pool, commitment level, or routing assumptions generated a displayed price.

Jupiter pool tracking is a distinct task from consuming Jupiter quotes. A Jupiter route quote represents a selected trading route and its constraints; it is not an exhaustive change log of every underlying pool account. Verify each API's coverage and timestamp semantics before using aggregated output as a trading trigger.

Comparing speed vs cost vs reliability for these methods

Method Best job Freshness driver Cost profile Main reliability limitation
HTTP RPC Snapshots, reconciliation Request schedule + RPC latency Per-request/CU + data transfer Polling gaps, costly scans
WebSocket PubSub Known pools, modest dashboards Provider account notifications Connection/subscription quotas Re-subscribe and missed-update recovery
Yellowstone gRPC High-volume live account and transaction feeds Geyser processing + network delivery Streaming plan or node cost Consumer lag, filtering, reconnects
Managed indexer Full catalog, history, search Indexer ingest and materialization delay Subscription, records, or queries Indexed-state lag and coverage gaps
Quark + Yellowstone Filtered bootstrap plus live monitoring Quark snapshot + gRPC events RPC/CU plus stream tier Requires eligible filters and reconciliation

No single method wins on latency across the board: an indexed lookup's server execution time, a stream's delivery delay, and an indexer's data age measure different things.

Choose the method by the unit of work you need, leaving aside a vendor's lowest millisecond figure.

Setup guide: Which method to use

Step-by-step for RPC Direct

1. Identify the pool universe.

Define protocols, program IDs, account layouts, token mint pairs, and liquidity criteria. If the goal is every pool ever created across an entire DEX, use a historical backfill or indexer. 

Remember that Quark's accelerated path works only for eligible filtered lookups.

2. Register with RPC Fast.

Create a project, copy the Solana RPC endpoint, and test an ordinary getHealth call. The free Start tier includes Quark for supported query shapes at 1 RPS; ordinary methods have a 15 RPS ceiling. Those are method-specific limits, not a promise of 16 additive RPS. Confirm credit usage in your dashboard.

3. Send a real filtered pool query.

For Orca Whirlpools, use the mainnet program whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc. The on-chain Whirlpool layout has a 653-byte account; the mint fields begin at byte offsets 101 and 181.

The example below searches the USDC/WSOL orientation. Use actual mint addresses and check token order for other pairs:

curl "$RPC_FAST_URL" -H 'Content-Type: application/json' -d '{
  "jsonrpc":"2.0", "id":1, "method":"getProgramAccounts",
  "params":[
    "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc",
    {"commitment":"confirmed","encoding":"base64","withContext":true,
     "filters":[
       {"dataSize":653},
       {"memcmp":{"offset":101,"bytes":"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"}},
       {"memcmp":{"offset":181,"bytes":"So11111111111111111111111111111111111111112"}}
     ]}
  ]
}'

4. Record the snapshot slot, account keys and bytes.

Keep the response context alongside the data. Using dataSlice: {"offset":0,"length":0} helps you discover pubkeys cheaply, but it doesn't provide the bytes needed for a price calculation. Fetch full account data for the monitored subset.

5. Avoid polling the entire program.

If you track 20 token-pair lookups on free Start, the 1 RPS Quark limit means at least 20 seconds of request scheduling, before network and serialization. That makes a one-query-per-pair refresh loop unsuitable for sub-second trading signals, no matter how low Quark's individual server lookup latency is.

Alternative for WebSocket

Connect to the WebSocket URL supplied by your RPC provider, subscribe with accountSubscribe for each known pool or with filtered programSubscribe, and specify the same commitment as your snapshot. For example:


{"jsonrpc":"2.0","id":1,"method":"accountSubscribe",
 "params":["YOUR_WHIRLPOOL_ACCOUNT",
           {"encoding":"base64","commitment":"confirmed"}]}

For Track Solana pools live dashboards, persist the subscription configuration, re-send it after reconnect, and reconcile current account data. A WebSocket stream solves notification scheduling; it does not solve historical cataloging or client-side consistency on its own.

Alternative for Indexer

Choose an indexer if discovery covers tens of thousands of pools, historical trades, token metadata, or cross-DEX search. Ingest its catalog into a database keyed by (protocol, programId, poolPubkey); record the source slot or update time; then switch active watched pools to direct gRPC subscriptions where fresh state is essential.

For Solana API pool monitoring, require explicit documentation of which DEX versions, pool types, token programs, and commitment levels are indexed. Without a defensible freshness field, an indexer is a discovery service rather than a real-time trading oracle.

Quark: Accelerated discovery tool

Quark is RPC Fast's indexed account-query engine. The standard RPC endpoint routes qualifying mainnet getProgramAccounts and supported getProgramAccountsPaginated requests to specialized account indexes. The wire format stays compatible with the normal Solana method, so there is no separate Quark URL or client library.

Technical overview || Official webpage

The request must match the supported program, commitment, base64 encoding, and filters. Orca and Meteora mint-indexed lookups require both token mints; several Raydium and PumpSwap layouts accept one or both. Quark does not accelerate getAccountInfo, getMultipleAccounts, transaction submission, swap simulation, or gRPC delivery.

A critical SDK inconvenience: Orca's higher-level fetchWhirlpoolsByTokenPair flow derives pool addresses and calls getMultipleAccounts instead of sending the indexed two-mint getProgramAccounts query. Likewise, size-only Raydium full-program scans do not automatically qualify. Inspect the actual RPC method and filters emitted by your SDK; “Orca supported” is not equivalent to “every Orca lookup accelerated.”

For comprehensive pool enumeration, Quark is one specialized path among several. A broad unfiltered scan belongs on a suitable paid RPC/indexer path; use pagination when supported to cap response size. 

RPC Fast's getProgramAccountsPaginated uses an opaque continuation key; indexed and non-indexed query shapes have different documented page limits. Never treat page boundaries as a consistent historical snapshot without a separate slot-consistency strategy. 

Large pools’ catalogs have a second operational cost: even if account reads finish quickly, decoding thousands of records, fetching dependent mint metadata, and storing tick or bin accounts often dominate startup time. Measure this downstream fanout independently from Quark request latency.

Code implementation

Working examples: Fetch an Orca pool snapshot

The following TypeScript uses raw JSON-RPC deliberately, so the request shape remains visible. Install Node.js 20+, web3.js, @solana/spl-token, @triton-one/yellowstone-grpc, and bs58 as appropriate. Save the code as pool.ts and set RPC_FAST_URL to your project endpoint. The SOL/USDC example is for mainnet.


npm init -y
npm pkg set type=module
npm install @solana/web3.js @solana/spl-token @triton-one/yellowstone-grpc bs58
npm install -D tsx typescript @types/node
// pool.ts
import { Connection, PublicKey } from '@solana/web3.js';
import { getMint } from '@solana/spl-token';

export const PROGRAM = 'whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc';
export const MINT_A = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'; // USDC
export const MINT_B = 'So11111111111111111111111111111111111111112'; // WSOL
const URL = process.env.RPC_FAST_URL!;

export type Pool = { key: string; bytes: Buffer; slot: number };

export async function fetchSnapshot(): Promise<{ slot: number; pools: Pool[] }> {
  if (!URL) throw new Error('Set RPC_FAST_URL');
  const response = await fetch(URL, {
    method: 'POST', headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'getProgramAccounts',
      params: [PROGRAM, { commitment: 'confirmed', encoding: 'base64',
        withContext: true, filters: [
          { dataSize: 653 },
          { memcmp: { offset: 101, bytes: MINT_A } },
          { memcmp: { offset: 181, bytes: MINT_B } }
        ] }] })
  });
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  const reply = await response.json();
  if (reply.error) throw new Error(JSON.stringify(reply.error));
  return { slot: reply.result.context.slot,
    pools: reply.result.value.map((p: any) => ({
      key: p.pubkey, bytes: Buffer.from(p.account.data[0], 'base64'),
      slot: reply.result.context.slot
    })) };
}

export function spotPriceBperA(data: Buffer, decA: number, decB: number) {
  if (data.length !== 653) throw new Error('Unexpected Whirlpool account length');
  // Whirlpool sqrt_price is little-endian u128 at bytes 65..80.
  const lo = data.readBigUInt64LE(65);
  const hi = data.readBigUInt64LE(73);
  const sqrtX64 = (hi << 64n) | lo;
  const sqrt = Number(sqrtX64) / 2 ** 64;
  return sqrt * sqrt * 10 ** (decA - decB);
}

export async function loadDecimals(): Promise<[number, number]> {
  const conn = new Connection(URL, 'confirmed');
  const [a, b] = await Promise.all([
    getMint(conn, new PublicKey(MINT_A)), getMint(conn, new PublicKey(MINT_B))
  ]);
  return [a.decimals, b.decimals];
}

This decoder produces a spot indication in units of token B per token A, not a guaranteed executable quote. For SOL per USDC, invert the value to report USDC per SOL, assuming nonzero price. Real routing requires tick arrays, fee computation, trade size, and token-program handling. The byte offsets and account length follow the Orca program's Whirlpool struct.

Code examples: Subscribe to live changes

For the continuous path, use Yellowstone gRPC from RPC Fast's Stream plan or another eligible streaming arrangement. 

The example below tracks the same Whirlpool account layout and token-mint orientation as the Quark snapshot. Yellowstone's accounts.owner and account-data filters avoid consuming unrelated DeFi programs. Find more here: Yellowstone specification and Triton TypeScript examples.

// stream.ts -- exported filter shared by the listener and bot
import { CommitmentLevel, type SubscribeRequest }
  from '@triton-one/yellowstone-grpc';
import { PROGRAM, MINT_A, MINT_B } from './pool.js';

export const subscription: SubscribeRequest = {
  accounts: { orcaPair: { account: [], owner: [PROGRAM], filters: [
    { dataSize: 653 },
    { memcmp: { offset: 101, data: { base58: MINT_A } } },
    { memcmp: { offset: 181, data: { base58: MINT_B } } }
  ] } },
  slots: { clock: { filterByCommitment: true } },
  transactions: {}, transactionsStatus: {}, blocks: {}, blocksMeta: {},
  entry: {}, accountsDataSlice: [], commitment: CommitmentLevel.CONFIRMED
};

A basic streaming listener then creates a client, calls subscribe(), attaches data/error handlers, and writes this filter. For production, use the complete bot.ts below; it adds recovery logic and avoids opening an extra connection when importing stream.ts.

This request is a filter definition. The complete bot below attaches the stream handlers, manages buffering, and reconnects. Review exported types against the installed Yellowstone client version.

Performance comparison

Speed benchmarks, cost analysis, accuracy comparison

RPC Fast's September 11, 2026 Quark benchmark compares filtered DeFi discovery requests against Agave for the same query shape. The following subset uses confirmed commitment, warm-repeat median, and full account data. These are vendor measurements of request execution, not independent end-to-end pool-update latency tests.

Pool-discovery query Quark median Agave median Relative latency
PumpSwap AMM pair 0.366 ms 38.26 s 104,595×
Orca Whirlpool pair 0.678 ms 4.59 s 6,771×
Meteora DLMM pair 0.599 ms 3.98 s 6,645×
Raydium CLMM pair 0.641 ms 9.55 s 14,898×
Raydium CPMM pair 0.856 ms 1.86 s 2,172×

This benchmark distinguishes first pass from warm repeats, tests several commitments and payload shapes, and does not claim cleared caches before the first pass. Request slots and payload sizes also vary with live chain state. Network round-trip time, JSON parsing, account decoding, strategy logic, and order landing lie outside those backend medians.

Illustrative latency budget, not a benchmark: If a Quark lookup spends 0.7 ms executing, but the consumer takes 30 ms round-trip and 2 ms decoding, the observed step is at least ~32.7 ms. If the pool changes again while that call is in flight, a fast lookup still returns a snapshot from a particular slot, not a guaranteed latest executable price.

The economic comparison also needs request frequency. Assume, for illustration, a 10-CU charge for each getProgramAccounts call and a 30-day month. One request per minute equals 43,200 calls and 432,000 CU monthly; a continuous one request per second equals 2,592,000 calls and 25.92M CU. 

Verify the current Quark-specific CU charge in the dashboard rather than treating this assumption as a billing guarantee. With a 1.5M-CU free allocation, a sustained 1-RPS scan may exhaust credits long before month-end.

A Stream plan is listed at $249/month and includes Yellowstone gRPC; the free Start plan lists the confirmed Quark-specific allowance as 1 RPS. A Focus plan at $45/month unlocks broader paid RPC methods but doesn't replace a Yellowstone-enabled tier.

Indexer costs depend on historical retention, indexed programs, event volume, and query billing; avoid comparing a per-stream subscription price with a per-request price without normalizing workload. For an apples-to-apples operating cost, define the number of monitored pools, update events per day, average event payload, read-repair frequency, data retention, and number of consumer replicas. A streaming bill might replace millions of polls but introduce queue storage and database-write costs. Both belong in the total cost model.

Finally, distinguish accuracy from speed. On-chain pool monitoring reads state at a stated commitment. An indexer's derived price may lag, a processed update may roll back, and a correct mid-price still differs from a size-aware executable quote. Compare sources by slot, mint orientation, account version, and quote assumptions before declaring one feed more accurate.

Best practices & Optimization

Mempool monitoring (pre-detection)

Solana does not expose a conventional public, Ethereum-style pending-transaction mempool. A bot interested in Token sniping preparation or earlier DEX opportunity detection instead looks at transaction flow routed toward leaders, shred-derived feeds, or specialized early-data services. Shredstream and RPC Fast Aperture TxStream address different stages of that transaction-visibility pipeline; neither turns a proposed transaction into finalized state.

Treat a pre-execution signal as hypothetical. The transaction might fail, be reordered, never land, or execute against a different state. Keep predicted state separate from observed account state, and reconcile both with confirmations.

An incoming transaction is not proof that the targeted swap will pass slippage checks, account locks, liquidity constraints, or subsequent transaction ordering. Measure the precision of speculative signals against confirmed on-chain outcomes and disable automated execution when the disagreement rate exceeds a defined threshold. Ordinary Yellowstone account updates remain the authoritative basis for a post-execution pool-state cache at their selected commitment.

Cache pool data

Cache decoded pool state by (programId, poolPubkey) and retain last update slot, commitment, and write version where available. Keep metadata such as token decimals separately from volatile price and liquidity fields. A 10-minute-old decimal value may be acceptable; a 10-minute-old price is not.

For pool price alerts Solana, attach the originating slot and feed age to each alert. Suppress repeated notifications from duplicate events, and mark the cache stale when a stream stalls. If quote-critical auxiliary accounts such as tick arrays change, invalidate quotes that depend on them even when the main pool account does not change.

Batch requests (parallel queries)

Use getMultipleAccounts for many known pubkeys; it reduces HTTP overhead compared with individual getAccountInfo requests, but it does not enter Quark's accelerated getProgramAccounts path. Parallelize independent targeted reads within your provider's quotas, bound concurrency, and retry with jitter on 429 and transient 5xx errors.

For real time price feeds Solana, parallelize by data dependency rather than issuing unnecessary queries. Fetch mint metadata once, update volatile pool state from the stream, and query auxiliary tick or vault accounts only when the strategy requires a size-aware quote.

Use dedicated RPC (RPC Fast advantage—Quark)

Quark is available through supported shared RPC Fast plans, including its limited free Start path; it does not require a dedicated node. Dedicated infrastructure enters the design when custom Geyser filters, high throughput, predictable isolation, geographic locality, or specialized indexers outweigh managed SaaS simplicity.

The two decisions are independent: use Quark to reduce supported discovery work; use dedicated resources when the overall workload needs isolation.

Track p50, p95, and p99 lookup response time, gRPC receive lag, bytes per update, slot freshness, reconnect count, missed-event recovery duration, quota errors, and percent of pool lookups actually matching Quark eligibility. Publishing only a fast median without tail latency and stale-state rates hides the principal failure modes of production trading systems.

Integration into trading bots

Architecture

A maintainable trading bot pool tracking system has distinct data-plane and decision-plane components:

Arbitrage bot Solana pools need two independent measurements: the time from on-chain state change to a correctly decoded signal, and the time from the signal to a landed, confirmed transaction. An improvement in Quark bootstrap latency does not imply improved landing probability. Measure complete Solana arbitrage detection and execution paths separately.

For real-time DEX pool monitoring, startup consistency matters: establish a stream and buffer updates, then load a snapshot, apply buffered updates newer than the snapshot slot, and enter live mode. The simpler “snapshot first, subscribe second” flow has a race window in which swaps may arrive after the snapshot but before the subscription is active. If implemented in that order, perform an additional reconciliation immediately after subscribing.

Complete bot example

The following bot.ts is a monitoring and paper-alert bot, not an automatic transaction signer. It imports the complete snapshot and Orca spot-price functions from pool.ts, waits for a live slot before taking a snapshot, buffers concurrent account changes, avoids processing older slots, and repeats bootstrap after any stream failure. It detects price movements larger than a configurable basis-point threshold.

// bot.ts -- run: npx tsx bot.ts
import Client, { CommitmentLevel } from '@triton-one/yellowstone-grpc';
import bs58 from 'bs58';
import { fetchSnapshot, loadDecimals, spotPriceBperA } from './pool.js';
import { subscription } from './stream.js';

const sleep = (ms: number) => new Promise(r => setTimeout(r, ms));
const thresholdBps = Number(process.env.ALERT_BPS ?? 25);
type State = { slot: number; writeVersion: bigint; price: number };
type Event = { key: string; slot: number; writeVersion: bigint; bytes: Buffer };
const state = new Map<string, State>();
const [decA, decB] = await loadDecimals();

function apply(e: Event) {
  const prev = state.get(e.key);
  if (prev && (e.slot < prev.slot ||
      (e.slot === prev.slot && e.writeVersion <= prev.writeVersion))) return;
  const price = spotPriceBperA(e.bytes, decA, decB);
  if (!Number.isFinite(price) || price <= 0) return;
  state.set(e.key, { slot: e.slot, writeVersion: e.writeVersion, price });
  if (!prev) return;
  const deltaBps = (price / prev.price - 1) * 10_000;
  if (Math.abs(deltaBps) >= thresholdBps) {
    console.log(JSON.stringify({ type: 'paper-alert', pool: e.key,
      slot: e.slot, deltaBps: +deltaBps.toFixed(2), priceBperA: price }));
  }
}

async function runSession() {
  const client = new Client(process.env.YELLOWSTONE_URL!,
                            process.env.YELLOWSTONE_TOKEN!);
  await client.connect();
  const stream = await client.subscribe();
  const queued: Event[] = [];
  let buffering = true;
  let firstSlotResolve!: (slot: number) => void;
  const firstSlot = new Promise<number>(r => { firstSlotResolve = r; });
  let gotSlot = false;
  const ended = new Promise<void>(resolve => {
    stream.once('error', e => { console.error('Stream error', e); resolve(); });
    stream.once('end', resolve);
    stream.once('close', resolve);
  });

  try {
    stream.on('data', msg => {
      if (msg.slot && !gotSlot) {
        gotSlot = true;
        firstSlotResolve(Number(msg.slot.slot));
      }
      if (!msg.account?.account?.pubkey || !msg.account.account.data) return;
      const a = msg.account.account;
      const event: Event = {
        key: bs58.encode(Buffer.from(a.pubkey)), slot: Number(msg.account.slot),
        writeVersion: BigInt(a.writeVersion.toString()),
        bytes: Buffer.from(a.data)
      };
      if (buffering) queued.push(event); else apply(event);
    });

    await new Promise<void>((resolve, reject) => stream.write(
      subscription, e => e ? reject(e) : resolve()
    ));
    const streamSlot = await Promise.race([
      firstSlot,
      ended.then(() => { throw new Error('Stream closed during bootstrap'); }),
      sleep(15_000).then(() => { throw new Error('No live slot received'); })
    ]);

    // The new stream is receiving slots. Fetch an indexed baseline.
    const baseline = await fetchSnapshot();
    if (baseline.slot + 1 < streamSlot) {
      throw new Error('Snapshot older than stream; retry with backoff');
    }
    state.clear();
    for (const p of baseline.pools) {
      const price = spotPriceBperA(p.bytes, decA, decB);
      state.set(p.key, { slot: baseline.slot, writeVersion: -1n, price });
    }
    // Confirmed snapshot contains state through baseline.slot.
    for (const e of queued) if (e.slot > baseline.slot) apply(e);
    buffering = false;
    queued.length = 0;
    console.log('Monitoring', state.size, 'Orca pools from slot', baseline.slot);
    await ended;
  } finally {
    stream.end();
  }
}

for (;;) {
  try { await runSession(); }
  catch (e) { console.error('Resynchronizing:', e); }
  await sleep(2_000); // Respect Start's 1-RPS Quark limit on retries.
}

Save the small Yellowstone subscription object from the previous section as stream.ts, exporting subscription. Set YELLOWSTONE_URL, YELLOWSTONE_TOKEN, RPC_FAST_URL, and optionally ALERT_BPS, then run npx tsx bot.ts. API credentials belong in environment variables or a secret manager, not source control.

This compact example has visible limits. 

  • It assumes a supported Orca Whirlpool layout, a mainnet USDC/WSOL mint ordering, and a stable comparison between the two providers' confirmed slots. 
  • It reloads all matching pools after a disconnect instead of replaying historical events. 

A production bot needs a bounded queue, timeouts and cancellation for every network operation, persistence across process restarts, safeguards against same-slot state races, stream health metrics, and independent price/quote validation. If the streaming provider supports bounded fromSlot replay, use it as an optimization; do not treat replay as a replacement for reconciliation. 

The strategy boundary should also remain explicit. A pool state monitoring engine supplies observed data; a trade executor must check current route liquidity, transaction simulation, acceptable slippage, priority fees, a recent blockhash, and actual landing/confirmation before counting a signal as profitable. 

Keep trading disabled during bootstrap, stream outages, materialization backlog, or an unresolved fork. Persist the last healthy slot and expose a readiness endpoint whose status reflects pool-data freshness rather than process uptime. An online container with a stale cache is not a healthy trading data source.

Choose the method by your workload

The terms Track Solana DEX pools real time, Real time Solana pool tracking, Monitor DEX pools Solana, Track pool prices Solana, and Real time pool monitoring refer to different engineering tasks despite their overlap. 

The decision matrix below translates them into explicit infrastructure requirements.

Workload / related search intent Recommended path Decision rule
Track Solana pools live/On-chain pool monitoring WebSocket or Yellowstone gRPC Use push notifications for ongoing state, not repeated full scans
Raydium pool tracking/Orca pool monitoring Quark discovery + protocol-specific stream decoding Verify mint filters, account layouts, and SDK request shapes
Solana liquidity pool tracking/Solana pool price tracking Account stream + auxiliary liquidity accounts Mid-price alone is insufficient for a size-aware trading quote
Real time DEX pool monitoring/Solana API pool monitoring Yellowstone + durable state cache Monitor slot freshness and apply gap recovery
Trading bot pool tracking/DEX opportunity detection Stream + quote/risk engine Separate observed opportunity from executable profit
Historical universe and pool creation Indexer/backfill + filtered live discovery Use full catalog data; Quark alone is not an all-pools enumerator

A good production design starts with the smallest set of pools required by the strategy, benchmarks the exact supported getProgramAccounts filters, and then streams the state actually used for pricing. Extend the catalog, throughput, and regional infrastructure only when the observed workload demands it.

Start with your actual pools

Sign up for the free RPC Fast Start plan, run your filtered getProgramAccounts calls for the DEX pairs your bot trades, and measure client-side p50/p95/p99, result size, and snapshot slot. Free Start supports eligible Quark lookups at 1 RPS; use a streaming-enabled plan to add Yellowstone gRPC when continuous low-latency pool monitoring becomes the requirement.

Compare the full snapshot-plus-stream system against your bot's maximum acceptable state age, recovery time, and cost per useful signal. That performance test will help you choose the best-fit infrastructure.

Ready to track your selected Solana DEX pool?

Start now with RPC Fast

Table of Content

See. Predict. Land.

See it first

99.97%

Predict the result

95%

Land the trade

330 ms

Get my endpoint

Ask in Telegram for the free $499 trial

We use cookies to personalize your experience