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

poly-rewards-shared

v0.4.0

Published

Client SDK for the Polymarket rewards engine. Ships:

Readme

poly-rewards-shared

Client SDK for the Polymarket rewards engine. Ships:

  • Four factory helpers: createClient, createPullLoop, createWebhookReceiver, registerWebhook.
  • Two HMAC utilities: signBody, verifySignature.
  • Typed errors: RewardsError base + UnauthorizedError, ForbiddenError, NotFoundError, ValidationError, RateLimitedError, NetworkError, HmacVerifyError.
  • Zod schemas: request/response shapes (./api) and the EventEnvelope discriminated union (./events).

For an end-to-end integration that wires every helper together, read packages/smoke-harness/src/index.ts. Bot developers are expected to copy that file and adapt the onEvent callbacks for their channel — see the smoke harness README.

Install

Published on npm. External consumers install it like any other package:

npm install poly-rewards-shared
# or
bun add poly-rewards-shared

Workspaces inside this monorepo continue to reference it via workspace:*:

{
  "dependencies": {
    "poly-rewards-shared": "workspace:*"
  }
}

Then import from the barrel or any subpath:

import { createClient, createPullLoop } from 'poly-rewards-shared';
import { signBody } from 'poly-rewards-shared/hmac';

createClient

Builds a RewardsClient that wraps every read/manage route. Each method adds Authorization: Bearer <apiKey>, applies defaultTimeoutMs, and validates responses against the Zod schemas in ./api. Status codes map to typed errors: 401 → UnauthorizedError, 403 → ForbiddenError, 404 → NotFoundError, 400/422 → ValidationError, 429 → RateLimitedError (with retryAfter from the Retry-After header), 5xx / network → NetworkError.

import { createClient } from 'poly-rewards-shared';

const client = createClient({
  baseUrl: 'http://localhost:3000',
  apiKey: process.env.REWARDS_API_KEY!,
  defaultTimeoutMs: 10_000,
});

const health = await client.healthz();
const markets = await client.markets.list({ limit: 50 });

Sub-namespaces: markets, recommendations, yolo, alerts, events, webhooks, meta, plus top-level healthz(). healthz and meta.* are the only no-auth methods.

defaultHeaders

Pass defaultHeaders: Record<string, string> to attach static headers to every outbound request made by this client — and by anything that operates on it (createPullLoop, registerWebhook). The primary use case is reaching an engine instance protected by a header-based gateway such as a Cloudflare Access service-token policy:

import { createClient } from 'poly-rewards-shared';

const client = createClient({
  baseUrl: 'https://rewards-engine.titus.finance',
  apiKey: process.env.REWARDS_API_KEY!,
  defaultHeaders: {
    'CF-Access-Client-Id':     process.env.CF_ACCESS_CLIENT_ID!,
    'CF-Access-Client-Secret': process.env.CF_ACCESS_CLIENT_SECRET!,
  },
});

SDK-set headers win — putting Authorization here is a no-op; use the fetchImpl escape hatch (an option on createClient that lets you wrap the underlying fetch call) if you need to override Bearer.

createPullLoop

In-memory cursor; the bot owns persistence via onCursorAdvance. Use the hook to write the cursor to your bot's store on every advance. On crash, reload the cursor and pass it as initialCursor on next boot — or start at 0 and dedupe on event.id (event IDs are stable).

import { createPullLoop } from 'poly-rewards-shared';

const loop = createPullLoop({
  client,
  initialCursor: 0,        // or load from your bot's store
  pollIntervalMs: 5_000,
  batchSize: 200,
  onEvent: async (event) => {
    if (event.event_type === 'market.new') {
      // typed payload narrowed by event_type
      console.log('new market', event.condition_id);
    }
  },
  onCursorAdvance: async (cursor) => {
    // persist `cursor` to your bot's durable store here
  },
  onError: (err) => {
    console.warn('pull-loop error', err);
  },
});

loop.start();
// later — for tests: await loop.pollOnce();
// shutdown: await loop.stop();

If onEvent throws, the whole batch is retried with capped backoff (1s/5s/30s). After the 4th failure, onError is called once and the cursor stays put — bots that also receive the same events via push will see them again, OR can rewind manually.

createWebhookReceiver

Returns { fetch } you can hand straight to Bun.serve. The receiver:

  • Rejects non-POST methods with 405.
  • Verifies HMAC on the raw request bytes (so do not consume req.body before delegating; the smoke harness's wrapper sniffs headers only).
  • Replay-protects via toleranceMs (default 5 minutes).
  • Validates payloads against EventSchema.
  • Auto-handles system.ping: responds 200 with the challenge and bypasses onEvent. Bots don't need to handle ping themselves.
import { createWebhookReceiver } from 'poly-rewards-shared';

const receiver = createWebhookReceiver({
  secret: webhookSecret,                // returned from registerWebhook(...)
  onEvent: async (event) => {
    // type-narrow via event.event_type
    if (event.event_type === 'rewards.config_changed') {
      // ...
    }
  },
  onError: (err) => { /* HmacVerifyError | ValidationError | thrown errors */ },
});

Bun.serve({ port: 8080, fetch: receiver.fetch });

registerWebhook

Thin composite over client.webhooks.create. Engine generates the secret, sends a signed system.ping to the URL, and verifies via the challenge echo.

import { registerWebhook } from 'poly-rewards-shared';

const { id, secret, verified } = await registerWebhook(client, {
  url: 'https://my-bot.example.com/webhooks/rewards',
  events: ['*'],          // or specific event types
  channel: 'my-bot',
});
// `secret` is shown ONCE here — store it in your bot's secret store. Use it
// to construct createWebhookReceiver({ secret }).

signBody

Returns the engine's signature format t=<timestampMs>,v1=<hex>.

import { signBody } from 'poly-rewards-shared/hmac';

const signature = signBody({
  secret: 'your-shared-secret',
  timestampMs: Date.now(),
  body: JSON.stringify(payload),
});
// signature === 't=1715354400000,v1=2f5a...e1'

verifySignature

Returns a discriminated { ok: true } | { ok: false; reason } where reason is one of 'malformed' | 'expired' | 'mismatch'.

import { verifySignature } from 'poly-rewards-shared/hmac';

const result = verifySignature({
  secret,
  signature,            // 't=...,v1=...'
  timestampMs: Date.now(),
  body: rawBytes,
  toleranceMs: 5 * 60 * 1000,
});
if (!result.ok) {
  // result.reason ∈ {'malformed','expired','mismatch'}
}

createWebhookReceiver calls this internally. Use it directly only when you need custom dispatch (e.g. integrating into a non-Bun.serve framework).

Multi-bot consumers

Multiple bots can consume the engine simultaneously. Each runs its own pull-loop with its own initialCursor (persisted bot-side), or registers its own webhook with its own secret. The engine fans out events to all matching subscribers; consumers don't see each other's deliveries. v2 has only proven this with the smoke harness as a single consumer; the multi-consumer scenario will be re-validated in v3.

Versioning policy

poly-rewards-shared is published to npm. The package follows semver: patch-level for additive non-breaking changes, minor for backwards-compatible new helpers, major for any breaking change. Any change to the EventSchema discriminated union shape is a breaking change (existing bots' type-narrowing would silently break) and bumps the major version.

Workspace consumers continue to track workspace:* and ride whatever version sits in packages/shared/package.json; external consumers pin a published version range.