@cashable/game-sdk
v0.8.5
Published
Cashable Unified Game SDK for embedded (WebView) and standalone (Capacitor) games
Readme
@ververonmedia/cashable-game-sdk
Unified TypeScript SDK for Cashable games. Provides a single API surface for both embedded (WebView inside the Cashable app) and standalone (Capacitor-wrapped APK) game modes.
Install
npm install @ververonmedia/cashable-game-sdkQuick Start
The npm package ships only the CDN loader — a ~1KB bootstrap that loads the full SDK from CDN at runtime. This keeps the SDK out of your bundle and enables over-the-air updates. See the Game Integration Guide for a complete walkthrough.
import { loadCashableSDK } from '@ververonmedia/cashable-game-sdk/loader';
import type { CashableSDKConfig } from '@ververonmedia/cashable-game-sdk/loader';
// Load SDK from CDN
const SDK = await loadCashableSDK({ apiBaseUrl: 'https://api.cashableapp.com' });
const cashable = new SDK({
gameId: 'smash-monsters',
debug: true,
});
await cashable.init();
// Show a rewarded video ad
const success = await cashable.ads.showRewardedVideo();
if (success) {
await cashable.rewards.awardCoins();
}
// Start play-time tracking
cashable.analytics.startAutoTracking();
// Clean up when done
cashable.dispose();Babylon.js Integration
The Babylon.js plugin is auto-detected at runtime and binds your scene for automatic freeze/resume:
// After SDK is loaded and initialized via the loader:
cashable.babylon.bindScene(scene, engine);
// The SDK will automatically stop/start the render loop
// when the host app freezes/resumes the gameModes
The SDK operates in two modes, auto-detected at runtime:
| | Embedded | Standalone |
|---|---|---|
| Context | WebView inside Cashable app | Capacitor-wrapped APK |
| Detection | window.ReactNativeWebView exists | window.Capacitor exists |
| Transport | postMessage bridge | HTTP REST calls |
| Auth | Host manages identity | App Set ID via Capacitor plugin |
| Ads | Host shows ads | Native AppLovin MAX plugin |
You can override detection by passing mode explicitly:
const cashable = new CashableSDK({
gameId: 'my-game',
mode: 'standalone',
apiBaseUrl: 'https://api.cashable.app',
appLovin: {
sdkKey: 'YOUR_SDK_KEY',
rewardedAdUnitId: 'YOUR_REWARDED_AD_UNIT_ID',
interstitialAdUnitId: 'YOUR_INTERSTITIAL_AD_UNIT_ID',
bannerAdUnitId: 'YOUR_BANNER_AD_UNIT_ID',
},
ui: {
onClose: () => Capacitor.Plugins.App.exitApp(),
},
});Configuration
interface CashableSDKConfig {
/** Unique game identifier */
gameId: string;
/** Force a specific mode instead of auto-detecting */
mode?: 'embedded' | 'standalone';
/** Backend API base URL (standalone mode only) */
apiBaseUrl?: string;
/** Request timeout in milliseconds (default: 10000) */
requestTimeoutMs?: number;
/** Enable debug logging (default: false) */
debug?: boolean;
/** AppLovin MAX config (required for standalone mode ads) */
appLovin?: {
/** SDK key from AppLovin dashboard (Account > General > Keys) */
sdkKey: string;
rewardedAdUnitId: string;
interstitialAdUnitId: string;
/** Optional - omit to disable banner ads */
bannerAdUnitId?: string;
};
/** UI config for standalone mode */
ui?: {
onClose?: () => void; // Close button callback
onMenuOpen?: () => void; // Menu button callback (optional)
showCashableButton?: boolean; // Show floating panel button (default: false)
cashableButton?: {
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
};
};
}Note: The
appLovinanduiconfigs are only used in standalone mode. In embedded mode, the host Cashable app handles ads and UI.
Modules
The SDK exposes its functionality through focused modules:
| Module | Accessor | Description |
|---|---|---|
| Session | cashable.session | User identity, balance |
| Rewards | cashable.rewards | Coin awarding |
| Ads | cashable.ads | Rewarded video, interstitial & banner ads |
| Analytics | cashable.analytics | Play-time tracking, theme color |
| Babylon | cashable.babylon | Babylon.js render loop freeze/resume |
| Feature Flags | cashable.featureFlags | Remote config, feature gates, event logging (Statsig) |
| UI | cashable.ui | Floating panel, sponsored modal, offers, tier celebrations (standalone only) |
Authentication
Embedded mode: The host Cashable app handles all authentication. The SDK is always considered authenticated (cashable.isAuthenticated === true).
Standalone mode: The SDK reads the device's App Set ID via a Capacitor plugin and sends it as X-App-Set-Id header. The backend maps this to a known user if the user has previously opened the game in the Cashable app. If no mapping exists, cashable.isAuthenticated is false and coin-related features will fail gracefully.
if (cashable.isAuthenticated) {
const { coins } = await cashable.session.getBalance();
console.log(`User has ${coins} coins`);
} else {
console.log('User not linked - coin features unavailable');
}Lifecycle Events
// Called when the host app puts the game in the background
cashable.onFreeze(() => {
// Pause game logic, audio, etc.
});
// Called when the host app brings the game back
cashable.onResume(() => {
// Resume game logic, audio, etc.
});If you've bound a Babylon.js scene, the render loop is automatically stopped/started for you.
Error Handling
SDK errors include a code property for programmatic handling:
| Code | Meaning |
|---|---|
| TIMEOUT | Request timed out |
| TRANSPORT_ERROR | Communication layer failure |
| AD_NOT_AVAILABLE | No ad fill available |
| NETWORK_ERROR | HTTP request failed |
| USER_NOT_LINKED | Standalone user has no Cashable account mapping |
try {
await cashable.rewards.awardCoins();
} catch (err) {
if (err.code === 'USER_NOT_LINKED') {
// User hasn't opened this game in the Cashable app yet
}
}Cleanup
Always call dispose() when the SDK is no longer needed:
cashable.dispose();This stops analytics tracking, removes event listeners, aborts pending requests, and cleans up the Babylon.js plugin.
Top-Level Convenience APIs
CashableSDK.getWidgetHeight(): number (static)
Computes the widget top-bar height from window.innerWidth alone — no DOM or init() required. Call it at script load time to offset your game canvas immediately:
const height = CashableSDK.getWidgetHeight();
game.setTopOffset(height);Also available as a standalone function from both entry points:
// CDN / main entry
import { getWidgetHeight } from '@ververonmedia/cashable-game-sdk';
// Loader entry (works before CDN script loads)
import { getWidgetHeight } from '@ververonmedia/cashable-game-sdk/loader';cashable.onWidgetReady(handler): () => void
Fires after init() mounts the UI with the exact DOM-measured height. Use it to refine the initial estimate:
sdk.onWidgetReady(({ height }) => {
game.setTopOffset(height); // update with real value
});cashable.getTopBarHeight(): number
Returns the pixel height of the SDK's top bar. Before init() completes, falls back to CashableSDK.getWidgetHeight() (computed estimate). After init(), returns the real DOM-measured value.
const offset = cashable.getTopBarHeight();
gameCanvas.style.marginTop = `${offset}px`;cashable.adConfig: Readonly<AdConfig>
Returns the current ad configuration with Statsig remote overrides merged on top of defaults. Shorthand for cashable.featureFlags?.adConfig ?? DEFAULT_AD_CONFIG.
const cooldown = cashable.adConfig.interstitialCooldownMs;See AdConfig for all available fields.
Standalone UI
In standalone mode, when the user is authenticated, the SDK renders a floating CashableButton that opens a panel with rewards info, cashout flow, and games. It also provides sponsored video modals for interstitials and bonus/booster offer modals.
See the full UI Module documentation for all methods, components, and behavior details.
CashableButton Panel
Shown automatically after init() when showCashableButton: true. The panel has three content areas:
- Rewards tab (main screen) — coins pill, tier card with progress bar, "Convert coins to real money" CTA, partners section
- Cashout screen (sub-screen) — countdown timer, earnings display, cashout button, partners. Accessed via the CTA button; back button returns to main screen
- Games tab — lists available games
The panel trigger row displays the current coin balance with earn animations, inline countdown timer, and offer countdown badges.
Sponsored Interstitial
// Show a 3-second countdown modal, then the interstitial ad
const success = await cashable.ui?.showSponsoredInterstitial();Shows an animated countdown, then the ad, then a success/failure state. Coins are automatically awarded on success with the current boost multiplier applied.
Bonus & Booster Offers
The SDK periodically shows bonus coin offers and 2x boost offers via the OfferScheduler. When an offer triggers, a 20-second countdown badge appears on the panel trigger. If the user taps within the countdown, the corresponding modal opens (rewarded ad required to claim).
Controlling the UI
cashable.ui?.updateCoins(newBalance); // Manually update displayed coins
cashable.ui?.updateThemeColor('#1a1a2e'); // Change panel background
await cashable.ui?.refreshWallet(); // Re-fetch wallet data from backend
cashable.uiisnullin embedded mode or when the user is not authenticated.
Testing on Device
The SDK ships with two Android test targets for on-device testing. Both reference the SDK as a local file: dependency, so they always pick up the latest build.
| Target | Directory | App ID | Description |
|--------|-----------|--------|-------------|
| test-app | debug/ | com.ververon.monster | Minimal SDK debug harness |
| test-game | debug/test-game/block-blast/Block-Blast/ | com.ververon.monster | Block Blast game with full SDK integration |
Prerequisites
- Android device connected via USB with ADB debugging enabled
- Java 21 available (
JAVA_HOME=$(/usr/libexec/java_home -v 21))
Quick Start
# 1. Build the SDK
cd cashable-game-sdk && npm run build
# 2a. Deploy test-app (convenience script)
cd debug
JAVA_HOME=$(/usr/libexec/java_home -v 21) npm run run:android
# 2b. Deploy test-game (Block Blast)
cd debug/test-game/block-blast/Block-Blast
npm run build
npx cap sync android
cd android && JAVA_HOME=$(/usr/libexec/java_home -v 21) ./gradlew assembleDebug && cd ..
adb install -r android/app/build/outputs/apk/debug/app-debug.apkAPK Location
After building, the debug APK is at:
- test-app:
debug/android/app/build/outputs/apk/debug/app-debug.apk - test-game:
debug/test-game/block-blast/Block-Blast/android/app/build/outputs/apk/debug/app-debug.apk
Claude Code Skill
The sdk-android-test skill automates the full build-and-deploy flow. Invoke it with /sdk-android-test in Claude Code to build the SDK and deploy to a connected device.
Versioning
This package has two independent version tracks:
| Version | File | What it tracks | When to bump |
|---------|------|---------------|--------------|
| npm (loader + types) | package.json "version" | Loader code, type declarations, native plugins | Loader logic or exported types change |
| CDN (SDK runtime) | cdn-version.json "version" | Full SDK IIFE bundle served from CDN | SDK runtime code changes |
The npm package ships only the loader (~1KB bootstrap) and type declarations. The full SDK runtime is built separately and deployed to CDN (cashableapp.com/sdk/v<CDN_VERSION>/cashable-sdk.js). At runtime, the loader fetches the SDK version from the backend and loads the IIFE bundle from CDN.
A CDN deploy does not require an npm publish, and vice versa.
