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

@bento.fun/sdk

v0.7.2

Published

Official TypeScript SDK for Bento prediction markets, packs, parlays, tournaments, fantasy, portfolio, and realtime.

Readme

@bento.fun/sdk

Official TypeScript SDK for Bento — prediction markets, packs, parlays, bracket tournaments, F1, and portfolio.

npm version License: MIT Documentation

Features

  • Two API hosts — markets (baseUrl) and tournaments / parlay (tournamentsBaseUrl) from one factory
  • Contract-backed types — OpenAPI-generated TypeScript for request/response shapes
  • Wallet-agnostic — bring Privy, Wagmi, Turnkey, or server signers; SDK accepts headers and viem wallets
  • Typed HTTP methods — public reads, authenticated trading, parlays, bracket tournaments, and gated admin
  • On-chain orchestration — optional viem helpers for parlay placement, vault deposit/claim, LP

Requirements

  • Node.js 18+
  • TypeScript 5+ (recommended)

Install

npm install @bento.fun/sdk

Quick start

Every createBentoSdk call needs a Builder API key (apiKey). Catalog reads still work without a user login. sdk.user (bets, create market) also needs a Bearer JWT from eoaLogin.

import {
  createBentoSdk,
  jwtAuthProvider,
  walletAuthProvider,
} from '@bento.fun/sdk';

const baseUrl = process.env.BENTO_URL!; // https://internal-server.bento.fun on testnet
const tournamentsBaseUrl = process.env.PARLAY_TOURNAMENT_URL; // optional
const BUILDER_API_KEY = process.env.BENTO_BUILDER_API_KEY!;

// apiKey is required; public reads ignore the empty user JWT slot
const sdk = createBentoSdk({
  baseUrl,
  apiKey: BUILDER_API_KEY,
  tournamentsBaseUrl,
  auth: walletAuthProvider(() => ({})),
});
const { data } = await sdk.public.listDuels({ page: 1, limit: 20 });

// Get a Bearer JWT (see Authentication below). A brand-new wallet must register
// first — eoaLogin returns { exists: false } (no token) until then.
const token = await getBearerToken();

// Authenticated client. walletAuthProvider only injects headers, so put the
// Bearer token inside it for the markets host.
const authed = createBentoSdk({
  baseUrl,
  tournamentsBaseUrl,
  apiKey: BUILDER_API_KEY,
  auth: walletAuthProvider(() => ({ Authorization: `Bearer ${token}` })),
  tournamentsAuth: jwtAuthProvider({ getAccessToken: () => token }),
});

// Use duelId (on-chain), not id (database row).
const row = data[0];
if (row?.duelId) {
  await authed.public.getDuelById({ duelId: row.duelId });
}

Authentication

Two layers: the builder API key (apiKey → x-builder-api-key) identifies your app; the user JWT (auth) identifies the user. createBentoSdk requires apiKey (type + runtime).

| Host | Config | Headers | | ------------------------ | ----------------- | --------------------------------------------------------------------------------------- | | Markets (sdk.user) | auth | Authorization: Bearer <JWT> from public.auth.eoaLogin | | Tournaments | tournamentsAuth | Bearer JWT (from tournaments.auth.login) or legacy x-wallet-* signature headers | | All hosts | apiKey | x-builder-api-key: <key> |

Both hosts sign Bento.fun Login\nTimestamp: …\nWallet: …. The tournaments x-wallet-* header path is legacy and verifies a different message (Bento.fun Authentication\n…) — prefer the Bearer JWT.

walletAuthProvider only injects headers — for markets pass Bearer inside it:

const ts = String(Date.now());
const signature = await wallet.signMessage({
  message: `Bento.fun Login\nTimestamp: ${ts}\nWallet: ${address}`,
});

// eoaLogin returns { exists: false } (HTTP 200, no token) for a wallet that has
// never registered. Register once to mint the account + JWT; afterwards eoaLogin
// returns { exists: true, token }.
let res = await publicClient.auth.eoaLogin({ address, signature, timestamp: ts });
if (!res.exists) {
  // username is required; mainnet also requires inviteCode.
  res = await publicClient.auth.eoaRegister({
    address,
    signature,
    timestamp: ts,
    username,
  });
}
const token = res.token as string;

| Provider | Use for | | -------------------- | ---------------------------------------------------- | | walletAuthProvider | Inject Authorization: Bearer (or tournaments headers) | | jwtAuthProvider | Bearer JWT |

Weblink auth: sdk.public.externalLink.getLinkUrl → user confirms on /connect → exchange({ code }) → you store the JWT. See docs/auth.md.

See Authentication and Accounts & wallets (signing key ≠ managed account address).

Important semantics

  • HTTP acceptance ≠ on-chain finality — poll reads after mutations.
  • duelId vs id — list rows have both; use duelId for getDuelById / bets.
  • Amounts in wei — bet/parlay amounts are base units (18 decimals on BSC/credits), not whole tokens.
  • Prefer placeBetFromEstimate — avoids slippage 400s from hand-built min_shares_out.
  • credits vs usdc — collateralMode on markets; credits stack is not geo-gated like USDC.
  • Referral analytics — sdk.user.referralAnalytics.getMine({ collateralStack: 'usdc' | 'credits' }); see backend REFERRAL_ANALYTICS.md deploy checklist.

On-chain (optional)

Requires 0.5.4+ for correct parlay/LP approval amounts.

await sdk.onchain!.placeParlayFromQuoteId(quoteId);
await sdk.onchain!.depositAndEnterTournament(tournamentId, { amountUsdc: '100' });

Handling errors

Every failure is a BentoSdkErrorException carrying a typed record. Inspect it with the helpers. Do not reach for isBentoSdkError on the caught value, which narrows the record type, not the exception that was thrown, and so is always false:

import {
  isAuthError, isValidationError, isRateLimited,
  getFieldErrors, getRetryAfterMs, getRequestId, getStatus, getErrorCode,
} from '@bento.fun/sdk';

try {
  await sdk.user.placeBet(body);
} catch (err) {
  if (isRateLimited(err)) {
    await sleep(getRetryAfterMs(err) ?? 1_000);
  } else if (isValidationError(err)) {
    showFieldErrors(getFieldErrors(err));   // { amount: ['too small'] }
  } else if (isAuthError(err)) {
    await refreshSession();
  } else {
    // Quote this in a bug report: it is the server's id when the backend echoes
    // `x-request-id`, so it can be found in backend logs.
    report({ status: getStatus(err), code: getErrorCode(err), requestId: getRequestId(err) });
  }
}

Branch on getErrorCode(err), not on message; messages are prose and will change.

Rate limiting

Bento enforces a limit per builder key: 120/min for team-issued keys, 300/min for self-serve testnet keys, configurable per app. It is a shared budget, so a burst on one endpoint returns 429 for everything that key is doing.

The SDK can pace calls before they leave your process. This is off by default:

const sdk = createBentoSdk({
  baseUrl, apiKey, auth,
  rateLimit: {
    default: { limit: 120, intervalMs: 60_000 },        // your key's ceiling
    endpoints: {
      'POST /bento/user/bets/create': { limit: 20, intervalMs: 60_000 },
      '/bento/user': { limit: 60, intervalMs: 60_000 },  // longest prefix wins
    },
    onLimit: 'wait',   // or 'throw' to shed load instead of blocking
  },
});

Rules match most-specific-first: METHOD /exact/path, then /exact/path, then the longest matching path prefix, then default. Paths work with or without the /bento prefix, so you can copy keys straight from the API reference.

The window is fixed and clock-aligned, matching how the server counts (floor(now / intervalMs)). That is deliberate: a smooth token bucket would allow a full burst plus steady refill inside one server window and still trip the limit at your configured ceiling.

On a server 429 the SDK also pauses that endpoint locally. The markets backend sends no Retry-After, so the pause defaults to the remainder of the current window, which is when the server's own counter resets.

This is politeness, not enforcement. The server remains the authority.

Documentation

https://docs.bento.fun

| Resource | Description | | ----------------------------------------------------------------------- | --------------------------------------------------------- | | Quickstart | Zero → first bet (golden path) | | Cookbook | ~15 methods that cover most apps | | Authentication | Bearer login / register flow | | Common patterns | IDs, errors, rate limits | | Test the SDK | sdk-sandbox | | SDK API reference | Every method + route | | llms.txt | AI-friendly docs index |

Links

License

MIT — see LICENSE.