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

dino-markets

v0.2.0

Published

Official TypeScript SDK for dino.markets - cross-venue prediction market data (Kalshi + Polymarket, matched and arbitrage signals).

Readme

dino-markets

Official TypeScript SDK for dino.markets: related Kalshi and Polymarket prediction markets across sports, crypto, weather, and economics, plus confirmed cross-venue arbitrage and a real-time stream.

Zero runtime dependencies. Built on the native fetch API, so it runs in Node 18+, Deno, Bun, and the browser without a bundler polyfill.

Install

npm install dino-markets

Auth

Get a free API key from the dashboard at https://dino.markets. Sign in and open the keys page, then mint a key with the sk_live_ prefix.

Pass the key directly, or set it as an environment variable and let the client pick it up:

export DINO_API_KEY=sk_live_...
import { Dino } from "dino-markets";

const dino = new Dino(); // reads DINO_API_KEY
// or: new Dino({ apiKey: "sk_live_..." });

Quickstart

import { Dino } from "dino-markets";

const dino = new Dino({ apiKey: "sk_live_..." });

const { markets } = await dino.markets({ sport: "baseball" });
console.log(markets.length, "markets");

const { markets: crypto } = await dino.markets({ market_type: "crypto_above" });

// v0.2.0: findArbitrage() calls the dedicated Opportunity endpoint -- one exact
// selected execution leg per outcome, not a filtered Market collection.
const arbs = await dino.findArbitrage({ sport: "soccer" });
for (const opportunity of arbs.opportunities) {
  console.log(opportunity.title, opportunity.roi_pct, opportunity.fee_model, opportunity.max_wager_usd);
}

const market = await dino.market(markets[0].id);
const history = await dino.history(markets[0].id);
const { sports } = await dino.leagues();

// opp_id is the Opportunity's own id, not a market id
await dino.reportBadArb({ opp_id: arbs.opportunities[0]?.id, reason: "stale price on one leg" });

Market catalog methods (markets, market, history) are typed against the same canonical Market object the REST API and the WebSocket stream both serve, including the family fields that appear only on spread/total/team-total markets and the ladder fields specific to weather markets. findArbitrage() returns the separate Opportunity type instead -- exact snapshot selected legs, conservative modeled ROI after fees, fee_model, and a USD capital estimate.

REST reads are priced roughly two minutes behind live on every plan. If you need the current price when it changes, use the WebSocket stream.

Streaming

Every plan gets a real-time WebSocket feed over Centrifugo: Free is scoped to a curated sample channel, while Basic, Premium, and Pro plans get the full market stream. Streaming needs the optional centrifuge peer dependency:

npm install centrifuge
import { Dino, watch } from "dino-markets";

const dino = new Dino({ apiKey: "sk_live_..." });

const handle = await watch(dino, {
  onFrame: (frame, channel) => console.log(channel, frame),
  onError: (err) => console.error(err),
  onRecoveryFailed: (channel) => bootstrapFromRest(channel),
});

// later
handle.close();

watch mints one short-lived ticket per connection attempt, including automatic reconnects, then opens the socket and forwards every publication on your plan's channels. Every plan's streamToken() call returns a real ticket; a Free key's is scoped to the sample channel rather than a silent empty stream. Initial admission is fenced to about 15 seconds; the first successful plan-matching refresh then grants the normal 15-minute renewal. If the five-minute recovery history can't fill a reconnect gap, onRecoveryFailed receives the channel that must be reloaded from REST.

Error handling

Every non-2xx response is thrown as a typed error, all extending DinoError:

import { AuthenticationError, PlanError, RateLimitError, ServerError } from "dino-markets";

try {
  await dino.markets();
} catch (err) {
  if (err instanceof RateLimitError) {
    console.log("retry after", err.retryAfter, "seconds");
  } else if (err instanceof AuthenticationError) {
    console.log("check your API key");
  } else if (err instanceof PlanError) {
    console.log("subscription inactive or plan not recognized:", err.body);
  } else if (err instanceof ServerError) {
    console.log("dino.markets had a server error, try again shortly");
  }
}

The client retries a 429 or 5xx automatically (maxRetries, default 2), honoring the server's Retry-After when present and backing off otherwise. A 4xx other than 429 is never retried.

Rate limits

REST requests are metered per API key:

| Plan | Requests/month | Requests/sec | WebSocket | | --- | ---: | ---: | --- | | Free | 10,000 | 10 | Curated sample, 1 connection | | Basic ($30/month) | 1,000,000 | 10 | Full market stream, 3 connections | | Premium ($100/month) | 5,000,000 | 25 | Full market stream, 10 connections | | Pro ($200/month) | 5,000,000 | 25 | Full stream plus raw quotes and early candidates, 10 connections |

Localized documentation

| Language | Docs | MCP server | |---|---|---| | 日本語 | Docs | MCP | | 한국어 | Docs | MCP | | 简体中文 | Docs | MCP | | Español | Docs | MCP |

Disclaimer

Informational data. Not investment advice. You trade on your own venue accounts at your own risk.

Support

Reach us at [email protected] with questions, or report a bad signal directly from your code with dino.reportBadArb(...).