CLI tooling to fetch leaderboard data and select Tide winners using a bracketed, weighted random process. It is designed to be auditable and reproducible: a published seed plus the saved leaderboard file must allow anyone to re-run the selection and get identical winners.
- Provide a transparent, deterministic methodology for selecting Tide winners.
- Keep the selection logic separate from the indexer, so results can be verified independently.
- Produce machine-readable outputs for publication and analysis.
- Fetch and rank leaderboard entries for an epoch:
- Pulls all entries from the indexer in batches.
- Sorts by
totalPointsWithMultiplier(descending), with a stable tie-breaker by address. - Writes
tides/{epoch}/leaderboard.json, including both display points and raw 18-decimal values.
- Build the eligible pool:
- Reads the saved leaderboard file.
- Excludes blacklisted addresses (optional input file).
- Excludes entries with zero Pearls.
- Uses 18-decimal raw totals for selection weights when available.
- Split into rank brackets:
- Ranks 1–100: 50 winners
- Ranks 101–200: 25 winners
- Ranks 201–300: 15 winners
- Ranks 301+: 10 winners
- Run weighted random draws per bracket (no replacement):
- For each winner slot, draw one winner.
- A wallet's chance in a draw is:
Pearls / Total Remaining Pearls in that bracket - After a wallet wins, it is removed from that bracket for subsequent draws.
- Write results:
- Outputs
tides/{epochId}/winners.jsonincluding the seed and per-draw probability for each winner.
- Outputs
- If you run
pick-winnerswith--seed <hex>, the draw is fully deterministic. - If you do not pass a seed, one is generated and stored in the output file.
- Publish both the seed and the exact
tides/{epoch}/leaderboard.jsonused for selection so anyone can reproduce the results.
- Only addresses with non-zero Pearls are eligible.
- Blacklisted addresses can be excluded via a supplied blacklist file.
- Eligibility is evaluated at the time of selection using the saved leaderboard file.
Written by fetch-leaderboard.
- Includes ranks and display points with 2 decimals.
- Also preserves raw 18-decimal values for accurate weighting.
Example fields:
{
"totalPointsWithMultiplier": "21010489.03",
"totalPointsWithMultiplierRaw": "21010489030000000000000000"
}Written by pick-winners.
{
"epochId": 1,
"timestamp": "2026-02-01T11:51:00.000Z",
"seed": "random-seed-hex",
"totalParticipants": 500,
"totalWinners": 100,
"winners": [
{
"address": "0x...",
"rank": 5,
"pearls": "1234567890123456789012",
"bracket": "Ranks 1-100",
"probability": 5.234
}
]
}One wallet address per line for non-technical sharing.
Written by export-report.
- Summarizes participants, total Pearls, and winners per bracket.
GitHub-friendly report with summary, bracket breakdown, and winner table.
- Create
.env:
cp .env.example .env- Configure Hasura credentials:
INDEXER_ENDPOINT=https://index.neverland.money/v1/graphql
HASURA_ADMIN_SECRET=your-actual-admin-secretIf you run the indexer locally, you don't need to set up Hasura credentials, as the indexer will use the local Hasura instance. See neverland-hyperindex/README.md for more details.
- Install dependencies:
pnpm installpnpm fetch-leaderboard <epoch> [--blacklist <path>]Blacklist input can be provided via --blacklist or BLACKLIST_PATH and supports:
- JSON array:
["0x...", "0x..."] - JSON object:
{ "addresses": ["0x..."] } - Newline-delimited addresses
pnpm pick-winners <epochId> [--seed <hex>] [--blacklist <path>]Reproducible run with a published seed:
pnpm pick-winners 1 --seed <seed-hex>pnpm export-report <epochId>Generate leaderboard, pick winners, and export reports in one step:
pnpm run-tide <epochId> [--seed <hex>] [--blacklist <path>]Create a Safe transaction batch JSON for permanent lock rewards:
pnpm generate-safe-batch <epochId> [--contract <address>] [--amount <dust>] [--safe <address>] [--chain <id>] [--batch-size <n>]Options:
--contract: Voting escrow contract address (default: from env or0xBB4738D05AD1b3Da57a4881baE62Ce9bb1eEeD6C)--amount: DUST amount (default: from env or100), automatically converted to wei (×10¹⁸)--safe: Safe multisig address (default: from env or0xb83a6637c87E6a7192b3ADA845c0745F815e9006)--chain: Chain ID (default: from env or143)--batch-size: Split winners into multiple batches of this size (optional, e.g.,50for block gas limit constraints)
Single batch (default):
Generates tides/{epochId}/safe-batch.json
Multiple batches:
pnpm generate-safe-batch 1 --batch-size 50Generates safe-batch-1.json, safe-batch-2.json, etc. Execute sequentially in the Safe Transaction Builder.
Edit src/config.ts to change:
- Bracket ranges and winner counts
- Pearls decimals
- Hasura endpoint and admin secret
The core selection logic has deterministic unit tests.
pnpm test
pnpm test:coverageCoverage is scoped to the core algorithm modules:
src/blacklist.tssrc/random.tssrc/selection.ts
- Pearls are stored with 18-decimal precision (like ETH wei).
- Probabilities are per draw, not guarantees.
- Selection is fair by process, not by outcome; winners are random but weighted.
This project is licensed under the MIT License - a permissive open-source license that allows you to:
- Use the code commercially or privately
- Modify and distribute the code
- Sublicense the code
- Use the code without warranty
The only requirement is that you include the original copyright notice and license text in any substantial portions of the software.
See the LICENSE file for the full license text.