npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@radion-app/sdk

v0.8.0

Published

Official SDK for the Radion platform

Readme

@radion-app/sdk

npm version license

Official, fully-typed SDK for the Radion platform.

One client, one API key, every Radion product surface.

import { Radion } from "@radion-app/sdk";

const radion = new Radion({ apiKey: process.env.RADION_API_KEY });
await radion.realtime.connect();
radion.realtime.subscribe({ id: "trading", channel: "trading" });
radion.realtime.onChannel("trading", (event) => console.log(event.data));

Features

  • Unified clientnew Radion({ apiKey }) is the single entry point for every product surface
  • Auto-reconnect — exponential backoff with jitter; stops on graceful shutdown
  • Subscription restore — active channels are re-subscribed after every reconnect
  • Heartbeats — ping/pong keep-alive that detects stale connections and reconnects
  • Typed end-to-end — channel names, inbound/outbound frames, and errors
  • Webhook helpers — verify delivery signatures and parse bodies into the same typed events, on any runtime with WebCrypto
  • Tiny — single dependency (ws), ESM + CJS, ships its own type definitions

Requirements

  • Node.js >= 20 (webhook helpers also run in any browser, Deno, Bun, or edge runtime)

Install

pnpm add @radion-app/sdk
# or: npm install @radion-app/sdk / yarn add @radion-app/sdk

Quick start

import { Radion } from "@radion-app/sdk";

const radion = new Radion({
  apiKey: process.env.RADION_API_KEY,
});

await radion.realtime.connect();

radion.realtime.subscribe({ id: "trading", channel: "trading" });

radion.realtime.onChannel("trading", (event) => {
  console.log(event.channel, event.data);
});

Usage

Configuration

new Radion(options);

| Option | Type | Default | Description | | --- | --- | --- | --- | | apiKey | string | — | Required. Sent as the X-API-Key header. | | baseUrl | string | https://api.radion.app | Base URL for the Radion API. | | wsUrl | string | wss://api.radion.app/ws | Override the realtime endpoint. | | realtime | RealtimeOptions | enabled | Reconnect / heartbeat tuning for the realtime client. |

Authentication

Two credential schemes, both keyed on apiKey (sent as X-API-Key):

Secret key (sk_ / rk_) — server-side use:

const radion = new Radion({ apiKey: process.env.RADION_API_KEY });

Public JWT (pk_jwt_) — browser-safe. Pair the public key with a per-user JWT minted by your own auth provider. Pass realtime.token as a provider so a fresh token is fetched on every (re)connect:

const radion = new Radion({
  apiKey: "pk_jwt_...",
  realtime: { token: () => fetchUserJwt() }, // string or () => string | Promise<string>
});

In a browser the client uses the native WebSocket and moves credentials to the URL query string automatically (authInQuery defaults to true there). Set realtime.authInQuery explicitly to force it on or off — e.g. for a header-stripping proxy under Node.

Warning: In a browser, use a pk_jwt_ public key only. Never ship a secret key to the browser — it becomes visible to anyone loading the page.

Realtime client

radion.realtime is a RealtimeClient. It can also be imported and constructed standalone:

import { RealtimeClient } from "@radion-app/sdk";

const client = new RealtimeClient({ apiKey: process.env.RADION_API_KEY });

| Method | Description | | --- | --- | | connect() | Open the connection. Resolves once established. | | subscribe(subscription) | Subscribe with { id, channel, confirmed?, filters? }. Replayed on reconnect. | | unsubscribe(id) | Unsubscribe by subscription id. | | onChannel(channel, fn) | Handle events on one channel; event.data is narrowed. Chainable. | | onAnyChannel(fn) | Handle every channel event (id + channel + data). Chainable. | | onLifecycle(event, fn) | Handle a lifecycle event (open/close/reconnect/error/warning/subscribed/unsubscribed). | | offChannel(channel, fn?) | Remove a channel handler (or all for the channel). Chainable. | | offAnyChannel(fn?) | Remove an all-channel handler (or all). Chainable. | | offLifecycle(event, fn?) | Remove a lifecycle handler (or all for the event). Chainable. | | close(code?, reason?) | Graceful shutdown. Stops reconnect attempts. | | connected | Getter — whether the socket is currently open. |

Subscriptions & filters

A subscription is { id, channel, confirmed?, filters? }. The id is your own string, echoed back on every event so you can tell subscriptions apart. confirmed selects the feed: it defaults to true (confirmed, on-chain), and false gives the pending (mempool) feed of the same channel. Some channels require a filter (wallets needs wallets; markets needs market_ids or token_ids), while trading accepts wallets, market_ids, token_ids, and min_usd optionally:

radion.realtime.subscribe({
  id: "whales",
  channel: "trading",
  filters: { min_usd: 10_000 },
});
radion.realtime.subscribe({
  id: "watch",
  channel: "wallets",
  filters: { wallets: ["0x…"] },
});

// onAnyChannel fires for every channel; the event carries id + channel + data.
radion.realtime.onAnyChannel((e) => console.log(e.id, e.channel, e.data));

Every event frame also carries seq and sent_at_ms. seq counts event frames on the connection (a jump means frames were dropped), and sent_at_ms is the server-send time in Unix ms, so server→client latency is Date.now() - event.sent_at_ms. Pending events keep data.seen_at_ms (block-detection time) for block→client latency.

radion.realtime.onAnyChannel((e) => {
  console.log(`${Date.now() - e.sent_at_ms}ms behind the server`);
});

Channels

Nine topic channels — every decoded event routes to exactly one:

trading · fees · oracle · resolution · lifecycle
positions · combos · transfers · accounts

Plus two cross-cutting filter channels that apply across all topics and require a filter:

wallets · markets

Every topic channel also has a pending feed emitting speculative transactions before block inclusion. Select it with confirmed: false on the subscription — the channel name stays bare (no prefix). A pending event's data is a MempoolPayload: the full pending-transaction envelope (seen_at_ms, transaction_hash, from, to, contract_kinds, method_selector, input, value) plus a decoded call (or null) carrying notional_usd and an un-collapsed orders array. Tell the feeds apart with event.confirmed. Available at runtime as CHANNELS and at the type level as Channel.

import { CHANNELS, type Channel } from "@radion-app/sdk";

for (const channel of CHANNELS) {
  radion.realtime.subscribe({ id: channel, channel });
}

// pending (mempool) feed for the same channel:
radion.realtime.subscribe({
  id: "trading-pending",
  channel: "trading",
  confirmed: false,
});
radion.realtime.onChannel("trading", (event) => {
  if (event.confirmed === false) {
    // event.data is a MempoolPayload
    console.log(event.data);
  }
});

If a pending subscribe reaches a node with no pending stream, the server sends a non-fatal warning frame (code: "mempool_unavailable"), surfaced through the warning lifecycle event:

radion.realtime.onLifecycle("warning", ({ code, id, message }) =>
  console.warn(code, id, message)
);

CLOB channels

A separate CLOB channel family is also subscribable:

clob.book · clob.prices · clob.last_trade
clob.midpoint · clob.tick_size · clob.best_bid_ask

Each CLOB channel requires a token_ids filter and has no pending feed (confirmed is ignored). Unlike topic channels, a CLOB event.data is a single fixed shape with no type discriminator. Available at runtime as CLOB_CHANNELS and at the type level as ClobChannel.

radion.realtime.subscribe({
  id: "book",
  channel: "clob.prices",
  filters: { token_ids: ["123…"] },
});
radion.realtime.onChannel("clob.prices", (event) => {
  // event.data narrows to the clob.prices payload
  console.log(event.data.market, event.data.changes);
});

Lifecycle events

radion.realtime.onLifecycle("open", () => console.log("connected"));
radion.realtime.onLifecycle("close", ({ code, reason }) =>
  console.log("closed", code, reason)
);
radion.realtime.onLifecycle("reconnect", ({ attempt, delayMs }) =>
  console.log(`reconnect #${attempt} in ${delayMs}ms`)
);
radion.realtime.onLifecycle("error", (err) => console.error(err));
radion.realtime.onLifecycle("warning", ({ code, message }) =>
  console.warn(code, message)
);

Reconnect & subscription restore

On an unexpected disconnect the client reconnects with exponential backoff and re-sends every active subscription once the socket reopens. After close() no further attempts run.

Heartbeats

A ping is sent every intervalMs. Any inbound frame (pong or data) counts as liveness; if nothing arrives within timeoutMs the connection is treated as stale, terminated, and reconnected.

Webhooks

Radion webhooks POST the same event frames as the WebSocket, signed with your endpoint's secret. The SDK ships standalone helpers to consume them — no client instance needed: verifyWebhookSignature checks the X-Radion-Signature HMAC (constant-time, with replay protection), and parseWebhookEvent validates the body into the same typed ChannelEvent the realtime client emits. Both are runtime-agnostic — they use WebCrypto, not node:crypto, so they run in Node.js 20+, browsers, Deno, Bun, and edge runtimes.

import { parseWebhookEvent, verifyWebhookSignature } from "@radion-app/sdk";

export async function handler(request: Request): Promise<Response> {
  const body = await request.text();

  const authentic = await verifyWebhookSignature({
    payload: body,
    secret: process.env.RADION_WEBHOOK_SECRET, // whsec_…
    signature: request.headers.get("x-radion-signature") ?? "",
    timestamp: request.headers.get("x-radion-timestamp") ?? "",
  });
  if (!authentic) {
    return new Response("invalid signature", { status: 401 });
  }

  const event = parseWebhookEvent(body);
  if (event?.channel === "trading") {
    console.log(event.seq, event.data);
  }
  return new Response("ok");
}

| Option | Type | Default | Description | | --- | --- | --- | --- | | payload | string \| Uint8Array | — | Required. Raw request body, exactly as received. | | signature | string | — | Required. The X-Radion-Signature header. | | timestamp | number \| string | — | Required. The X-Radion-Timestamp header (Unix ms). | | secret | string \| string[] | — | Required. Signing secret(s); pass both during a rotation window. | | toleranceMs | number | 5 minutes | Max delivery age before it is rejected as a replay. |

Sign-related gotcha: verify over the raw body exactly as received — parse JSON only after the signature checks out. Deliveries are retried and unordered; deduplicate on (id, seq) or on fields of data.

Error handling

import { RadionConnectionError, RadionServerError } from "@radion-app/sdk";

radion.realtime.onLifecycle("error", (err) => {
  if (err instanceof RadionServerError) {
    console.error("server error", err.code, err.channel);
  } else if (err instanceof RadionConnectionError) {
    console.error("connection error", err.message);
  }
});

A throwing consumer handler is reported via the error event and never retried.

License

MIT