Budokan SDK
The Budokan SDK (@provable-games/budokan-sdk) provides a TypeScript client for querying tournaments, players, prizes, and leaderboards. It supports both REST API and on-chain RPC queries with automatic fallback, plus WebSocket subscriptions for real-time updates.
Installation
npm install @provable-games/budokan-sdk
# or
bun add @provable-games/budokan-sdkQuick Start
import { createBudokanClient } from "@provable-games/budokan-sdk";
const client = createBudokanClient({ chain: "mainnet" });
// Fetch tournaments
const { data: tournaments } = await client.getTournaments({ limit: 10 });
// Fetch a single tournament
const tournament = await client.getTournament("1");
// Fetch leaderboard
const leaderboard = await client.getTournamentLeaderboard("1");Configuration
import { BudokanClient } from "@provable-games/budokan-sdk";
const client = new BudokanClient({
chain: "mainnet", // or "sepolia" — sets defaults for all URLs and addresses
// Override any defaults:
apiBaseUrl: "https://budokan-api-production.up.railway.app",
wsUrl: "wss://budokan-api-production.up.railway.app/ws",
rpcUrl: "https://api.cartridge.gg/x/starknet/mainnet/rpc/v0_10",
rpcHeaders: { Authorization: "Bearer <token>" }, // optional RPC auth
viewerAddress: "0x013c8239361fdbd7ec26db2c83f4ff270c5bba83a0bc105b4005b676ff57fdbe",
primarySource: "api", // "api" (default) or "rpc"
timeout: 10000,
retryAttempts: 3,
retryDelay: 1000,
});Chain Presets
When you specify chain: "mainnet" or chain: "sepolia", the SDK uses these defaults:
Mainnet
| Service | URL |
|---|---|
| API | https://budokan-api-production.up.railway.app |
| WebSocket | wss://budokan-api-production.up.railway.app/ws |
| RPC | https://api.cartridge.gg/x/starknet/mainnet/rpc/v0_10 |
| Contract | Address |
|---|---|
| Budokan | 0x0596ced030e74ebc37f33607f07ecd5a62eff22cdc4ae31fe2d724040c1bdc0b |
| Viewer | 0x013c8239361fdbd7ec26db2c83f4ff270c5bba83a0bc105b4005b676ff57fdbe |
Sepolia
| Service | URL |
|---|---|
| API | https://budokan-api-sepolia.up.railway.app |
| WebSocket | wss://budokan-api-sepolia.up.railway.app/ws |
| RPC | https://starknet-sepolia.public.blastapi.io |
| Contract | Address |
|---|---|
| Budokan | 0x017750a167b7c4968249d7db06dccc8b3908ef8954cb40cfe4d3c651ca0dcd1d |
| Viewer | 0x03d5febe0042b943967074f4ebd850a6b5d50850cd3fb84fbd0eb66dadd9ddec |
Tournament Queries
// List tournaments with filtering
const { data, total } = await client.getTournaments({
gameAddress: "0x...",
phase: "live",
creator: "0x...",
limit: 20,
offset: 0,
sort: "created_at",
includePrizeSummary: true,
});
// Single tournament (returns null if not found)
const tournament = await client.getTournament("42");
// Leaderboard
const entries = await client.getTournamentLeaderboard("42");
// entries: [{ position: 1, tokenId: "0x..." }, ...]
// Registrations
const { data: registrations } = await client.getTournamentRegistrations("42");
// Prizes
const prizes = await client.getTournamentPrizes("42");
// Prize aggregation (totals by token)
const aggregation = await client.getTournamentPrizeAggregation("42");
// Reward claims
const { data: claims } = await client.getTournamentRewardClaims("42");
const summary = await client.getTournamentRewardClaimsSummary("42");
// Qualification entries
const { data: qualifications } = await client.getTournamentQualifications("42");
// Custom-distribution shares (only populated for Distribution::Custom tournaments)
const shares = await client.getTournamentDistributionShares("42");
// shares: number[] — basis points, summing to 10000Player queries: the client doesn't expose direct
getPlayer*methods. To fetch a player's tournaments or earnings, use the React hooksuseTournamentsByOwnerandusePlayerRewards, which compose the underlying token-ownership data.
Game Queries
// Tournaments for a specific game
const { data } = await client.getGameTournaments("0x...", { phase: "live" });
// Game statistics
const gameStats = await client.getGameStats("0x...");Activity & Platform Stats
// Platform-wide stats
const platformStats = await client.getActivityStats();
// { totalTournaments, totalPrizes, totalRegistrations, totalSubmissions }
// Prize stats
const prizeStats = await client.getPrizeStats();WebSocket Subscriptions
// Connect to WebSocket
client.connect();
// Subscribe to channels
const unsubscribe = client.subscribe(
["tournaments", "registrations", "leaderboards"],
(message) => {
console.log(message.channel, message.data);
},
["42", "43"] // optional: filter by tournament IDs
);
// Check connection status
console.log(client.wsConnected);
// Listen for connection changes
const off = client.onWsConnectionChange((connected) => {
console.log("WebSocket connected:", connected);
});
// Cleanup
unsubscribe();
client.disconnect();Available channels: tournaments, registrations, leaderboards, prizes, rewards, metrics
Data Source & Fallback
The SDK supports dual data sources with automatic fallback:
- API-first (default): Queries the REST API, falls back to RPC if API is unavailable
- RPC-first: Set
primarySource: "rpc"to prefer on-chain reads via the BudokanViewer contract
// Monitor connection status
const status = client.getConnectionStatus();
// { api: "connected" | "unavailable", rpc: "connected" | "unavailable", mode: "api" | "rpc" }
const off = client.onConnectionStatusChange((status) => {
console.log("Data source mode:", status.mode);
});React Hooks
import { BudokanProvider, useBudokanClient } from "@provable-games/budokan-sdk/react";Setup
Wrap your app with BudokanProvider:
import { BudokanProvider } from "@provable-games/budokan-sdk/react";
function App() {
return (
<BudokanProvider config={{ chain: "mainnet" }}>
<MyTournamentApp />
</BudokanProvider>
);
}Data Hooks
Most data hooks return { data, loading, error, refetch } where refetch() returns Promise<void>. The exception is usePlayerRewards, which returns { rewards, loading, error, refetch }.
import {
useTournaments,
useTournament,
useTournamentCount,
useLeaderboard,
useRegistrations,
useTournamentsByOwner,
useTournamentsByOwnerCount,
useRegistrationsByOwner,
usePlayerRewards,
usePrizes,
usePrizeStats,
usePrizeAggregation,
useRewardClaims,
useRewardClaimsSummary,
useQualifications,
useActivityStats,
} from "@provable-games/budokan-sdk/react";
// Tournaments
const { data: tournaments, loading } = useTournaments({ phase: "live", limit: 10 });
const { data: tournament } = useTournament("42");
const { data: count } = useTournamentCount({ phase: "live" });
// Leaderboard & Registrations
const { data: leaderboard } = useLeaderboard("42");
const { data: registrations } = useRegistrations("42");
// Player-scoped views (resolved via current Budokan token ownership)
const { data: ownedTournaments } = useTournamentsByOwner("0x...", { phase: "live" });
const { data: ownedCount } = useTournamentsByOwnerCount("0x...");
const { data: ownedRegistrations } = useRegistrationsByOwner("42", "0x...");
// Aggregated placements/earnings across finalized tournaments the player holds
// tokens in. Requires a DenshokanProvider in the tree alongside BudokanProvider.
const { rewards } = usePlayerRewards("0x...");
// Prizes & Rewards
const { data: prizes } = usePrizes("42");
const { data: aggregation } = usePrizeAggregation("42");
const { data: claims } = useRewardClaims("42");
const { data: claimsSummary } = useRewardClaimsSummary("42");
// Qualifications & Activity
const { data: qualifications } = useQualifications("42");
const { data: activityStats } = useActivityStats();Subscription Hooks
import { useSubscription, useConnectionStatus } from "@provable-games/budokan-sdk/react";
// Subscribe to real-time updates
const { lastMessage } = useSubscription(
["tournaments", "registrations"],
["42"] // optional tournament ID filter
);
// Monitor connection
const { connected } = useConnectionStatus();Error Handling
import {
BudokanError,
BudokanApiError,
BudokanTimeoutError,
BudokanConnectionError,
TournamentNotFoundError,
RpcError,
DataSourceError,
} from "@provable-games/budokan-sdk";
try {
const tournament = await client.getTournament("999999");
} catch (error) {
if (error instanceof TournamentNotFoundError) {
console.log("Tournament not found");
} else if (error instanceof BudokanApiError) {
console.log("API error:", error.statusCode);
}
}