Why this matters

If players can't audit your shuffle, they have to trust your server. With one batched API call and a few lines of Fisher-Yates, the same shuffle becomes reproducible — anyone with the seed can recompute the deck.

Step 1: build the deck

const SUITS = ["♠", "♥", "♦", "♣"];
const RANKS = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"];

function freshDeck() {
  const deck = [];
  for (const s of SUITS) for (const r of RANKS) deck.push(r + s);
  return deck; // 52 cards
}

Step 2: pull all the swap indices in one request

Fisher-Yates needs 51 random integers — one per swap. Grab them in a single call with count=51; each index i needs a number in [0, i], so request the widest range and modulo down.

async function getSwapIndices(clientSeed, n = 51) {
  const url = new URL("https://api.provable.io/api/ints");
  url.searchParams.set("clientSeed", clientSeed);
  url.searchParams.set("count", String(n));
  url.searchParams.set("min", "0");
  url.searchParams.set("max", "1000000");
  const res = await fetch(url, {
    headers: { "x-api-key": process.env.PROVABLE_KEY }
  });
  return (await res.json()).outcome;
}

Step 3: Fisher-Yates with the API's numbers

async function shuffleDeck(clientSeed) {
  const deck = freshDeck();
  const rands = await getSwapIndices(clientSeed, deck.length - 1);

  for (let i = deck.length - 1; i > 0; i--) {
    const j = rands[deck.length - 1 - i] % (i + 1);
    [deck[i], deck[j]] = [deck[j], deck[i]];
  }
  return deck;
}

const hand = (await shuffleDeck("hand_2026_05_24_table_7")).slice(0, 5);
console.log(hand); // e.g. ["Q♦","7♠","2♣","K♥","9♦"]

Make it verifiable

  1. Publish the serverHash for the seed before the hand starts.
  2. Reveal the clientSeed (or let the player pick it).
  3. Anyone can re-run the same Fisher-Yates code with the same numbers and confirm the deck.

Tips for production

Next steps