@provable-games/budokan-sdk
v0.1.38
Published
TypeScript SDK for Budokan — tournament management platform on Starknet
Readme
@provable-games/budokan-sdk
TypeScript SDK for Budokan — query and manage tournaments via REST API and Starknet RPC with automatic fallback.
Features
- Dual data source — API-first with automatic RPC fallback when the indexer is unavailable
- Health monitoring — Background
ConnectionStatusservice tracks API/RPC availability and auto-switches modes - React hooks — Provider, data hooks, and WebSocket subscriptions out of the box
- WebSocket subscriptions — Real-time tournament updates with auto-reconnect
- ESM + CJS — Dual build with full TypeScript declarations
- camelCase types — All public types use camelCase field names
Install
npm install @provable-games/budokan-sdk
# or
pnpm add @provable-games/budokan-sdkPeer dependencies (install if you need their features):
npm install starknet # Required for RPC calls
npm install react # Required for React hooksQuick Start
Basic Client
import { createBudokanClient } from "@provable-games/budokan-sdk";
const client = createBudokanClient({
chain: "mainnet",
});
// Fetch tournaments from API
const { data: tournaments } = await client.getTournaments();
console.log(tournaments[0].id, tournaments[0].name);
// Fetch a single tournament (API with automatic RPC fallback)
const tournament = await client.getTournament("42");
console.log(tournament.name, tournament.entryCount);
// Fetch leaderboard
const leaderboard = await client.getTournamentLeaderboard("42");React
import { BudokanProvider, useTournaments, useTournament } from "@provable-games/budokan-sdk/react";
function App() {
return (
<BudokanProvider
config={{
chain: "mainnet",
}}
>
<TournamentList />
</BudokanProvider>
);
}
function TournamentList() {
const { data, isLoading, error } = useTournaments();
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<ul>
{data?.data.map((t) => (
<li key={t.id}>{t.name}</li>
))}
</ul>
);
}WebSocket Subscriptions
import { useSubscription } from "@provable-games/budokan-sdk/react";
function TournamentFeed({ tournamentId }: { tournamentId: string }) {
useSubscription(
["registration", "submission"],
(message) => {
console.log("Event:", message.channel, message.data);
},
[tournamentId],
);
return <div>Listening for tournament updates...</div>;
}Whitelisted Games
The SDK ships a curated per-chain list of games and per-game UX metadata (homepage URLs, default entry-fee token, controller-only flag, etc.) that the official Budokan client uses to filter the on-chain denshokan registry. Other integrations — Telegram bot, third-party UIs — can use the same list to stay consistent.
import {
getWhitelistedGames,
findWhitelistedGame,
isGameWhitelisted,
getGameDefaults,
} from "@provable-games/budokan-sdk";
// Sorted by name, disabled entries last
const games = getWhitelistedGames("mainnet");
// → [{ contractAddress: "0x4de0...", name: "Death Mountain", url: "...", ... }, ...]
const dm = findWhitelistedGame("mainnet", "0x4de0351c..."); // address auto-normalized
const ok = isGameWhitelisted("sepolia", anyAddress);
// Defaults block — falls back to STRK / 1% / $0.25 when the game isn't whitelisted
const { minEntryFeeUsd, defaultEntryFeeToken, defaultGameFeePercentage } =
getGameDefaults("mainnet", gameAddress);The denshokan registry is still the source of truth for which games exist; this whitelist is a layer on top that callers can intersect with the registry to filter to "games we trust + display metadata for."
Telegram Tournament Bot Example
This repo includes a dependency-free Telegram bot example that lets a chat follow Budokan tournaments and receive live updates as registrations, scores, prizes, and reward claims land.
bun run build
TELEGRAM_BOT_TOKEN=123456789:your-token node examples/telegram-tournament-bot.mjsIn Telegram:
/follow 42
/tournament 42
/leaderboard 42
/play 42Full setup, testing, and deployment instructions are in examples/telegram-tournament-bot.md. Optional environment variables are listed in examples/telegram-tournament-bot.env.example. The bot persists chat follows in .telegram-tournament-bot-registrations.json by default. Tournament actions (entering, submitting a score, claiming a prize) are surfaced as deeplinks back to https://budokan.gg so the user signs with their Cartridge wallet in the browser.
Configuration
interface BudokanClientConfig {
chain?: "mainnet" | "sepolia"; // Default: "mainnet"
apiBaseUrl?: string; // REST API base URL
wsUrl?: string; // WebSocket URL
rpcUrl?: string; // Custom Starknet RPC endpoint
provider?: RpcProvider; // starknet.js provider (takes precedence over rpcUrl)
viewerAddress?: string; // BudokanViewer contract address
budokanAddress?: string; // Budokan contract address
primarySource?: "api" | "rpc"; // Default: "api"
retryAttempts?: number; // Default: 3
retryDelay?: number; // Default: 1000ms
timeout?: number; // Default: 10000ms
}Data Source Fallback
The SDK supports two data sources: API (REST indexer) and RPC (direct Starknet contract calls via BudokanViewer). Set primarySource: "api" (default) or primarySource: "rpc" in config. When the API goes down, methods with RPC support automatically fall back to direct contract calls.
Feature Support
| Method | API | RPC | Notes |
|--------|:---:|:---:|-------|
| Tournaments | | | |
| getTournaments(params?) | ✅ | ✅ | RPC groups phases: scheduled includes Scheduled+Registration+Staging, live includes Live+Submission |
| getTournament(id) | ✅ | ✅ | |
| getTournamentLeaderboard(id) | ✅ | ✅ | |
| getTournamentRegistrations(id) | ✅ | ✅ | RPC: playerAddress and gameAddress fields will be empty |
| getTournamentPrizes(id) | ✅ | ✅ | |
| getGameTournaments(addr) | ✅ | ✅ | |
| Prize Aggregation | | | |
| getTournamentPrizeAggregation(id) | ✅ | ❌ | API only |
| includePrizeSummary param | ✅ | ✅ | RPC fetches prizes per tournament and builds aggregation client-side |
| Rewards | | | |
| getTournamentRewardClaims(id) | ✅ | ✅ | RPC checks is_prize_claimed per prize via viewer |
| getTournamentRewardClaimsSummary(id) | ✅ | ✅ | RPC returns totals from viewer |
| getTournamentQualifications(id) | ✅ | ⚠️ | On-chain via get_qualification_entries — requires proof input, not yet wired |
| Players | | | |
| getPlayerTournaments(addr) | ✅ | ✅ | RPC iterates tournaments and checks entry ownership via ERC721 |
| getPlayerStats(addr) | ✅ | ❌ | API only — requires aggregated stats |
| Games | | | |
| getGameStats(addr) | ✅ | ❌ | API only — requires aggregated stats |
| Activity | | | |
| getActivity(params?) | ✅ | ❌ | API only — activity is indexed from events |
| getActivityStats() | ✅ | ❌ | API only — requires aggregated stats |
| getPrizeStats() | ✅ | ❌ | API only — requires aggregated stats |
| WebSocket | | | |
| subscribe(channels, handler) | ✅ | ❌ | Requires API WebSocket server |
RPC Behaviour
When primarySource: "rpc":
- All tournament queries go directly to the BudokanViewer contract — no API calls
- Phase filtering uses
tournaments_by_phasesfor grouped queries (e.g., "scheduled" queries 3 phases in 1 RPC call) - Prize aggregation for tournament cards is built client-side from per-tournament prize data
- API-only methods will throw an error — they require the indexed API
- Stale data is automatically cleared when switching networks
API Reference
Client Methods
Tournaments — getTournaments(params?), getTournament(id), getTournamentLeaderboard(id), getTournamentRegistrations(id, params?), getTournamentPrizes(id)
Rewards & Qualifications — getTournamentRewardClaims(id, params?), getTournamentRewardClaimsSummary(id), getTournamentQualifications(id, params?), getTournamentPrizeAggregation(id)
Players — getPlayerTournaments(address, params?), getPlayerStats(address)
Games — getGameTournaments(gameAddress, params?), getGameStats(gameAddress)
Activity — getActivity(params?), getActivityStats(), getPrizeStats()
WebSocket — connect(), disconnect(), subscribe(channels, handler, tournamentIds?), onWsConnectionChange(listener)
Utilities — getConnectionStatus(), onConnectionStatusChange(listener), destroy()
React Hooks
All data hooks return { data, isLoading, error, refetch }.
Data — useTournaments(params?), useTournament(id), useLeaderboard(tournamentId), usePlayerTournaments(address, params?), usePlayerStats(address), usePlayer(address)
Rewards & Prizes — useRewardClaims(tournamentId), useRewardClaimsSummary(tournamentId), usePrizes(tournamentId), usePrizeStats(), useQualifications(tournamentId)
WebSocket — useSubscription(channels, handler, tournamentIds?)
Context — useBudokanClient(), useConnectionStatus()
Error Handling
import { BudokanError, BudokanApiError, DataSourceError } from "@provable-games/budokan-sdk";
try {
const tournament = await client.getTournament("42");
} catch (error) {
if (error instanceof DataSourceError) {
console.log("Primary failed:", error.primaryError.message);
console.log("Fallback failed:", error.fallbackError.message);
} else if (error instanceof BudokanApiError) {
console.log("HTTP status:", error.statusCode);
}
}Error classes: BudokanError, BudokanApiError, BudokanTimeoutError, BudokanConnectionError, TournamentNotFoundError, RpcError, DataSourceError.
Development
bun install
bun run build # ESM + CJS to dist/
bun run typecheck # TypeScript validation
bun run dev # Watch modePublishing
Publishing is automated via GitHub Actions. To release:
- Bump the version in
package.json - Create a GitHub Release (e.g.
v0.1.0) - The
publish.ymlworkflow runs typecheck, build, and publishes to npm
Requires an NPM_TOKEN secret configured in the repo settings.
License
MIT
