baes-sdk
v1.0.1
Published
SDK for checkpoint saving and loading in games using IPFS
Maintainers
Readme
BAES SDK
A comprehensive gaming toolkit for JavaScript/TypeScript game developers to integrate checkpoint saving and leaderboard attestation into their games using IPFS and EAS (Ethereum Attestation Service).
🎮 What is BAES SDK?
The BAES SDK provides everything you need to add persistent saves and competitive leaderboards to your games:
- 🔄 Checkpoint System: Save and load game progress using IPFS
- 🏆 Smart Leaderboards: Submit scores only when they're new highs
- 🌐 Game Isolation: Each game has its own leaderboard
- ⚡ Gas Efficient: Prevents wasteful transactions
- 🔒 Verifiable: On-chain attestations with EAS
- 📱 Cross-Platform: Works in browsers, Node.js, and React Native
✨ Features
🔄 Checkpoint System
- Save Progress: Store game state on IPFS (Pinata)
- Load Progress: Retrieve latest or specific checkpoint
- Smart Loading: Default to latest, or load by timestamp
- Game Isolation: Each game + user has separate checkpoints
🏆 Smart Leaderboard System
- Smart Submit: Only submits if score > previous best
- Gas Efficient: Prevents wasteful transactions
- Game Isolation: Each game has its own leaderboard
- Global Leaderboards: Cross-game leaderboards
- EAS Integration: Verifiable on-chain attestations
🛠️ Developer Experience
- TypeScript Support: Full type safety and IntelliSense
- Dual Import Styles: Class-based or functional programming
- CLI Tools: Version checking and help commands
- Error Handling: Comprehensive error messages
- Tree Shaking: Only import what you need
How BAES Leaderboard Schema Works
The BAES SDK uses a unified leaderboard schema that all games attest to. This creates a cohesive ecosystem where:
🎯 One Schema, Multiple Games
- BAES creates one schema for all games on EAS Base
- All developers use the same schema UID - no need to create your own
- Games are distinguished by unique
gameIdvalues - Leaderboards are filtered by
gameIdto show only relevant scores
📋 Schema Structure
// BAES Global Leaderboard Schema (used by ALL games)
string gameId, // ← Your unique game identifier
uint256 score // Player's scoreAutomatic Fields (provided by EAS):
playerAddress- From attestation recipienttimestamp- From attestation timeblockNumber- From transaction block
🎮 Example Usage
// Game A submits score
await baesSDK.submitScore({
gameId: 'racing-game-v1', // ← Unique game identifier
score: 1500
}, userAddress);
// Game B submits score
await baesSDK.submitScore({
gameId: 'puzzle-game-v1', // ← Different game identifier
score: 2500
}, userAddress);
// Both use the SAME BAES schema UID, but different gameId values🧠 Smart Score Checking
The SDK automatically checks if a score is worth submitting:
// First time player - always submits
await baesSDK.submitScore({ gameId: 'my-game', score: 100 }, userAddress);
// ✅ Submits (no previous score to beat)
// Better score - submits
await baesSDK.submitScore({ gameId: 'my-game', score: 200 }, userAddress);
// ✅ Submits (200 > 100)
// Worse score - doesn't submit
await baesSDK.submitScore({ gameId: 'my-game', score: 50 }, userAddress);
// ❌ Throws SCORE_TOO_LOW error (50 <= 200)🏆 Benefits
- Simplified Setup: No schema creation required
- Minimal Schema: Just gameId + score (everything else is automatic)
- Smart Score Checking: Only submits if score is higher than user's previous best
- Gas Efficiency: Prevents wasteful transactions for lower scores
- Game Isolation: Each game's leaderboard is separate
- Cross-Game Features: Can build global leaderboards
- Unified Ecosystem: All games use the same attestation system
- Lower Costs: Smaller transaction data = lower gas fees
📋 Table of Contents
- Installation
- Quick Start
- How BAES Leaderboard Schema Works
- Environment Variables
- Checkpoint Functions
- Leaderboard Functions
- Step-by-Step Setup
- Examples
- CLI Commands
- API Reference
- Troubleshooting
📦 Installation
NPM Package
npm install baes-sdkCLI Tool (Optional)
# Install globally for CLI access
npm install -g baes-sdk
# Check version
baes-sdk --version
# Get help
baes-sdk --helpDirect Import
# Use with npx (no global install needed)
npx baes-sdk --versionQuick Start
Style 1: Default Import (Class-based)
import BaesAppSDK from 'baes-sdk';
// Initialize SDK with both checkpoint and leaderboard support
const baesSDK = new BaesAppSDK({
pinataApiKey: process.env.PINATA_API_KEY!,
debug: true,
easConfig: {
schemaUid: process.env.EAS_SCHEMA_UID!,
contractAddress: process.env.EAS_CONTRACT_ADDRESS!,
graphqlUrl: process.env.EAS_GRAPHQL_URL!
}
});
// Save a checkpoint
await baesSDK.saveCheckpoint({
gameId: 'my-awesome-game',
userAddress: '0x123...',
data: { level: 5, score: 1500, inventory: ['sword', 'shield'] }
});
// Submit a score to leaderboard
const transactionCall = await baesSDK.submitScore({
gameId: 'my-awesome-game',
score: 1500
}, '0x123...');
// Get game leaderboard
const leaderboard = await baesSDK.getGameLeaderboard({
gameId: 'my-awesome-game',
limit: 10
});Style 2: Named Imports (Functional)
import {
initialize,
submitScore,
getGameLeaderboard,
saveCheckpoint
} from 'baes-sdk';
// Initialize once
initialize({
pinataApiKey: process.env.PINATA_API_KEY!,
debug: true,
easConfig: {
schemaUid: process.env.EAS_SCHEMA_UID!,
contractAddress: process.env.EAS_CONTRACT_ADDRESS!,
graphqlUrl: process.env.EAS_GRAPHQL_URL!
}
});
// Use functions directly
await saveCheckpoint({
gameId: 'my-awesome-game',
userAddress: '0x123...',
data: { level: 5, score: 1500, inventory: ['sword', 'shield'] }
});
const transactionCall = await submitScore({
gameId: 'my-awesome-game',
score: 1500
}, '0x123...');
const leaderboard = await getGameLeaderboard({
gameId: 'my-awesome-game',
limit: 10
});Environment Variables
Required for Checkpoints
# Pinata IPFS API Key (JWT token)
PINATA_API_KEY=your_pinata_jwt_token_hereRequired for Leaderboards
# EAS Schema UID (BAES global leaderboard schema)
# This is the BAES schema that ALL games attest to
# You do NOT create your own schema - use the BAES schema UID
EAS_SCHEMA_UID=0xef404fc28a42db1d9016dd314e2d1af2badeed4e123c216d225827e5f15a2d2d
# EAS Contract Address (Base network)
EAS_CONTRACT_ADDRESS=0x4200000000000000000000000000000000000021
# EAS GraphQL URL
EAS_GRAPHQL_URL=https://base.easscan.org/graphqlOptional
# Enable debug mode for detailed logging
DEBUG=trueCheckpoint Functions
saveCheckpoint(params)
Save game data to IPFS as a checkpoint.
await baesSDK.saveCheckpoint({
gameId: 'my-game',
userAddress: '0x123...',
data: { level: 5, score: 1500, inventory: ['sword'] }
});loadCheckpoint(params)
Load checkpoint data from IPFS. Smart function that can:
- Load latest checkpoint (default)
- Load specific checkpoint by timestamp
- Load checkpoint from
listCheckpointsresult
// Load latest checkpoint
const latestData = await baesSDK.loadCheckpoint({
gameId: 'my-game',
userAddress: '0x123...'
});
// Load specific checkpoint
const specificData = await baesSDK.loadCheckpoint({
gameId: 'my-game',
userAddress: '0x123...',
timestamp: 1703123456789
});listCheckpoints(params)
List all checkpoints for a game and user.
const checkpoints = await baesSDK.listCheckpoints({
gameId: 'my-game',
userAddress: '0x123...'
});Leaderboard Functions
submitScore(params, userAddress)
Submit a score to the BAES global leaderboard. Smart function that checks if the score is higher than the user's previous best before building the transaction. Only returns a transaction call if the score is actually a new high score.
Parameters:
params.gameId(string, required): Unique identifier for your gameparams.score(number, required): The player's scoreuserAddress(string, required): Player's wallet address
Returns: Transaction call object for wallet execution (only if score is higher than previous best)
Throws: SDKError with code 'SCORE_TOO_LOW' if score is not higher than user's previous best
// Basic score submission
const transactionCall = await baesSDK.submitScore({
gameId: 'my-awesome-game',
score: 1500
}, '0x123...');
// Simple score submission
const transactionCall = await baesSDK.submitScore({
gameId: 'my-racing-game',
score: 8500
}, '0x123...');
// Execute the transaction (example with wagmi)
const { write } = useContractWrite({
...transactionCall,
onSuccess: () => {
console.log('Score submitted successfully!');
}
});
// Or with ethers.js
const tx = await wallet.sendTransaction(transactionCall);
await tx.wait();
// Handle score too low error
try {
const transactionCall = await baesSDK.submitScore({
gameId: 'my-game',
score: 500
}, userAddress);
// Execute transaction...
} catch (error) {
if (error.code === 'SCORE_TOO_LOW') {
console.log('Score not high enough to submit');
}
}getGameLeaderboard(params)
Get leaderboard for a specific game. Fetches attestations from EAS and filters by gameId.
Parameters:
params.gameId(string, required): Your game's unique identifierparams.limit(number, optional): Number of scores to return (default: 10)params.offset(number, optional): Number of scores to skip for pagination
Returns: Array of LeaderboardScore objects
// Get top 10 scores for your game
const leaderboard = await baesSDK.getGameLeaderboard({
gameId: 'my-awesome-game',
limit: 10
});
// Get scores 11-20 for pagination
const nextPage = await baesSDK.getGameLeaderboard({
gameId: 'my-awesome-game',
limit: 10,
offset: 10
});
// Returns:
// [
// {
// gameId: 'my-awesome-game',
// playerAddress: '0x123...',
// score: 1500,
// scoreType: 'points',
// timestamp: 1703123456,
// blockNumber: 0,
// metadata: '',
// attestationUid: '0xabc...',
// transactionHash: '0xdef...'
// }
// ]getGlobalLeaderboard(params)
Get global leaderboard across all games or specific games.
Parameters:
params.limit(number, optional): Number of scores to return (default: 50)params.offset(number, optional): Number of scores to skip for paginationparams.gameIds(string[], optional): Filter by specific game IDs
Returns: Array of LeaderboardScore objects
// Get top 50 scores across all BAES games
const globalLeaderboard = await baesSDK.getGlobalLeaderboard({
limit: 50
});
// Get top 20 scores from specific games only
const racingGamesLeaderboard = await baesSDK.getGlobalLeaderboard({
limit: 20,
gameIds: ['racing-game-1', 'racing-game-2', 'racing-game-3']
});
// Get scores 51-100 for pagination
const nextPage = await baesSDK.getGlobalLeaderboard({
limit: 50,
offset: 50
});Step-by-Step Setup
1. Install the SDK
npm install baes-sdk2. Set up Environment Variables
Create a .env file:
# Required for checkpoints
PINATA_API_KEY=your_pinata_jwt_token_here
# Required for leaderboards
EAS_SCHEMA_UID=0xef404fc28a42db1d9016dd314e2d1af2badeed4e123c216d225827e5f15a2d2d
EAS_CONTRACT_ADDRESS=0x4200000000000000000000000000000000000021
EAS_GRAPHQL_URL=https://base.easscan.org/graphql
# Optional
DEBUG=true3. Get Required API Keys
Pinata API Key (for checkpoints):
- Sign up at Pinata Cloud
- Go to API Keys in your dashboard
- Create a new API key with
pinFileToIPFSandpinListpermissions - Copy your JWT token
EAS Schema UID (for leaderboards):
Important: You do NOT create your own schema. All games use the same BAES global leaderboard schema.
Use the BAES Schema UID provided by the BAES team:
0xdc3cf7f28b4b5255ce732cbf99fe906a5bc13fbd764e2463ba6034b4e1881835This schema structure is used by all games:
string gameId, address playerAddress, uint256 score, string scoreType, uint256 timestamp, uint256 blockNumber, string metadataGames are distinguished by
gameId- each developer uses a unique game identifier
4. Initialize the SDK
import BaesAppSDK from 'baes-sdk';
const baesSDK = new BaesAppSDK({
pinataApiKey: process.env.PINATA_API_KEY!,
debug: process.env.DEBUG === 'true',
easConfig: {
schemaUid: process.env.EAS_SCHEMA_UID!,
contractAddress: process.env.EAS_CONTRACT_ADDRESS!,
graphqlUrl: process.env.EAS_GRAPHQL_URL!
}
});5. Use in Your Game
// Save checkpoint when player reaches a milestone
async function saveGameProgress() {
await baesSDK.saveCheckpoint({
gameId: 'my-game',
userAddress: playerWallet,
data: {
level: currentLevel,
score: currentScore,
inventory: playerInventory,
position: playerPosition
}
});
}
// Submit high score to leaderboard
async function submitHighScore(score: number) {
const transactionCall = await baesSDK.submitScore({
gameId: 'my-game',
score: score,
scoreType: 'points',
metadata: {
level: currentLevel,
time: gameTime,
difficulty: currentDifficulty
}
}, playerWallet);
// Execute transaction with user's wallet
return transactionCall;
}
// Display leaderboard
async function showLeaderboard() {
const leaderboard = await baesSDK.getGameLeaderboard({
gameId: 'my-game',
limit: 10
});
// Display leaderboard in your UI
displayLeaderboard(leaderboard);
}🖥️ CLI Commands
The BAES SDK includes a command-line interface for version checking and help.
Install CLI
# Install globally
npm install -g baes-sdk
# Or use with npx (no global install needed)
npx baes-sdk --versionAvailable Commands
# Check version
baes-sdk --version
baes-sdk -v
# Show help
baes-sdk --help
baes-sdk -hExample Output
$ baes-sdk --version
baes-sdk v1.0.0
$ baes-sdk --help
BAES SDK - Gaming toolkit for checkpoint saving and leaderboard attestation
Usage:
baes-sdk [command] [options]
Commands:
version, -v, --version Show version
help, -h, --help Show this help
Examples:
baes-sdk --version
baes-sdk -v
baes-sdk --help
For more information, visit: https://github.com/baesdotso/baes-sdk🎮 Examples
Complete Game Integration
import BaesAppSDK from 'baes-sdk';
class MyGame {
private baesSDK: BaesAppSDK;
private playerWallet: string;
constructor() {
this.baesSDK = new BaesAppSDK({
pinataApiKey: process.env.PINATA_API_KEY!,
easConfig: {
schemaUid: process.env.EAS_SCHEMA_UID!,
contractAddress: process.env.EAS_CONTRACT_ADDRESS!,
graphqlUrl: process.env.EAS_GRAPHQL_URL!
}
});
}
async startGame(walletAddress: string) {
this.playerWallet = walletAddress;
// Try to load previous progress
const savedData = await this.baesSDK.loadCheckpoint({
gameId: 'my-game',
userAddress: walletAddress
});
if (savedData) {
this.loadGameState(savedData);
}
}
async saveProgress() {
await this.baesSDK.saveCheckpoint({
gameId: 'my-game',
userAddress: this.playerWallet,
data: this.getGameState()
});
}
async gameOver(finalScore: number) {
// Save final progress
await this.saveProgress();
// Submit score to leaderboard
const transactionCall = await this.baesSDK.submitScore({
gameId: 'my-game',
score: finalScore
}, this.playerWallet);
return transactionCall;
}
async showLeaderboard() {
const leaderboard = await this.baesSDK.getGameLeaderboard({
gameId: 'my-game',
limit: 10
});
return leaderboard;
}
}React/Next.js Integration
import { useState, useEffect } from 'react';
import BaesAppSDK from 'baes-sdk';
export function useGameLeaderboard(gameId: string) {
const [leaderboard, setLeaderboard] = useState([]);
const [loading, setLoading] = useState(false);
const baesSDK = new BaesAppSDK({
pinataApiKey: process.env.NEXT_PUBLIC_PINATA_API_KEY!,
easConfig: {
schemaUid: process.env.NEXT_PUBLIC_EAS_SCHEMA_UID!,
contractAddress: process.env.NEXT_PUBLIC_EAS_CONTRACT_ADDRESS!,
graphqlUrl: process.env.NEXT_PUBLIC_EAS_GRAPHQL_URL!
}
});
const loadLeaderboard = async () => {
setLoading(true);
try {
const data = await baesSDK.getGameLeaderboard({ gameId, limit: 10 });
setLeaderboard(data);
} catch (error) {
console.error('Failed to load leaderboard:', error);
} finally {
setLoading(false);
}
};
const submitScore = async (score: number, userAddress: string) => {
try {
const transactionCall = await baesSDK.submitScore({
gameId,
score
}, userAddress);
return transactionCall;
} catch (error) {
console.error('Failed to submit score:', error);
throw error;
}
};
useEffect(() => {
loadLeaderboard();
}, [gameId]);
return { leaderboard, loading, submitScore, refresh: loadLeaderboard };
}Vanilla JavaScript Integration
// For vanilla JS games (like bario-drift)
import BaesAppSDK from 'baes-sdk';
const baesSDK = new BaesAppSDK({
pinataApiKey: 'your-pinata-jwt-token',
easConfig: {
schemaUid: 'your-eas-schema-uid',
contractAddress: '0x4200000000000000000000000000000000000021',
graphqlUrl: 'https://base.easscan.org/graphql'
}
});
class Game {
constructor() {
this.sdk = baesSDK;
}
async submitHighScore(score, userAddress) {
try {
const transactionCall = await this.sdk.submitScore({
gameId: 'my-game',
score: score
}, userAddress);
// Execute with user's wallet
return transactionCall;
} catch (error) {
console.error('Failed to submit score:', error);
}
}
async showLeaderboard() {
try {
const leaderboard = await this.sdk.getGameLeaderboard({
gameId: 'my-game',
limit: 10
});
// Display in your game UI
this.displayLeaderboard(leaderboard);
} catch (error) {
console.error('Failed to load leaderboard:', error);
}
}
}API Reference
BaesAppSDK Constructor
new BaesAppSDK(config: {
pinataApiKey: string;
debug?: boolean;
easConfig?: EASConfig;
})Types
interface SaveCheckpointParams {
gameId: string;
userAddress: string;
data: Record<string, any>;
}
interface LoadCheckpointParams {
gameId: string;
userAddress: string;
timestamp?: number;
checkpoint?: Checkpoint;
}
interface SubmitScoreParams {
gameId: string;
score: number;
}
interface GetLeaderboardParams {
gameId: string;
limit?: number;
offset?: number;
}
interface GetGlobalLeaderboardParams {
limit?: number;
offset?: number;
gameIds?: string[];
}
interface LeaderboardScore {
gameId: string;
playerAddress: string;
score: number;
scoreType: string;
timestamp: number;
blockNumber: number;
metadata: string;
attestationUid: string;
transactionHash: string;
}
interface EASConfig {
schemaUid: string;
contractAddress: string;
graphqlUrl: string;
}🚨 Troubleshooting
Common Issues
"Pinata API key is required"
- Ensure
PINATA_API_KEYis set in your environment variables - Verify the API key is valid and has upload permissions
- Ensure
"EAS configuration not provided"
- Make sure
easConfigis passed to the SDK constructor - Verify all EAS environment variables are set
- Make sure
"Failed to save checkpoint"
- Check your internet connection
- Verify Pinata API key is valid
- Check Pinata service status
"GraphQL request failed"
- Verify EAS GraphQL URL is correct
- Check if EAS service is available
- Ensure schema UID is valid
"Score too low"
- This is expected behavior - the SDK prevents submitting lower scores
- Only scores higher than the user's previous best will be submitted
"Transaction failed"
- Ensure user has enough ETH for gas fees on Base network
- Verify the EAS contract address is correct
- Check that the schema UID matches your schema
Debug Mode
Enable debug mode to see detailed logs:
const baesSDK = new BaesAppSDK({
pinataApiKey: process.env.PINATA_API_KEY!,
debug: true, // Enable debug logging
easConfig: { ... }
});Getting Help
- Check the Pinata documentation for IPFS issues
- Visit EAS documentation for attestation issues
- Open an issue on GitHub
🎯 Summary
The BAES SDK is your complete gaming toolkit for adding persistent saves and competitive leaderboards to any JavaScript/TypeScript game.
What You Get:
- ✅ Checkpoint System: Save/load game progress on IPFS
- ✅ Smart Leaderboards: Only submit new high scores
- ✅ Game Isolation: Each game has its own leaderboard
- ✅ Gas Efficiency: Prevents wasteful transactions
- ✅ TypeScript Support: Full type safety
- ✅ Dual Import Styles: Class-based or functional
- ✅ CLI Tools: Version checking and help
- ✅ Cross-Platform: Works everywhere
Quick Start:
npm install baes-sdkimport { initialize, submitScore, saveCheckpoint } from 'baes-sdk';
initialize({
pinataApiKey: 'your-key',
easConfig: { /* EAS config */ }
});
await saveCheckpoint({ gameId: 'my-game', userAddress: '0x...', data: {...} });
await submitScore({ gameId: 'my-game', score: 100 }, '0x...');Ready to build the next generation of web3 games? 🚀
License
MIT License - see LICENSE file for details.
