Create a tournament
Check creator eligibility, fund the stage-1 buy-in on-chain, then author a bracket tournament.
By the end: you'll know the full paid create flow: eligibility, vault deposit, then admin create, plus optional protocol-admin ops.
Prerequisites
tournamentsBaseUrlandtournamentsAuth(JWT from marketseoaLogin)- Markets-host JWT that can call
POST /bento/user/wallet/send-transaction(managed smart wallet) - Credits or USDC balance for the stage-1 buy-in (testnet: auto-mint faucet after register; register alone does not fund you)
- Tournament vault + collateral token addresses for the chain (credits vault for
stakeAsset: 'credits')
Important: create is not a single POST
The host enforces a minimum stage-1 buy-in of 5 USDC or 5 credits. Free (buyinUsdc: '0') is rejected.
For any stage-1 buy-in greater than 0, the API requires:
| Field | Meaning |
|---|---|
clientTournamentId | 24-character hex id you generate before create |
creatorDepositTxHash | Vault deposit tx for that id, stage 1, buy-in amount |
Skipping the vault step returns HTTP 400:
Paid tournaments require clientTournamentId … deposit to the vault … creatorDepositTxHash.
Fund the vault, then POST /api/tournaments/admin/tournaments. Create persists the tournament as LIVE and registers the creator entry. There is no separate activate call in the create path.
Check eligibility
import { createBentoSdk, jwtAuthProvider, walletAuthProvider } from '@bento.fun/sdk';
const token = process.env.BENTO_JWT!;
const sdk = createBentoSdk({
baseUrl: process.env.BENTO_URL!, // https://internal-server.bento.fun
apiKey: process.env.BENTO_BUILDER_API_KEY!,
tournamentsBaseUrl: process.env.PARLAY_TOURNAMENT_URL,
auth: walletAuthProvider(() => ({ Authorization: `Bearer ${token}` })),
tournamentsAuth: jwtAuthProvider({ getAccessToken: () => token }),
});
const stage1BuyinRaw = '5000000000000000000'; // 5 credits (18 decimals)
const eligibility = await sdk.tournaments!.tournaments.getCreatorCreateFlowEligibility({
wallet: managedWalletAddress,
stakeAsset: 'credits',
stage1BuyinRaw,
minEntriesToActivate: 2,
});
// Inspect eligibility.canProceed before fundingFund creator buy-in (on-chain), then create
Admin create is not wrapped on createBentoSdk().tournaments. Call the tournaments host admin route with a Bearer JWT.
import { buildVaultDepositCalldata } from '@bento.fun/sdk';
import { encodeFunctionData, erc20Abi, maxUint256 } from 'viem';
const stage1BuyinRaw = '5000000000000000000'; // 5 units
// 12 random bytes as a 24-character hex id.
const clientTournamentId = Array.from(
crypto.getRandomValues(new Uint8Array(12)),
(b) => b.toString(16).padStart(2, '0'),
).join('');
const creditsToken = process.env.CREDITS_TOKEN!; // ERC-20
const creditsVault = process.env.TOURNAMENT_VAULT_CREDITS!;
// 1) Approve vault to pull credits (via managed wallet relay)
await fetch(`${process.env.BENTO_URL}/bento/user/wallet/send-transaction`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
to: creditsToken,
data: encodeFunctionData({
abi: erc20Abi,
functionName: 'approve',
args: [creditsVault, maxUint256],
}),
value: '0x0',
}),
});
// 2) Vault deposit for stage 1 (binds clientTournamentId on-chain)
const depositRes = await fetch(`${process.env.BENTO_URL}/bento/user/wallet/send-transaction`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
to: creditsVault,
data: buildVaultDepositCalldata(clientTournamentId, 1, '5', 18),
value: '0x0',
}),
});
const { txHash: creatorDepositTxHash } = await depositRes.json();
// 3) Author the tournament (tournaments admin route, your Bearer JWT)
const created = await fetch(
`${process.env.PARLAY_TOURNAMENT_URL}/api/tournaments/admin/tournaments`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
ownerWallet: managedWalletAddress,
name: 'My bracket',
type: 'PROTOCOL',
sport: 'Football',
stakeAsset: 'credits',
vaultStack: 'credits',
clientTournamentId,
creatorDepositTxHash,
totalStages: 2,
stages: [
{ stageIndex: 1, buyinUsdc: stage1BuyinRaw, multiplier: 1, isFinal: false },
{ stageIndex: 2, buyinUsdc: stage1BuyinRaw, multiplier: 2, isFinal: true },
],
config: { minEntriesToActivate: 2, stakeAsset: 'credits' },
}),
},
);
const { tournament } = await created.json();
// tournament.id === clientTournamentId when paid create succeedsbuyinUsdc is always the raw amount string (the field name is historical). Credits and BSC-testnet USDC use 18 decimals; Base USDC uses 6.
Optional follow-ups (not required for create):
- Stage builder / markets: admin stage endpoints
POST .../admin/tournaments/:id/activate: exists, but create already lands LIVE
Exact body fields vary by format; see OpenAPI or tournamentsAdmin in the SDK API reference.
Participant flow
Once live, other players use the Enter a tournament guide (depositAndEnterTournament, claims). That path deposits against an existing tournament id; creator create deposits before the tournament exists, using clientTournamentId.
Protocol-admin lifecycle
Settlement, disputes, automation, and cancellation run through the gated createTournamentsProtocolAdminClient, a separate opt-in client, not part of createBentoSdk():
import { createTournamentsProtocolAdminClient, jwtAuthProvider } from '@bento.fun/sdk';
const admin = createTournamentsProtocolAdminClient({
baseUrl: process.env.PARLAY_TOURNAMENT_URL!,
auth: jwtAuthProvider({ getAccessToken: () => process.env.TOURNAMENTS_ADMIN_JWT! }),
});
await admin.protocol.runAutomation();
await admin.protocol.settleReadyStages();
await admin.protocol.setPayoutRootOnchain(tournamentId);| Phase | admin.protocol methods |
|---|---|
| Automation | runAutomation, settleReadyStages, processTimeouts, getAutomationStatus |
| Settle | forceSettleStage, getPayoutRoot, prepareSetRoot, setPayoutRootOnchain, setRefundRootOnchain |
| Disputes | listOpenDisputes, getDisputeStats, resolveDispute, processExpiredDisputes |
| Cancel | cancelTournament, discardTournament, listCancelRequests, rejectCancelRequest |
Full method list: SDK API reference → TournamentsProtocolAdminClient.
Related
- Enter a tournament: player deposit, chip picks on matches, claims
- Authentication: Bearer JWT
- Common patterns: paid-create errors
- SDK API reference: admin protocol methods