The setup

A loot table maps outcomes to weights. A common pattern:

const LOOT_TABLE = [
  { item: "Common Sword",     weight: 600 }, // 60%
  { item: "Uncommon Shield",   weight: 300 }, // 30%
  { item: "Rare Potion",       weight: 90  }, // 9%
  { item: "Epic Helmet",       weight: 9   }, // 0.9%
  { item: "Legendary Artifact", weight: 1  }, // 0.1%
];

Weights don't have to sum to 100 or 1000 — they're relative.

The algorithm

  1. Sum all weights → total.
  2. Pull one random integer in [1, total] from the API.
  3. Walk the table accumulating weights until you pass the random value.
async function pullLoot(clientSeed, table) {
  const total = table.reduce((s, e) => s + e.weight, 0);

  const url = new URL("https://api.provable.io/api/ints");
  url.searchParams.set("clientSeed", clientSeed);
  url.searchParams.set("count", "1");
  url.searchParams.set("min", "1");
  url.searchParams.set("max", String(total));

  const res = await fetch(url, {
    headers: { "x-api-key": process.env.PROVABLE_KEY }
  });
  const { outcome, serverHash } = await res.json();
  const roll = outcome[0];

  let cumulative = 0;
  for (const entry of table) {
    cumulative += entry.weight;
    if (roll <= cumulative) {
      return { item: entry.item, roll, total, serverHash };
    }
  }
}

console.log(await pullLoot("user_42_pull_001", LOOT_TABLE));

Pulling 10 at once

For a "10x pull" box, request count=10 and walk the table for each roll:

async function pullTen(clientSeed, table) {
  const total = table.reduce((s, e) => s + e.weight, 0);
  const url = `https://api.provable.io/api/ints?clientSeed=${clientSeed}&count=10&min=1&max=${total}`;
  const { outcome } = await fetch(url).then(r => r.json());

  return outcome.map(roll => {
    let c = 0;
    for (const e of table) { c += e.weight; if (roll <= c) return e.item; }
  });
}

Why this is provably fair

The split is deterministic — the same roll always maps to the same item. The roll itself comes from a committed serverHash. Publish the table, the seed, and the hash, and a player who pulls a Common can confirm "the API really did roll 437 out of 1000, and 437 falls in the Common Sword band".

Production tips

Next steps