Skip to main content

Utility Library

Tests Coverage Status ts GPLv3 license npm

The @pooltogether/v4-utils-js node module package provides calculations, computations and general utility functions for the PoolTogether V4 protocol.

Helping with both low-level calculations and higher-order computations, plus essential utilities like encoding, filtering and sorting.

Join the PoolTogether Discord, ask questions and get help from the community.


Table of Contents






💾  Installation

This project is available as an NPM package:

npm install @pooltogether/v4-utils-js
yarn add @pooltogether/v4-utils-js

The repo can be cloned from Github for contributions.

git clone

🏎️  Quickstart

Draw and PrizeDistribution structs should be fetched using the @pooltogether/v4-js-client node module.

Winnings Picks

import { winningPicks } from '@pooltogether/v4-utils-js';
const computedAndEncodedWinningPicks = winningPicks(wallet.address, [draw], [prizeDistribution]);

Compute & Encode Winnings Picks

import { computeWinningPicks, encodeWinningPicks } from '@pooltogether/v4-utils-js';
const computedPicks = computeWinningPicks(wallet.address, [draw], [prizeDistribution]);
const transaction = encodeWinningPicks(wallet.address, computedWinningPicks);

🧮  Examples

The utility library simulates smart contract rules/operations and also encapsulates higher-level abstractions common to PoolTogether V4 required transactions.

For example, in the DrawCalculator smart contract an account address is used to generate a random number via the keccak256 hashing function. The hashed address is subquentially encoded with a pickIndices to calculate a randomNumber. Using a depositors normalizedBalance and totalPrizeDistributionPicks we can find the ceiling for the number of picks to calculate for each Draw epoch timerange.

In other words, the library exposes low-level functions like hashUserAddress and calculateNumberOfMatches so it's easier to build the high-level abstractions, like winningPicks which simply takes a user address, plus historical Draw/PrizeDistribution structs and generates/encode all potential winning picks for a user into a single transaction.

Compute User Picks (computeUserPicks)

Calculates a depositor potential picks using the totalNumberOfPicks relative to the normalizedBalance.

import { parseEther } from '@ethersproject/units';
import { computeUserPicks } from '@pooltogether/v4-utils-js';

const totalPicks = parseUnits('1000', 18);
const address = '0x000.000';
const normalizedBalance = parseUnits('0.1', 18)

const userPicksByIndexAndHash = computeUserPicks(

Calculate Number of Matches (calculateNumberOfMatches)

A user's pick number and the Draw random generated number are compared to compute winning picks.

The pick and winningRandomNumber are NOT compared directly when calculating winning picks.

Instead using bitwise operations in conjuction with bitRangeSize and matchCardinality the pick/randomNumber can be compared at the bit level via dynamic "index" positions and "indexRanges" supplied by the PrizeDistrubtion parameters.

Abstract Example

UserPicks: 22, 6, 30, 2, 52, 90
WinningRandomNumber: 22, 6, 30, 66, 100, 40
Matches: true true true false false false
TotalMatches: 3

For efficient EVM storage the protocol avoids literal array representation for matching winning numbers - opting instead for cost-efficient bitwise operators, but the end result is the same: matching sets of numbers.

import { BigNumber } from '@ethersproject/bignumber';
import { calculateNumberOfMatches } from '@pooltogether/v4-utils-js';

const pickNumber = BigNumber.from('0x03030303030');
const winningRandomNumber = BigNumber.from('0x525255252552525');
const bitRangeSize = 10;
const matchCardinality = 3;

const numberOfMatchesForAPickNumber = calculateNumberOfMatches(

Reminder: All inputs should be formatted to match the underlying asset decimals.