DEV Community

OrbitFlare RPC
OrbitFlare RPC

Posted on

Building a Live Solana TPS Meter with OrbitFlare's TypeScript SDK

Every Solana dashboard tells you the network's TPS. None of them tell you how busy the one program you care about is right now. Did Pumpfun cool off after a launch? Is Raydium spiking? Is the program your bot listens to still alive in the last 30 seconds? You're stuck picking between a smoothed-out aggregate or a paid dashboard that refreshes once a minute.

The smallest thing that answers the question directly is below. Point it at any Solana program, get a live transactions-per-second gauge in the terminal with a recent-signatures tape under it. One TypeScript file, less than 100 LoC, runs in a terminal, built on @orbitflare/sdk.

jetstream tx rate: pumpfun (6EF8rrec...)
chain slot:        421442130

last 1s:     14 tx/s
last 10s:    9.7 tx/s avg

recent signatures:
   0.1s ago  slot 421442129  4bpPQBUHmobNJ4SS9ETLaMwY...
   0.4s ago  slot 421442128  Gsi3P32j6h9Yx3ce1gfHR5Pa...
   0.7s ago  slot 421442128  55AWdcey9N1WTYkmQDXPeKHD...
   ...
Enter fullscreen mode Exit fullscreen mode

Full source: github.com/orbitflare/jetstream-tx-rate.

The honest meter problem

The meter could be built with one Jetstream subscription: filter on the program, count transactions per second. That gets done in 50 lines. The problem only shows up the first time the program goes quiet for ten seconds. The meter reads zero. So does the signature tape. And now there's no way to know whether the program is actually idle or whether the SDK silently lost the connection three minutes ago.

The fix is one extra subscription on a second transport. WebSocket slotSubscribe() ticks every ~400ms whether anything is happening or not, because the chain itself never stops.

The clients

import { JetstreamClientBuilder } from '@orbitflare/sdk/jetstream';
import { WsClientBuilder } from '@orbitflare/sdk/ws';

const jet = new JetstreamClientBuilder()
  .url('http://jp.jetstream.orbitflare.com')
  .build();

const ws = await new WsClientBuilder()
  .url('ws://ams.rpc.orbitflare.com')
  .build();
Enter fullscreen mode Exit fullscreen mode

That's it for setup. Jetstream needs no api key. WebSocket picks one up from ORBITFLARE_LICENSE_KEY if set.

The two subscriptions

WebSocket pipes the chain slot into a shared variable. One callback, no state machine:

let currentSlot = 0;
const slotSub = await ws.slotSubscribe();
slotSub.on((s) => {
  if (typeof s?.slot === 'number') currentSlot = s.slot;
});
Enter fullscreen mode Exit fullscreen mode

Jetstream gets the transaction firehose, filtered server-side to just the program of interest. for await drains it and stamps each arrival:

const stream = jet.subscribe({
  transactions: {
    target: {
      accountInclude: [],
      accountExclude: [],
      accountRequired: [program],
    },
  },
  accounts: {},
  ping: { id: 1 },
} as any);

const arrivals: number[] = [];
const recent: { slot: number; sig: string; at: number }[] = [];

for await (const u of stream) {
  const sig = u.transaction?.transaction?.signature;
  if (!sig) continue;
  const now = Date.now();
  arrivals.push(now);
  recent.unshift({ slot: Number(u.transaction!.slot), sig: bs58.encode(sig), at: now });
  if (recent.length > 10) recent.length = 10;
}
Enter fullscreen mode Exit fullscreen mode

The accountRequired filter is the load-bearing piece. It tells the Jetstream server to only ship transactions where the program is in the account list, so the bandwidth that reaches the laptop is already the data that matters. No client-side filtering, no wasted parsing.

Two pieces of state, both trivial. arrivals is a list of millisecond timestamps the renderer reads to compute rates. recent is a 10-element ring of the most recent signatures, kept in arrival order, displayed as a scrolling tape under the gauge.

The renderer

Every second, the renderer does three things. First it drops any timestamp older than 10 seconds from arrivals. Then it counts: how many timestamps in the last 1 second (that's the instantaneous rate), and the total count divided by 10 (that's the rolling 10-second average). Finally it clears the screen with an ANSI escape and prints the dashboard.

function render(): void {
  const now = Date.now();
  while (arrivals.length && now - arrivals[0]! > 10_000) arrivals.shift();
  const last1s = arrivals.filter((t) => now - t <= 1_000).length;
  const last10sAvg = arrivals.length / 10;

  process.stdout.write('\x1B[2J\x1B[H');
  console.log(`jetstream tx rate: ${label} (${program.slice(0, 8)}...)`);
  console.log(`chain slot:        ${currentSlot}\n`);
  console.log(`  last 1s:  ${String(last1s).padStart(5)} tx/s`);
  console.log(`  last 10s: ${last10sAvg.toFixed(1).padStart(5)} tx/s avg\n`);
  console.log('recent signatures:');
  for (const r of recent) {
    const age = Math.max(0, (now - r.at) / 1000).toFixed(1);
    console.log(`  ${age.padStart(4)}s ago  slot ${r.slot}  ${r.sig.slice(0, 24)}...`);
  }
}

setInterval(render, 1_000);
render();
Enter fullscreen mode Exit fullscreen mode

Running it

git clone https://github.com/orbitflare/jetstream-tx-rate.git
cd jetstream-tx-rate
npm install
npm start pumpfun       # or raydium, jupiter, or any raw program id
Enter fullscreen mode Exit fullscreen mode

Signatures land within a second of the first run. Watching Pumpfun for five minutes shows it idling around 8-15 tx/s on a slow afternoon and spiking to 50-80 when a launch hits. Raydium runs lighter on average but every big swap is a visible jolt. Any program ID on Solana gets the same view of how busy it actually is, not how busy it averages out.

What this tiny snippet doesn't have to do

Reconnects, regional failover, api-key scrubbing, ping/pong liveness, the protobuf wire format, the WebSocket re-subscribe dance after a drop. The SDK handles all of it the same way across both transports. None of it shows up in the source. What's left is a filter, a callback, and the rendering math.

That’s the trade. And a good one. Use the SDK’s plumbing, write the part that’s actually yours.

Resources

  1. Full source: github.com/orbitflare/jetstream-tx-rate
  2. @orbitflare/sdk on npm
  3. SDK docs

Top comments (0)