BentoSDK

Common patterns & pitfalls

Practical tips for integrating @bento.fun/sdk, covering IDs, response shapes, auth, and errors.

Answers to questions that come up often when wiring the SDK against live APIs.

Two hosts, one factory

const sdk = createBentoSdk({
  baseUrl: process.env.BENTO_URL!,
  tournamentsBaseUrl: process.env.PARLAY_TOURNAMENT_URL,
  auth: walletAuthProvider(() => ({ Authorization: `Bearer ${token}` })),
  tournamentsAuth: jwtAuthProvider({ getAccessToken: () => token }),
});
ClientHost envTypical auth
sdk.public / sdk.userBENTO_URLBearer JWT on user
sdk.tournamentsPARLAY_TOURNAMENT_URLJWT on tournamentsAuth

See Two API hosts.

id vs duelId on market rows

listDuels / listMarkets return rows with two identifiers:

FieldExampleUse for
id6a1ef4329676cfd69ceec4f0Database row id: do not pass to getDuelById
duelId94c88855f750…On-chain duel id: use this for detail, bets, charts
const { data } = await sdk.public.listDuels({ page: 1, limit: 10 });
const row = data[0];

// ✓ Correct
await sdk.public.getDuelById({ duelId: row.duelId });

// ✗ 404: wrong id type
await sdk.public.getDuelById({ duelId: row.id });

Amounts are in wei

betAmountUsdc and betAmount are base units of the collateral token (wei), not whole tokens. On BSC / credits the collateral is 18 decimals, so 10 units is '10000000000000000000'; passing '10' means 10 wei, effectively zero.

Scale by the token's decimals (use collateralDecimals from your onchain.contracts, e.g. viem's parseUnits('10', 18)).

Collateral: credits vs USDC

Markets and bets carry a collateralMode of 'usdc' or 'credits':

ModeWhat it isGeo
usdcon-chain USDC collateralgeo-gated: restricted regions get 403 with code GEO_BLOCKED
creditsa platform-managed balancenot geo-gated; usable where usdc is blocked

Either mode needs a funded balance, or writes fail with 400 (Insufficient Credits / insufficient balance). Fund the managed account before writing. On testnet, register/login does not fund you; call POST /bento/auto-mint/mint with { userAddress: managedAccountAddress } (~1000 USDC + ~1000 credits). See Money.

Wrapped API responses

Some tournaments endpoints return objects, not bare arrays:

// Bracket tournaments list
const { tournaments, total } = await sdk.tournaments!.tournaments.list({
  status: 'active',
}) as { tournaments: unknown[]; total: number };

Check the Tournaments OpenAPI schema when typing responses.

HTTP acceptance ≠ on-chain finality

placeBet, sellBet, createDuel, duel invite userJoin, pack picks, and tournament enters return when the server accepted the request, not when the chain finalized.

After mutations:

  1. Poll reads (getDuelById, getUserShares, tournament status)
  2. Use waitFor* helpers where provided

See Mutation semantics.

Auth providers by integration

Your appProviderPass as
Browser walletwalletAuthProviderauth
JWT session (tournaments)jwtAuthProvidertournamentsAuth

See Authentication.

Idempotency for bets

Always pass an idempotency key when placing bets:

// Estimate first (see Place a bet), then place with the quote fields:
await sdk.user.placeBet(
  { duelId, duelType, bet: 'optionA', optionIndex: 0, betAmount, betAmountUsdc,
    sharesOut, minSharesOut, slippageBps: 100, quoteId },
  { idempotencyKey }, // caller-stable, not Date.now()
);

Errors

Failed HTTP calls throw BentoSdkErrorException with error.sdkError:

import { isGeoblockError } from '@bento.fun/sdk';

try {
  await sdk.user.placeBet(body, { idempotencyKey });
} catch (e) {
  // NB: pass the `.sdkError` record to the guards, not the caught exception
  const err = (e as { sdkError?: { kind: string; status?: number; correlationId?: string } }).sdkError;
  if (isGeoblockError(err)) {
    // geo-blocked: detects the 403 with code GEO_BLOCKED the API actually returns (and 451, defensively)
  } else if (err?.kind === 'auth_error') {
    // 401 / 403: message, status, and code when the body carries one (no correlationId)
  } else if (err?.kind === 'http_error') {
    console.log(err.status, err.correlationId);
  }
}

Error kinds

Each sdkError.kind carries different fields; branch on kind, not on status:

kindHTTPFields present
http_error404, 451, 5xxstatus, correlationId, message, details
auth_error401, 403message, status, code? (no correlationId)
validation_error400, 422status, fieldErrors?, message
rate_limited429retryAfterMs?, message

Common errors

You seeCauseFix
401 / 403 (auth_error) on sdk.usermissing or invalid Bearer JWTlog in (eoaLogin) and send Authorization: Bearer; markets is Bearer, not x-wallet-*
400 Insufficient Credits / insufficient balancemanaged account unfunded in the chosen collateralModefund via testnet POST /bento/auto-mint/mint (or switch mode)
400 Start time must be in the future on createDuelstartTime ≤ nowset startTime strictly ahead of wall clock
500 Pre-flight simulation failed on createDuelon-chain requestCreateDuel reverted in the managed (custodial) wallet simulation; often startTime only a few minutes aheaduse a larger offset (~31 min ahead public / ~5 min private); see Create a market
500 Unable to place bet… after a good estimateamount below the 5-unit platform / on-chain minimum (estimateBuy does not enforce it)stake ≥ 5 units; pass tokenDecimals so the SDK rejects early; see Place a bet
400 Paid tournaments require clientTournamentIdstage-1 buy-in > 0 without a prior vault depositgenerate 24-hex id → approve + vault deposit → create with creatorDepositTxHash; see Create a tournament
404 from getDuelById / getMarketByIdpassed the database id, not duelIduse duelId from the list row
403 with code GEO_BLOCKED (auth_error)region geo-gated on the usdc stack, not a JWT problemcheck isGeoblockError before treating a 403 as auth; use collateralMode: 'credits'
429 (rate_limited)rate limitedback off at least retryAfterMs before retrying
sdk.onchain.* returns { success: false }on-chain methods never throwbranch on result.success; read result.error

Rate limits

A 429 surfaces as kind: 'rate_limited'. The SDK parses Retry-After into retryAfterMs and honours it in automatic retry backoff (0.5.4+). When handling errors yourself, still wait at least retryAfterMs before retrying.

Security

  • Wallet auth on sdk.user is a Bearer JWT from eoaLogin / eoaRegister. See Authentication.
  • collateralMode: 'credits' bypasses geo-restrictions; the usdc stack is geo-gated.
  • Never hard-code private keys, API keys, or access codes; keep them in env / secrets.

Where to look next

NeedPage
Every SDK method + routeSDK API reference
JSON request/response shapesMarkets · Tournaments OpenAPI
Smoke-test published npm packageTest the SDK
End-to-end flowsQuickstart · Guides

On this page