Skip to content

Leaderboard

Background

The trio of leaderboards offered through Sediment enables quick scaffolding of an initial on-chain feature. A leaderboard is a great feature for games looking to begin coming into Web3; but don’t want to go all-in on “Web3”.

This is because the leaderboard is designed to be set by either an authoritative server or another smart contract.

This guarantees that leaderboards cannot be manipulated maliciously by users directly.

There are currently three types of leaderboards:

  1. Leaderboard - Uses address as the key for the ranking. Great for games that have user wallets whether non-custodial or otherwise.

  2. BytesLeaderboard - Uses bytes32 as the key for the ranking. Great for games that have identifiers that they want to hash before putting on-chain.

  3. String Leaderboard - Uses string as the key for the ranking. Great for anonymous user names that are pre-validated by a server.

Installation

Terminal window
npm add @dirtroad/sediment

Using Leaderboard

Setup Contract

LeaderboardExample.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@dirtroad/sediment/contracts/leaderboard/Leaderboard.sol";
contract LeaderboardExample is Leaderboard(10) {
constructor(address[] memory _signers) {
for (uint256 i = 0; i < _signers.length; i++) {
_grantRole(SERVER_ROLE, _signers[i]);
}
}
}

Contract Interaction

backend.js
/*
Commented out for build purposes
import dotenv from "dotenv";
import express from "express";
import { Wallet } from "ethers";
import ABI from "./abi.json";
*/
dotenv.config();
const signerKey = process.env.PRIVATE_KEY;
async function main() {
const app = express();
app.use(express.json());
app.use(express.urlencoded());
const provider = new JsonRpcProvider("https://testnet.skalenodes.com/v1/giant-half-dual-testent");
const signer = new Wallet(signerKey, provider);
let nonce = await provider.getTransactionCount(signer.address);
const leaderboard = new Contract("0x...", LeaderboardABI, signer);
app.post("/finish-game", async (req, res) => {
const playerId = res.locals.playerId; // Attained from JWT
const { gameId } = req.body;
const player = await Player.findOne(playerId); // simulate database call
if (player === undefined) return res.status(404).send("Player not found");
const game = await Games.findOne(gameId); // simulate database call
if (game === undefined) return res.status(404).send("Game not found");
const playerFinalScore = game.scores[player.id];
await signer.sendTransaction({
nonce: nonce++, // auto increment to avoid pending issues
data: leaderboard.interface.encodeFunctionData(
"submitScore",
[
player.address, // string a.k.a address in 0x format
playerFinalScore // number
]
)
});
return res.status(200).send("Game Completed");
});
}
main()
.catch((err) => {
console.error(err);
process.exitCode = 1;
});

API

Solidity API

Leaderboard

A contract to manage a leaderboard of scores associated with Ethereum addresses

This contract inherits from the Authority contract and uses role-based access control

User

Struct representing a user entry in the leaderboard

struct User {
address user;
uint64 highScore;
uint64 timestamp;
uint32 index;
}

resetIndex

uint32 resetIndex

leaderboard

struct Leaderboard.User[] leaderboard

maxLength

uint32 maxLength

paused

bool paused

IncrementalReset

event IncrementalReset(uint32 amount)

Reset

event Reset()

SubmitScore

event SubmitScore(address user, uint64 highScore)

SubmitScoreAndAdd

event SubmitScoreAndAdd(address user, uint64 highScore)

constructor

constructor(uint32 _maxLength) public

Constructor to initialize the contract

Parameters

NameTypeDescription
_maxLengthuint32The maximum length of the leaderboard

submitScore

function submitScore(address user, uint64 highScore) public virtual

Submit a new high score for a user

Only callable by the SERVER_ROLE

Parameters

NameTypeDescription
useraddressThe Ethereum address of the user
highScoreuint64The new high score to be submitted

length

function length() public view returns (uint32)

Get the current length of the leaderboard

Return Values

NameTypeDescription
[0]uint32The length of the leaderboard

_addToLeaderboard

function _addToLeaderboard(address user, uint64 highScore, uint32 index) internal virtual

Internal function to add a new user to the leaderboard

Parameters

NameTypeDescription
useraddressThe Ethereum address of the user
highScoreuint64The new high score to be added
indexuint32The index at which the new user should be inserted

reset

function reset() external

Reset the entire leaderboard

Only callable by the MANAGER_ROLE Will revert if the leaderboard length is greater than 25,000

incrementalReset

function incrementalReset() public virtual

Perform an incremental reset of the leaderboard

Only callable by the MANAGER_ROLE Removes up to 1,500 entries from the leaderboard

_sort

function _sort(struct Leaderboard.User[] arr, int256 left, int256 right) internal virtual

Internal function to sort the leaderboard array using the quicksort algorithm

Parameters

NameTypeDescription
arrstruct Leaderboard.User[]The leaderboard array to be sorted
leftint256The left index of the subarray to be sorted
rightint256The right index of the subarray to be sorted