{"slug": "building-a-live-solana-tps-meter-with-orbitflare-s-typescript-sdk", "title": "Building a Live Solana TPS Meter with OrbitFlare's TypeScript SDK", "summary": "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.", "body_md": "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.\n\nThe 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 .\n\n`@orbitflare/sdk`\n\n```\njetstream tx rate: pumpfun (6EF8rrec...)\nchain slot:        421442130\n\nlast 1s:     14 tx/s\nlast 10s:    9.7 tx/s avg\n\nrecent signatures:\n   0.1s ago  slot 421442129  4bpPQBUHmobNJ4SS9ETLaMwY...\n   0.4s ago  slot 421442128  Gsi3P32j6h9Yx3ce1gfHR5Pa...\n   0.7s ago  slot 421442128  55AWdcey9N1WTYkmQDXPeKHD...\n   ...\n```\n\nFull source: [github.com/orbitflare/jetstream-tx-rate](https://github.com/orbitflare/jetstream-tx-rate).\n\n## The honest meter problem\n\nThe 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.\n\nThe fix is one extra subscription on a second transport. WebSocket `slotSubscribe()`\n\nticks every ~400ms whether anything is happening or not, because the chain itself never stops.\n\n## The clients\n\n``` js\nimport { JetstreamClientBuilder } from '@orbitflare/sdk/jetstream';\nimport { WsClientBuilder } from '@orbitflare/sdk/ws';\n\nconst jet = new JetstreamClientBuilder()\n  .url('http://jp.jetstream.orbitflare.com')\n  .build();\n\nconst ws = await new WsClientBuilder()\n  .url('ws://ams.rpc.orbitflare.com')\n  .build();\n```\n\nThat's it for setup. Jetstream needs no api key. WebSocket picks one up from `ORBITFLARE_LICENSE_KEY`\n\nif set.\n\n## The two subscriptions\n\nWebSocket pipes the chain slot into a shared variable. One callback, no state machine:\n\n``` js\nlet currentSlot = 0;\nconst slotSub = await ws.slotSubscribe();\nslotSub.on((s) => {\n  if (typeof s?.slot === 'number') currentSlot = s.slot;\n});\n```\n\nJetstream gets the transaction firehose, filtered server-side to just the program of interest. `for await`\n\ndrains it and stamps each arrival:\n\n``` js\nconst stream = jet.subscribe({\n  transactions: {\n    target: {\n      accountInclude: [],\n      accountExclude: [],\n      accountRequired: [program],\n    },\n  },\n  accounts: {},\n  ping: { id: 1 },\n} as any);\n\nconst arrivals: number[] = [];\nconst recent: { slot: number; sig: string; at: number }[] = [];\n\nfor await (const u of stream) {\n  const sig = u.transaction?.transaction?.signature;\n  if (!sig) continue;\n  const now = Date.now();\n  arrivals.push(now);\n  recent.unshift({ slot: Number(u.transaction!.slot), sig: bs58.encode(sig), at: now });\n  if (recent.length > 10) recent.length = 10;\n}\n```\n\nThe `accountRequired`\n\nfilter 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.\n\nTwo pieces of state, both trivial. `arrivals`\n\nis a list of millisecond timestamps the renderer reads to compute rates. `recent`\n\nis a 10-element ring of the most recent signatures, kept in arrival order, displayed as a scrolling tape under the gauge.\n\n## The renderer\n\nEvery second, the renderer does three things. First it drops any timestamp older than 10 seconds from `arrivals`\n\n. 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.\n\n``` js\nfunction render(): void {\n  const now = Date.now();\n  while (arrivals.length && now - arrivals[0]! > 10_000) arrivals.shift();\n  const last1s = arrivals.filter((t) => now - t <= 1_000).length;\n  const last10sAvg = arrivals.length / 10;\n\n  process.stdout.write('\\x1B[2J\\x1B[H');\n  console.log(`jetstream tx rate: ${label} (${program.slice(0, 8)}...)`);\n  console.log(`chain slot:        ${currentSlot}\\n`);\n  console.log(`  last 1s:  ${String(last1s).padStart(5)} tx/s`);\n  console.log(`  last 10s: ${last10sAvg.toFixed(1).padStart(5)} tx/s avg\\n`);\n  console.log('recent signatures:');\n  for (const r of recent) {\n    const age = Math.max(0, (now - r.at) / 1000).toFixed(1);\n    console.log(`  ${age.padStart(4)}s ago  slot ${r.slot}  ${r.sig.slice(0, 24)}...`);\n  }\n}\n\nsetInterval(render, 1_000);\nrender();\n```\n\n## Running it\n\n```\ngit clone https://github.com/orbitflare/jetstream-tx-rate.git\ncd jetstream-tx-rate\nnpm install\nnpm start pumpfun       # or raydium, jupiter, or any raw program id\n```\n\nSignatures 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.\n\n## What this tiny snippet doesn't have to do\n\nReconnects, 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.\n\nThat’s the trade. And a good one. Use the SDK’s plumbing, write the part that’s actually yours.", "url": "https://wpnews.pro/news/building-a-live-solana-tps-meter-with-orbitflare-s-typescript-sdk", "canonical_source": "https://dev.to/orbitflarerpc/building-a-live-solana-tps-meter-with-orbitflares-typescript-sdk-b66", "published_at": "2026-05-22 21:18:18+00:00", "updated_at": "2026-05-22 21:31:45.969008+00:00", "lang": "en", "topics": ["web3", "developer-tools", "open-source"], "entities": ["Solana", "OrbitFlare", "Pumpfun", "Raydium", "Jetstream", "TypeScript"], "alternates": {"html": "https://wpnews.pro/news/building-a-live-solana-tps-meter-with-orbitflare-s-typescript-sdk", "markdown": "https://wpnews.pro/news/building-a-live-solana-tps-meter-with-orbitflare-s-typescript-sdk.md", "text": "https://wpnews.pro/news/building-a-live-solana-tps-meter-with-orbitflare-s-typescript-sdk.txt", "jsonld": "https://wpnews.pro/news/building-a-live-solana-tps-meter-with-orbitflare-s-typescript-sdk.jsonld"}}