
Security News
OWASP 2025 Top 10 Adds Software Supply Chain Failures, Ranked Top Community Concern
OWASP’s 2025 Top 10 introduces Software Supply Chain Failures as a new category, reflecting rising concern over dependency and build system risks.
@b3dotfun/leaderboards
Advanced tools
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.
useLatestScore flaghasAttemptLimit and initialAttemptsisReverseSortYou can install our TypeScript SDK using the commands below:
yarn add @b3dotfun/leaderboards
npm install @b3dotfun/leaderboards
chainId: (SupportedChainId)
b3.id, b3Sepolia.id, base.id).viem/chains (e.g., import { b3Sepolia } from "viem/chains"; const chainId = b3Sepolia.id;).walletKey: string (optional)
0x.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)
0x.contractAddress is used to interact with the specific leaderboard contract. This parameter is only used when initializing the Leaderboard class, not LeaderboardFactory.The Leaderboard smart contract includes a useLatestScore flag that determines how scores are updated:
When useLatestScore is true (Latest Score Mode):
When useLatestScore is false (High Score Mode):
The Leaderboard smart contract includes features for managing user attempts:
When hasAttemptLimit is true:
initialAttemptsWhen hasAttemptLimit is false:
initialAttempts value is ignoredThe Leaderboard smart contract includes a isReverseSort flag that determines how scores are sorted:
When isReverseSort is true:
When isReverseSort is false:
When creating a new leaderboard, you can specify these flags:
// Create a leaderboard with custom settings
const leaderboardAddress = await factory.createLeaderboard(
ADMIN_ADDRESS,
slug,
BigInt(startTime),
BigInt(endTime),
title,
false, // isPrivate
true, // useLatestScore - will always use the latest score
true, // hasAttemptLimit - will limit user attempts
3, // initialAttempts - users start with 3 attempts
false // isReverseSort - higher scores are better
);
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";
import { b3Sepolia } from "viem/chains"; // Example: Using B3 Sepolia Testnet
dotenv.config();
const CHAIN_ID = b3Sepolia.id; // Example: Using B3 Sepolia Testnet ID
const ADMIN_ADDRESS = "0x742d35Cc6634C0532925a3b844Bc454e4438f44e";
// Initialize and connect to the Factory contract
function initializeFactory(): LeaderboardFactory | null {
const factory = new LeaderboardFactory(
CHAIN_ID,
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,
useLatestScore: boolean = false,
hasAttemptLimit: boolean = false,
initialAttempts: number = 0,
isReverseSort: 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}`);
console.log(`- Use Latest Score: ${useLatestScore ? "Yes (Latest Score Mode)" : "No (High Score Mode)"}`);
console.log(`- Has Attempt Limit: ${hasAttemptLimit ? "Yes" : "No"}`);
console.log(`- Initial Attempts: ${initialAttempts}`);
console.log(`- Reverse Sort: ${isReverseSort ? "Yes (Lower scores are better)" : "No (Higher scores are better)"}`);
const leaderboardAddress = await factory.createLeaderboard(
ADMIN_ADDRESS,
slug,
BigInt(startTime),
BigInt(endTime),
title,
isPrivate,
useLatestScore,
hasAttemptLimit,
initialAttempts,
isReverseSort
);
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 with custom settings
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
false, // useLatestScore
true, // hasAttemptLimit
3, // initialAttempts
false // isReverseSort
);
await queryLeaderboards(factory, "game-tournament-1");
} catch (error) {
console.error("Error:", error);
}
}
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";
import { b3Sepolia } from "viem/chains"; // Example: Using B3 Sepolia Testnet
dotenv.config();
const CHAIN_ID = b3Sepolia.id; // Example: Using B3 Sepolia Testnet ID
const LEADERBOARD_ADDRESS = "0x5BA4634aBB1D42897E2ba65f0cf9036B091Ea3B7"; // Replace with your actual leaderboard address
const USER_ADDRESS = "0x1234567890123456789012345678901234567890"; // Replace with a user address
function initializeLeaderboard(): Leaderboard | null {
const leaderboard = new Leaderboard(
CHAIN_ID,
process.env.LEADERBOARD_ADMIN_PRIVATE_KEY, // Optional: only needed for write operations
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;
}
// Check if user has attempts remaining (if attempt limit is enabled)
const props = await leaderboard.getLeaderboardProperties();
if (props?.hasAttemptLimit) {
const attempts = await leaderboard.getUserAttempts(user);
if (attempts <= 0) {
console.log(`User ${user} has no attempts remaining`);
return;
}
}
// Update scores
await leaderboard.updateScores([{ user, score: 1000, timestamp }]);
await leaderboard.incrementScores([{ user, score: 500, timestamp }]);
const userScore = await leaderboard.getUserScore(user);
if (userScore) {
console.log(`Current score for ${user}: ${userScore.score}`);
console.log(`Last updated at: ${new Date(userScore.lastUpdated * 1000).toISOString()}`);
} else {
console.log(`No score found for ${user}`);
}
}
// 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("- Use Latest Score:", props.useLatestScore ? "Yes (Latest Score Mode)" : "No (High Score Mode)");
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);
}
}
Create a .env file in your project root with the following variables:
LEADERBOARD_ADMIN_PRIVATE_KEY="your_private_key_here"
# The CHAIN_ID is now passed directly to the constructor, not set via ENV
chainId values for the desired network (e.g., b3.id for B3 Mainnet, b3Sepolia.id for B3 Testnet, base.id for Base Mainnet).addEligibleUsers and removeEligibleUsersisUserEligibleblacklistUsersunblacklistUseraddAdmin and removeAdmingetUserAttempts, increaseUserAttempts, and decreaseUserAttemptsupdateScoresincrementScoresdecrementScoresuseLatestScore flaguseLatestScore = true): Always shows the most recent score
useLatestScore = false): Only keeps the highest score achieved
hasAttemptLimitinitialAttemptsgetUserAttemptsincreaseUserAttempts and decreaseUserAttemptsisReverseSortgetLeaderboardsWithProps(slug)getLatestLeaderboardWithProps(slug)getAllLeaderboardsWithProps()getLeaderboardCountBySlug(slug) and getTotalLeaderboardCount()For more detailed information about the smart contract functionality, please refer to our smart contract documentation.
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.
FAQs
SDK to interact with leaderboards smart contract
The npm package @b3dotfun/leaderboards receives a total of 901 weekly downloads. As such, @b3dotfun/leaderboards popularity was classified as not popular.
We found that @b3dotfun/leaderboards demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 9 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
OWASP’s 2025 Top 10 introduces Software Supply Chain Failures as a new category, reflecting rising concern over dependency and build system risks.

Research
/Security News
Socket researchers discovered nine malicious NuGet packages that use time-delayed payloads to crash applications and corrupt industrial control systems.

Security News
Socket CTO Ahmad Nassri discusses why supply chain attacks now target developer machines and what AI means for the future of enterprise security.