@bountyboard/arcade-sdk
v1.1.0
Published
Bounty Board Arcade SDK — leaderboards, cloud saves, rewarded ads, A/B variants, and multiplayer rooms for games on bountyboard.gg
Readme
@bountyboard/arcade-sdk
Connect your HTML5 game to the Bounty Board arcade: global leaderboards, cross-device cloud saves, player identity, A/B variants, rewarded-ad revenue (90% studio share), site-lock anti-theft, and authoritative multiplayer rooms. Zero dependencies. Every call is optional and safe standalone — off Bounty Board the SDK quietly does nothing, so the same build runs anywhere.
Install
npm install @bountyboard/arcade-sdkimport { BBArcade } from '@bountyboard/arcade-sdk';
BBArcade.lockToHost(); // optional: refuse to run on scrape-and-reupload sites
BBArcade.init(); // handshake with the host (fire-and-forget is fine)
BBArcade.gameLoadingFinished(); // the player can press start
BBArcade.gameplayStart(); // a run began
BBArcade.submitScore(score); // as the score changes (host throttles)
BBArcade.gameOver(finalScore); // once per run — feeds the leaderboardsFull TypeScript definitions are included. Importing the module never installs a
window.BBArcade global and is SSR-safe; if your setup wants the global, use
import '@bountyboard/arcade-sdk/global'.
No build step? Use the script tag instead — same code, same protocol:
<script src="https://www.bountyboard.gg/arcade-sdk/v1.js"></script>with standalone typings at /arcade-sdk.d.ts.
The surface in one look
| Call | What it does |
| ---------------------------------------- | --------------------------------------------------------------------------- |
| lockToHost({ allow? , signed? }) | Block scraped re-hosts of your build (deterrent, not DRM) |
| init(options?) / configure(options?) | Configure modules (rewarded ads), announce to the host |
| ready() / gameLoadingFinished() | Loading finished, player can start |
| gameplayStart() / gameplayStop() | Bracket active play (playtime + feed ranking) |
| submitScore(n, { mode?: 'daily' }) | Live integer score; server enforces plausibility caps |
| gameOver(n, { mode?: 'daily' }) | Final score, once per run |
| save(blob) / load() | 1MB cloud save per player (hosted builds; always catch rejections) |
| getPlayer() | { name, avatarUrl } or null — display identity only, always handle null |
| getVariant(key, variants) | Deterministic A/B split; alphabetical first variant = control |
| prepareRewardedAd(opts) | Prepare an ad; call ready show() directly from the player's click |
| rewardedBreak(opts) | Legacy one-call ad flow (kept for compatibility; avoid in new integrations) |
| xrSessionStart() / xrSessionEnd() | Keep WebXR headset time counting as playtime |
| multiplayer.joinRoom(...) | Authoritative multiplayer rooms — see below |
Rejections from save()/load() are real Errors carrying a typed machine-readable
code: 'unsupported' | 'unauthenticated' | 'too_large' | 'rejected' | 'error'.
Rewarded ads without losing the player's click
Google can report that an ad is ready asynchronously. Prepare at the natural break (for
example, when the defeat panel opens), then call the retained show() as the first SDK action
inside the player's explicit Watch Ad click. Do not await, queue a microtask, or schedule a
timer before show().
const prepared = await BBArcade.prepareRewardedAd({
placement: 'death_revive',
reward: 'extra_life',
onStart: () => pauseGameAndAudio(),
});
if (prepared.status === 'ready') {
watchAdButton.disabled = false;
watchAdButton.onclick = () => {
const result = prepared.show(); // synchronous: keep this first in the click handler
void result.then(final => {
if (final.status === 'viewed') revivePlayer();
else showAdUnavailable(final.error, final.breakStatus);
});
};
} else {
showAdUnavailable(prepared.error, prepared.breakStatus);
}show() is one-shot and always returns the same final-result promise. Grant the reward only
when status === 'viewed'; a raw breakStatus string is diagnostic context, not proof that
Google fired its completion callback. rewardedBreak() remains available so existing games do
not break, but new integrations should use the prepared flow because an asynchronous
beforeReward callback cannot reliably preserve browser user activation.
Multiplayer
import { joinRoom } from '@bountyboard/arcade-sdk/multiplayer';
const room = await joinRoom({
create: true, // or code: 'ABCD'
joinData: { avatar: 'golem', mode: 'ffa' }, // untrusted, validated by your module
});
room.on('snapshot', ({ state }) => render(state)); // server-authoritative state
room.on('end', ({ results }) => showResults(results));
room.on('connection', status => showReconnecting(!status.connected));
if (!room.trySend({ move: dir })) showReconnecting(true); // inputs onlyRooms run on Bounty Board's authoritative room servers: clients send inputs, the server
validates and simulates, and each player receives only the state they're allowed to see
(your hiders stay hidden). Auth is a short-lived signed ticket the SDK obtains from the
host automatically; off Bounty Board joinRoom rejects with code: 'unsupported'.
joinData is limited to a 1 KiB JSON object, is never trusted or broadcast by the
platform, and must not contain secrets. The room runtime supports separate simulation
and snapshot rates (for example 60 Hz simulation / 30 Hz snapshots), a 15-second seat
grace for automatic reconnects, and bounded server frames.
Multiplayer is currently a curated integration: Bounty Board must add and deploy a
server-side game module for your slug before joinRoom() can accept players. The
initial reference module is hide-and-seek; arbitrary games do not become
authoritative multiplayer games merely by importing the client SDK. A production
module must validate join data and every input, clear held controls on disconnect,
serialize only per-viewer-safe state, and return final authoritative standings.
Docs
- Human docs: https://www.bountyboard.gg/arcade/sdk
- Agent-readable reference: https://www.bountyboard.gg/arcade/sdk/llms.txt
- Integration best practices for coding agents:
AGENTS.md - What makes a game perform on Bounty Board:
docs/game-design-playbook.md - Submit your game: https://www.bountyboard.gg/arcade/submit
License
MIT. See LICENSE.
