Building a Live Solana TPS Meter with OrbitFlare's TypeScript SDK This article explains how to build a live transactions-per-second (TPS) meter for any specific Solana program using OrbitFlare's TypeScript SDK. The solution uses a Jetstream subscription to count transactions filtered by a target program, combined with a WebSocket slot subscription to verify the connection is still active when the program goes quiet. The entire tool runs in a terminal as a single TypeScript file under 100 lines of code, displaying real-time TPS rates and recent transaction signatures. 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... ... Full source: github.com/orbitflare/jetstream-tx-rate https://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 js 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 ; 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: js let currentSlot = 0; const slotSub = await ws.slotSubscribe ; slotSub.on s = { if typeof s?.slot === 'number' currentSlot = s.slot; } ; Jetstream gets the transaction firehose, filtered server-side to just the program of interest. for await drains it and stamps each arrival: js 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; } 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. js 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 ; 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 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.