@b3dotfun/leaderboards
TypeScript icon, indicating that this package has built-in type declarations

1.0.15 • Public • Published

Leaderboard Smart Contract - TypeScript SDK

NPC Labs presents the TypeScript SDK for our On-Chain Leaderboard Smart Contract, a key contributor to B3 - the gaming ecosystem. This SDK enables game developers to seamlessly incorporate secure on-chain leaderboards for tracking and displaying user scores on the blockchain.

Features

  • Create and manage multiple leaderboards with unique slugs
  • Public and private leaderboards
  • User eligibility management for private leaderboards
  • User blacklisting
  • Flexible score management (increment, decrement, update)
  • Admin management system
  • Real-time ranking and score tracking
  • Time-based leaderboard lifecycle management

Installation

You can install our TypeScript SDK using the commands below:

yarn add @b3dotfun/leaderboards
npm install @b3dotfun/leaderboards

Getting Started

LeaderboardSDK Props

  • ENV: ("testnet" | "mainnet")

    • This parameter specifies the environment in which the SDK is operating. Use "testnet" for test networks and "mainnet" for production networks.
  • walletKey: string (optional)

    • This is an optional parameter that represents the wallet key used for transactions. If provided, it should be a hexadecimal string starting with 0x.
    • The walletKey is required for write operations that modify the state of the blockchain (e.g., deploying contracts, adding/removing admins, setting scores). For read operations (e.g., retrieving leaderboard data, user scores), the walletKey is not required.
  • contractAddress: string (optional)

    • This optional parameter specifies the address of the leaderboard contract. It should be a hexadecimal string starting with 0x.
    • The contractAddress is used to interact with the specific leaderboard contract.

Examples

1. Creating and Managing Leaderboards

Here's a complete example of creating and managing leaderboards using the Factory contract:

import * as dotenv from "dotenv";
import type { Address } from "viem";
import { LeaderboardFactory } from "@b3dotfun/leaderboards";

dotenv.config();

const ENVIRONMENT = "testnet";
const ADMIN_ADDRESS = "0x742d35Cc6634C0532925a3b844Bc454e4438f44e";

// Initialize and connect to the Factory contract
function initializeFactory(): LeaderboardFactory | null {
  const factory = new LeaderboardFactory(
    ENVIRONMENT,
    process.env.LEADERBOARD_ADMIN_PRIVATE_KEY
  );

  const isConnected = factory.connect();
  if (!isConnected) {
    console.error("Failed to connect to the Factory contract.");
    return null;
  }

  console.log("Connected to the Factory contract");
  return factory;
}

// Create a new leaderboard with specified parameters
async function createNewLeaderboard(
  factory: LeaderboardFactory,
  slug: string,
  startTime: number,
  endTime: number,
  title: string,
  isPrivate: boolean = false
): Promise<Address> {
  console.log(`Creating leaderboard "${slug}" with following parameters:`);
  console.log(`- Title: ${title}`);
  console.log(`- Start time: ${new Date(startTime * 1000).toISOString()}`);
  console.log(`- End time: ${new Date(endTime * 1000).toISOString()}`);
  console.log(`- Private: ${isPrivate}`);

  const leaderboardAddress = await factory.createLeaderboard(
    ADMIN_ADDRESS,
    slug,
    BigInt(startTime),
    BigInt(endTime),
    title,
    isPrivate
  );

  console.log("Created new leaderboard at address:", leaderboardAddress);
  return leaderboardAddress;
}

// Query leaderboard information
async function queryLeaderboards(factory: LeaderboardFactory, slug: string) {
  const leaderboardsWithProps = await factory.getLeaderboardsWithProps(slug);
  
  if (leaderboardsWithProps.length > 0) {
    console.log(`\nAll leaderboards for "${slug}":`);
    leaderboardsWithProps.forEach((leaderboard, index) => {
      console.log(`\nLeaderboard #${index + 1}:`);
      console.log("- Address:", leaderboard.address);
      console.log("- Title:", leaderboard.properties.title);
      console.log("- Start time:", new Date(leaderboard.properties.startTime * 1000).toISOString());
      console.log("- End time:", new Date(leaderboard.properties.endTime * 1000).toISOString());
      console.log("- Concluded:", leaderboard.properties.hasConcluded ? "Yes" : "No");
      console.log("- Private:", leaderboard.properties.isPrivate ? "Yes" : "No");
    });
  }

  const slugCount = await factory.getLeaderboardCountBySlug(slug);
  console.log(`\nTotal number of leaderboards for "${slug}":`, slugCount);
}

// Example usage
async function main() {
  try {
    const factory = initializeFactory();
    if (!factory) return;

    // Create a new 24-hour leaderboard
    const startTime = Math.floor(Date.now() / 1000);
    const endTime = startTime + 24 * 3600;

    const leaderboardAddress = await createNewLeaderboard(
      factory,
      "game-tournament-1",
      startTime,
      endTime,
      "Game Tournament #1",
      false // isPrivate
    );

    await queryLeaderboards(factory, "game-tournament-1");
    
    const totalCount = await factory.getTotalLeaderboardCount();
    console.log("Total leaderboards across all slugs:", totalCount);
  } catch (error) {
    console.error("Error:", error);
  }
}

2. Interacting with Individual Leaderboards

Here's an example of interacting with a specific leaderboard:

import * as dotenv from "dotenv";
import type { Address } from "viem";
import { Leaderboard } from "@b3dotfun/leaderboards";

dotenv.config();

const ENVIRONMENT = "testnet";
const LEADERBOARD_ADDRESS = "0x5BA4634aBB1D42897E2ba65f0cf9036B091Ea3B7";
const USER_ADDRESS = "0x1234567890123456789012345678901234567890";

function initializeLeaderboard(): Leaderboard | null {
  const leaderboard = new Leaderboard(
    ENVIRONMENT,
    process.env.LEADERBOARD_ADMIN_PRIVATE_KEY,
    LEADERBOARD_ADDRESS
  );

  const isConnected = leaderboard.connect();
  if (!isConnected) {
    console.error("Failed to connect to the Leaderboard contract.");
    return null;
  }

  console.log("Connected to the Leaderboard contract");
  return leaderboard;
}

// Manage user scores
async function manageUserScores(leaderboard: Leaderboard, user: Address) {
  const timestamp = Math.floor(Date.now() / 1000);

  // Check if user is eligible (for private leaderboards)
  const isPrivate = await leaderboard.isPrivate();
  if (isPrivate) {
    const isEligible = await leaderboard.isUserEligible(user);
    if (!isEligible) {
      console.log(`User ${user} is not eligible for this private leaderboard`);
      return;
    }
  }

  // Check if user is blacklisted
  const isBlacklisted = await leaderboard.isUserBlacklisted(user);
  if (isBlacklisted) {
    console.log(`User ${user} is blacklisted`);
    return;
  }

  // Update scores
  await leaderboard.updateScores([{ user, score: 1000, timestamp }]);
  await leaderboard.incrementScores([{ user, score: 500, timestamp }]);
  
  const userScore = await leaderboard.getUserScore(user);
  console.log(`Current score for ${user}: ${userScore}`);
}

// Manage private leaderboard eligibility
async function manageEligibleUsers(leaderboard: Leaderboard, users: Address[]) {
  const isPrivate = await leaderboard.isPrivate();
  if (!isPrivate) {
    console.log("This is not a private leaderboard");
    return;
  }

  await leaderboard.addEligibleUsers(users);
  const eligibleUsers = await leaderboard.getEligibleUsers();
  console.log("Current eligible users:", eligibleUsers);
}

async function displayLeaderboardInfo(leaderboard: Leaderboard) {
  const props = await leaderboard.getLeaderboardProperties();
  const isActive = await leaderboard.isLeaderboardActive();
  const userCount = await leaderboard.getLeaderboardUserCount();

  if (props) {
    console.log("\nLeaderboard Information:");
    console.log("- Title:", props.title);
    console.log("- Slug:", props.slug);
    console.log("- Start time:", new Date(props.startTime * 1000).toISOString());
    console.log("- End time:", new Date(props.endTime * 1000).toISOString());
    console.log("- Status:", isActive ? "Active" : "Inactive");
    console.log("- Concluded:", props.hasConcluded ? "Yes" : "No");
    console.log("- Private:", props.isPrivate ? "Yes" : "No");
    console.log("- Total Users:", userCount);
  }
}

async function main() {
  try {
    const leaderboard = initializeLeaderboard();
    if (!leaderboard) return;

    // Display leaderboard information
    await displayLeaderboardInfo(leaderboard);

    // Manage user scores
    await manageUserScores(leaderboard, USER_ADDRESS);

    // Manage eligible users for private leaderboards
    await manageEligibleUsers(leaderboard, [USER_ADDRESS]);
  } catch (error) {
    console.error("Error:", error);
  }
}

Environment Setup

Create a .env file in your project root with the following variables:

LEADERBOARD_ADMIN_PRIVATE_KEY="your_private_key_here"
ENV="testnet"  # or "mainnet" for production

Important Notes

  1. Always ensure your private keys are kept secure and never committed to version control
  2. Use appropriate environment settings:
    • Use "testnet" for development and testing
    • Use "mainnet" for production deployments
  3. For private leaderboards:
    • Users must be added to the eligible users list before they can participate
    • Only admins can manage user eligibility
  4. Blacklisted users cannot participate in any leaderboard activities
  5. The examples above demonstrate basic usage. For production deployments, implement proper error handling and security measures

Features in Detail

Private Leaderboards

  • Create private leaderboards with restricted access
  • Manage eligible users through addEligibleUsers and removeEligibleUsers
  • Check user eligibility with isUserEligible

User Management

  • Blacklist users with blacklistUsers
  • Unblacklist users with unblacklistUser
  • Add/remove admins with addAdmin and removeAdmin

Score Management

  • Update scores with updateScores
  • Increment scores with incrementScores
  • Decrement scores with decrementScores
  • Get user scores and rankings
  • View top scores and full leaderboard

Leaderboard Lifecycle

  • Set start and end times
  • Check leaderboard status (active/ended)
  • Conclude leaderboards
  • Update leaderboard parameters

Leaderboard Queries

  • Get leaderboards by slug with getLeaderboardsWithProps(slug)
  • Get latest leaderboard for a slug with getLatestLeaderboardWithProps(slug)
  • Get all leaderboards across all slugs with getAllLeaderboardsWithProps()
  • Get leaderboard counts with getLeaderboardCountBySlug(slug) and getTotalLeaderboardCount()

For more detailed information about the smart contract functionality, please refer to our smart contract documentation.

Using JSON ABIs Directly

The SDK also exports the raw JSON ABIs for direct use with libraries like viem, ethers.js, or web3.js. This is useful if you want to interact with the contracts directly without using the SDK wrapper classes.

import { LeaderboardFactoryJson, LeaderboardJson } from '@b3dotfun/leaderboards';
import { createPublicClient, http } from 'viem';
import { mainnet } from 'viem/chains';
import { B3LeaderboardFactoryContractAddress, B3MainnetRpcUrl } from '@b3dotfun/leaderboards';

// Create a public client
const client = createPublicClient({
  chain: mainnet,
  transport: http(B3MainnetRpcUrl),
});

// Use the LeaderboardFactory ABI to interact with the contract
const totalLeaderboards = await client.readContract({
  address: B3LeaderboardFactoryContractAddress,
  abi: LeaderboardFactoryJson,
  functionName: 'getTotalLeaderboardCount',
});

console.log(`Total leaderboards: ${totalLeaderboards}`);

// Get all leaderboards
const leaderboards = await client.readContract({
  address: B3LeaderboardFactoryContractAddress,
  abi: LeaderboardFactoryJson,
  functionName: 'getAllLeaderboards',
});

// If there are any leaderboards, get details of the first one
if (Array.isArray(leaderboards) && leaderboards.length > 0) {
  const firstLeaderboardAddress = leaderboards[0];
  
  // Use the Leaderboard ABI to interact with the leaderboard contract
  const leaderboardProps = await client.readContract({
    address: firstLeaderboardAddress,
    abi: LeaderboardJson,
    functionName: 'getLeaderboardProps',
  });

  console.log('Leaderboard properties:', leaderboardProps);
}

This approach gives you more flexibility when integrating with existing applications or when you need more control over the contract interactions.

Dependents (0)

Package Sidebar

Install

npm i @b3dotfun/leaderboards

Weekly Downloads

2,029

Version

1.0.15

License

Copyright © 2025 NPC Labs, Inc. All rights reserved.

Unpacked Size

310 kB

Total Files

64

Last publish

Collaborators

  • npclabs
  • gionpclabs
  • alex-kaffetzakis
  • mathenls
  • tranhoangdev
  • b3_david
  • anqt-dev