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

@quanaris/sdk

v0.1.0

Published

Typed TypeScript SDK for the Quanaris Solana market-data API — REST methods generated from the OpenAPI contract plus an auto-reconnecting WebSocket client.

Downloads

27

Readme

@quanaris/sdk

The typed TypeScript SDK for the Quanaris Solana market-data API — reconciled live + historical OHLCV, normalized swaps and real-time metrics for any Solana token, over a clean REST + WebSocket API.

Birdeye-grade, but with a real SDK. Birdeye ships no first-party typed client and churns its API. Quanaris gives you typed REST methods that match the OpenAPI contract shipped inside this package, plus an auto-reconnecting WebSocket client so you never babysit heartbeats.

  • ✅ Fully typed REST methods (token, wallet, pair, pricing) covering the whole read surface — overview, metrics, holders, security, markets, top traders, trade stats, price history, coverage, portfolio, PnL and more
  • ✅ camelCase response types, identical across REST and WebSocket
  • Automatic retry/backoff on 429/502/503/network errors for idempotent GETs — exponential backoff + jitter, honours Retry-After (opt-out)
  • Cursor-paginated async iterators (tradesAll / tradesPages) that follow nextCursor for you
  • ✅ Auto-reconnecting WS client: 30s ping, mandatory resubscribe on reconnect, replay-on-gap hook
  • ✅ Zero runtime dependencies — uses the platform fetch and WebSocket

Install

npm install @quanaris/sdk
# or: pnpm add @quanaris/sdk

Requires Node 18+ (fetch) and, for streaming, Node 22+ (global WebSocket) or a ws instance passed via WebSocketImpl.

REST quickstart (curl-equivalent, in TS)

import { QuanarisClient } from "@quanaris/sdk";

const q = new QuanarisClient({ apiKey: process.env.QUANARIS_API_KEY });

// curl https://www.quanaris.com/v1/token/<mint>/price
const price = await q.token.price("So11111111111111111111111111111111111111112");
console.log(price.priceUsd, price.updatedAt);

// full market snapshot (price, mcap, liquidity, pools, holders, volume)
const overview = await q.token.overview(mint);

// curl ".../v1/token/<mint>/ohlcv?interval=1m&limit=100"
const { data } = await q.token.ohlcv(mint, { interval: "1m", limit: 100 });
console.log(data.at(-1)); // { t, o, h, l, c, v, trades, confirmed }

// curl ".../v1/token/<mint>/trades?limit=50"
const { trades, nextCursor } = await q.token.trades(mint, { limit: 50 });

// the rest of the token surface — all typed:
await q.token.metadata(mint);                       // identity + price
await q.token.metrics(mint, { window: 24 });        // rolling-window volume/pressure
await q.token.tradeData(mint);                       // multi-window activity grid
await q.token.holders(mint);                          // holder count + top accounts
await q.token.security(mint);                          // on-chain rug-check
await q.token.markets(mint);                            // every pool for the token
await q.token.topTraders(mint, { sort: "volume" });    // key-gated
await q.token.priceHistory(mint, { interval: "1h" });  // sparkline points
await q.token.priceAt(mint, { time: 1780521900 });     // price at a past moment
await q.token.coverage(mint);                            // series confidence / contiguity

// pair / pool reads
const pair = await q.pair.overview(poolAddress);
await q.pair.ohlcv(poolAddress, { interval: "1h" });

// wallet reads
await q.wallet.portfolio(wallet);                  // holdings valued in USD
await q.wallet.token(wallet, mint);               // one token's balance + value
const pnl = await q.wallet.pnl(wallet);           // gas-aware realized/unrealized PnL

// batch price a whole portfolio in one call
const prices = await q.pricing.multi([mintA, mintB, mintC]);

Every read accepts { network: "mainnet" | "devnet" } (default mainnet), and you can set a client-wide default:

const q = new QuanarisClient({ apiKey, network: "devnet" });

Errors are thrown as a typed QuanarisApiError carrying status, code and the parsed body:

import { QuanarisApiError } from "@quanaris/sdk";
try {
  await q.token.price(badMint);
} catch (e) {
  if (e instanceof QuanarisApiError) console.error(e.status, e.code, e.message);
}

Need an endpoint not yet wrapped? Use the typed low-level escape hatch:

const me = await q.request<{ plan: string }>("GET", "/v1/me");

Resilience: automatic retry & backoff

Idempotent GETs are retried automatically on 429, 502, 503 and network errors with exponential backoff + jitter, honouring a Retry-After header when the server sends one. POST/DELETE are never retried. It's on by default — tune or disable it on the client:

// tune it (defaults: 3 retries, 250ms base delay):
const q = new QuanarisClient({ apiKey, retry: { maxRetries: 5, baseDelayMs: 500 } });

// or turn it off entirely:
const q = new QuanarisClient({ apiKey, retry: false });

When a request finally fails with a rate-limit, the thrown QuanarisApiError carries the parsed RateLimit-* / Retry-After headers:

try {
  await q.token.price(mint);
} catch (e) {
  if (e instanceof QuanarisApiError && e.status === 429) {
    console.log(e.rateLimit); // { remaining, reset, retryAfter }
  }
}

Pagination: cursor-following async iterators

The trade tapes are keyset-paginated (nextCursor). Instead of looping by hand, iterate the whole tape — tradesAll yields one trade at a time, tradesPages yields each page. Both stop at nextCursor: null or when you cap them with maxItems / maxPages (default cap: 50 pages):

// every trade, newest first, across as many pages as it takes:
for await (const trade of q.token.tradesAll(mint, { maxItems: 1000 })) {
  console.log(trade.signature, trade.priceUsd);
}

// page-by-page (e.g. to write each page somewhere):
for await (const page of q.wallet.tradesPages(wallet, { limit: 200 })) {
  save(page.trades);
}

The same helpers exist on q.token, q.wallet and q.pair.

Streaming quickstart (auto-reconnect)

One socket streams every mint on your account (the v2 account channel). The client auto-reconnects, pings every 30s, resubscribes automatically, and hands you the last-seen timestamps on reconnect so you can backfill the gap.

import { QuanarisStream } from "@quanaris/sdk/ws";
import { QuanarisClient } from "@quanaris/sdk";

const q = new QuanarisClient({ apiKey });

const stream = new QuanarisStream({
  apiKey,                       // required for the account channel
  interval: "1m",
  onTrade: ({ mint, trade }) => console.log("trade", mint, trade.priceUsd),
  onCandle: ({ mint, candle }) => console.log("candle", mint, candle.c),
  onStateChange: (s) => console.log("ws", s), // connecting | open | reconnecting | closed
  onReconnect: async ({ lastSeen }) => {
    // replay-on-gap: backfill anything missed while disconnected
    for (const [mint, t] of Object.entries(lastSeen)) {
      const { data } = await q.token.ohlcv(mint, { interval: "1m", from: t });
      // …merge the missed candles…
    }
  },
});

stream.connect();
stream.account();               // stream ALL your mints over one socket

Or subscribe to specific per-mint channels — these are replayed on reconnect too:

stream.connect();
stream.subscribe("ohlcv", mint, "1m");
stream.subscribe("trades", mint);
// later
stream.unsubscribe("ohlcv", mint, "1m");
stream.close();                 // stops reconnecting, clears timers

Older Node / custom runtimes

import WebSocket from "ws";
const stream = new QuanarisStream({ apiKey, WebSocketImpl: WebSocket as any });

The OpenAPI contract

The machine-readable spec the REST methods are pinned to is committed at src/openapi.json (also exported as @quanaris/sdk/openapi.json) and served live at GET https://www.quanaris.com/openapi.json. The response types in this package mirror the same canonical camelCase serializers the API emits, so REST and WebSocket payloads share one shape.

Full reference docs: https://www.quanaris.com/docs.

License

MIT