Quickstart
Make your first Bento API call in a few minutes, then add a login to place a bet.
Install
npm install @bento.fun/sdkNode.js 18+.
Get a Builder API key
createBentoSdk requires a Builder API key (apiKey). Catalog reads are
open without it on the wire, but login / register / weblink and authenticated
calls need x-builder-api-key so your app is attributed.
Generate a testnet key (hackathon / docs form), then:
export BENTO_BUILDER_API_KEY='bnt_…' # from the form
export BENTO_URL='https://internal-server.bento.fun'Your first call
Public reads (catalog, prices, analytics) need no user login. Pass apiKey
anyway; you'll need it for login next:
import { createBentoSdk, walletAuthProvider } from '@bento.fun/sdk';
// createBentoSdk requires apiKey + auth; public reads ignore the user JWT slot
const sdk = createBentoSdk({
baseUrl: 'https://internal-server.bento.fun',
apiKey: process.env.BENTO_BUILDER_API_KEY!,
auth: walletAuthProvider(() => ({})),
});
const { data } = await sdk.public.listDuels({ page: 1, limit: 10 });
console.log(`${data.length} live markets`);
// open one: use duelId (the on-chain id), NOT id (the database row id)
const market = await sdk.public.getDuelById({ duelId: data[0].duelId });That's your first call. duelId vs id trips up everyone, so always pass duelId.
Log in (to act as a user)
Reads need only the Builder key; placing bets, creating markets, entering tournaments also need a user JWT. You sign a short message with your wallet and trade it for a JWT:
const ts = String(Date.now());
const signature = await account.signMessage({
message: `Bento.fun Login\nTimestamp: ${ts}\nWallet: ${account.address}`,
});
// new wallet → eoaRegister({ ..., username }); existing wallet → eoaLogin
const { token } = (await sdk.public.auth.eoaLogin({
address: account.address,
signature,
timestamp: ts,
})) as { token: string };
// authenticated client: markets auth is a Bearer JWT
const authed = createBentoSdk({
baseUrl: 'https://internal-server.bento.fun',
apiKey: process.env.BENTO_BUILDER_API_KEY!,
auth: walletAuthProvider(() => ({ Authorization: `Bearer ${token}` })),
});account is your wallet (e.g. a viem account). The login response includes your managed account address (where balances live). On testnet, fund it with the auto-mint faucet before betting; register alone does not credit USDC or credits. → Authentication · Money
Place a bet
Estimate first, then place. Amounts are wei (18 decimals); '1000000000000000000' is 1 unit:
const duelId = data[0].duelId; // from your first call above
const idempotencyKey = crypto.randomUUID(); // dedupes retries, optional; the SDK generates one if omitted
const est = await authed.user.bets.estimateBuy({
duelId,
optionIndex: 0, // 0 or 1, not a 'YES' / 'NO' label
betAmountUsdc: '1000000000000000000',
slippageBps: 100,
});
if (!est.success) throw new Error('estimate rejected');
await authed.user.placeBet(
{
duelId,
duelType: 'prediction',
bet: 'optionA',
optionIndex: 0,
betAmount: '1000000000000000000',
betAmountUsdc: '1000000000000000000',
sharesOut: est.estimate.shares_out,
minSharesOut: est.estimate.min_shares_out,
slippageBps: 100,
quoteId: est.estimate.quote_id,
},
{ idempotencyKey },
);A write returns when the server accepted it, not when the chain settled. Poll a read to confirm. → Place a bet
Tournaments (second host)
Bracket tournaments and F1 live on a second host. Add tournamentsBaseUrl when you need them:
import { jwtAuthProvider } from '@bento.fun/sdk';
const sdk = createBentoSdk({
baseUrl: 'https://internal-server.bento.fun',
apiKey: process.env.BENTO_BUILDER_API_KEY!,
tournamentsBaseUrl: process.env.PARLAY_TOURNAMENT_URL, // tournaments host URL
auth: walletAuthProvider(() => ({ Authorization: `Bearer ${token}` })),
tournamentsAuth: jwtAuthProvider({ getAccessToken: () => token }),
});
const { tournaments } = (await sdk.tournaments!.tournaments.list({
limit: 5,
})) as { tournaments: unknown[] };→ Two API hosts · Enter a tournament (buy-in, then chip picks on matches)