Zulti Documentation

Referral Rewards

Rewards are earned from a random spin for each person you refer once they confirm their email and phone number. Every play pays out gems, XP and USD with every result able to be checked by you, independently of us.

How you earn plays

  • You earn one play for every person who signs up with your referral link and confirms their account.
  • Your first 7 confirmed referrals per withdrawal still earn you $1.00 each on top of the play.
  • Plays never expire. If a referral is banned before you play, their play is cancelled; it comes back if they are unbanned.
  • Open the Referral Program and choose Rewards to play. With several plays waiting, Quick claim all plays them back to back.

The distribution

Every reel uses a Kumaraswamy curve, a close relative of the Beta distribution whose formula can be inverted exactly, so anyone can reproduce a result by hand. A uniform number u between 0 and 1 is turned into a reward with:

x      = (1 - (1 - u)^(1 / scale))^(1 / shape)
reward = min + step × floor(((max - min) / step) × x + 0.5)

shape below 1 gives the curve its long tail. scale is solved so that the average reward, after rounding to the step, is exactly the advertised average. Rewards are paid to the nearest gems, whole XP and cent.

Reel min max step mean shape scale
Gems 10 500 1 150 0.5 1.19
XP 1 50 1 15 0.5 1.19
USD 0.05 10 0.01 0.15 0.5 12.6

Provably fair

Referral rewards follow the same commit-and-reveal scheme used by online casinos, so we cannot pick or change a result once you have started playing:

  1. We generate a secret server seed and show you its SHA-256 hash (the seed commit) before you play.
  2. You have a client seed, which you can change whenever you like.
  3. Every play uses the next nonce (0, 1, 2, …) so no two plays share an outcome.
  4. For reel r (0 gems, 1 XP, 2 USD) we compute HMAC-SHA256(key: server seed, message: "client seed:nonce:r") and divide its first 52 bits by 252 to get u.
  5. You can rotate your seeds whenever you have a play waiting. The new seeds are used from your next play, which also reveals the previous server seed. Hash it to check it matches the commit you were shown, then recompute every play made with it.

Verify a play

Enter a revealed server seed, the client seed and a nonce to recompute that play:

You can also run this in your browser's console:

async function reel(serverSeed, clientSeed, nonce, index, { min, max, step, shape, scale }) {
  const bytes = (text) => new TextEncoder().encode(text);
  const key = await crypto.subtle.importKey(
    "raw", bytes(serverSeed), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]
  );
  const digest = new Uint8Array(
    await crypto.subtle.sign("HMAC", key, bytes(`${clientSeed}:${nonce}:${index}`))
  );

  let bits = 0n;
  for (let i = 0; i < 7; i++) bits = (bits << 8n) | BigInt(digest[i]);
  const u = Number(bits >> 4n) / 2 ** 52; // first 52 bits

  const x = Math.pow(1 - Math.pow(1 - u, 1 / scale), 1 / shape);
  const k = Math.floor(((max - min) / step) * x + 0.5);
  return min + k * step;
}

Different devices can disagree in the last digit of floating point maths, which only matters for a result that falls exactly on a rounding boundary.