npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

baes-sdk

v1.0.1

Published

SDK for checkpoint saving and loading in games using IPFS

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 gameId values
  • Leaderboards are filtered by gameId to show only relevant scores

📋 Schema Structure

// BAES Global Leaderboard Schema (used by ALL games)
string gameId,        // ← Your unique game identifier
uint256 score         // Player's score

Automatic Fields (provided by EAS):

  • playerAddress - From attestation recipient
  • timestamp - From attestation time
  • blockNumber - 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

  1. Installation
  2. Quick Start
  3. How BAES Leaderboard Schema Works
  4. Environment Variables
  5. Checkpoint Functions
  6. Leaderboard Functions
  7. Step-by-Step Setup
  8. Examples
  9. CLI Commands
  10. API Reference
  11. Troubleshooting

📦 Installation

NPM Package

npm install baes-sdk

CLI Tool (Optional)

# Install globally for CLI access
npm install -g baes-sdk

# Check version
baes-sdk --version

# Get help
baes-sdk --help

Direct Import

# Use with npx (no global install needed)
npx baes-sdk --version

Quick 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_here

Required 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/graphql

Optional

# Enable debug mode for detailed logging
DEBUG=true

Checkpoint 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 listCheckpoints result
// 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 game
  • params.score (number, required): The player's score
  • userAddress (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 identifier
  • params.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 pagination
  • params.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-sdk

2. 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=true

3. Get Required API Keys

Pinata API Key (for checkpoints):

  1. Sign up at Pinata Cloud
  2. Go to API Keys in your dashboard
  3. Create a new API key with pinFileToIPFS and pinList permissions
  4. 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.

  1. Use the BAES Schema UID provided by the BAES team:

    0xdc3cf7f28b4b5255ce732cbf99fe906a5bc13fbd764e2463ba6034b4e1881835
  2. This schema structure is used by all games:

    string gameId, address playerAddress, uint256 score, string scoreType, uint256 timestamp, uint256 blockNumber, string metadata
  3. Games 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 --version

Available Commands

# Check version
baes-sdk --version
baes-sdk -v

# Show help
baes-sdk --help
baes-sdk -h

Example 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

  1. "Pinata API key is required"

    • Ensure PINATA_API_KEY is set in your environment variables
    • Verify the API key is valid and has upload permissions
  2. "EAS configuration not provided"

    • Make sure easConfig is passed to the SDK constructor
    • Verify all EAS environment variables are set
  3. "Failed to save checkpoint"

    • Check your internet connection
    • Verify Pinata API key is valid
    • Check Pinata service status
  4. "GraphQL request failed"

    • Verify EAS GraphQL URL is correct
    • Check if EAS service is available
    • Ensure schema UID is valid
  5. "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
  6. "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

🎯 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-sdk
import { 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.