What it is

A drop-in randomizer for NFT projects built on /api/shuffle (for token ID assignment and reveal order) and /api/pick (for trait selection from weighted layers). Off-chain, so no LINK token, no gas, no oracle delay — but every assignment is backed by a serverHash committed before mint opens, which a holder can paste into /verify later.

A reveal order and its proof

This is what anyone can check. The three values in the steps are all a verifier needs, and the replay button recomputes the shuffle right here, without calling Provable.

GET /api/shuffle?clientSeed=mint_collection_abc_reveal_order&items=1,2,…,20Example record
{
  "clientSeed": "mint_collection_abc_reveal_order",
  "nonce": 0,
  "serverHash": "b11435bb484995cc67fe250afa1254920fb9a29a7a29454556cef7672ce551ac",
  "outcome": ["16", "15", "13", "1", "2", "4", "8", "11", "14", "9", "7", "3", "6", "12", "10", "20", "5", "18", "17", "19"]
}
  1. Before the shuffle, the server commits to a seedPublish this hash before the shuffle, so the seed can't be swapped afterwards.serverHash = sha256(serverSeed) = b11435bb484995cc67fe250afa1254920fb9a29a7a29454556cef7672ce551ac
  2. The shuffle is a pure function of both seedsHMAC-SHA256(serverSeed, "mint_collection_abc_reveal_order:0:0") → Fisher-Yates order of 20 items = [16, 15, 13, 1, 2, 4, 8, 11, 14, 9, … 10 more]
  3. After the shuffle, the seed is revealed and anyone can replay itserverSeed = 21bac4ab78790b7fa80d65720e2e279b81bea5902e03e40935ee403fe9156780

Try it live — shuffle a 100-token reveal order

curl "https://api.provable.io/api/ints?clientSeed=mint_collection_abc_reveal_order&count=10&min=1&max=100"

Or assign a trait per layer for a single token (background, body, eyes, accessory) with published rarity weights:

curl "https://api.provable.io/api/pick?clientSeed=token_42_background&items=sky,sunset,void,rainbow&weights=60,25,10,5"

Integration snippet

// 1. Before mint opens: pin a single commitment for the whole drop.
const { commitId, serverHash } = await fetch(
  "https://api.provable.io/api/commit",
  { method: "POST", headers: { "x-api-key": process.env.PROVABLE_KEY } }
).then((r) => r.json());
publishCollectionMetadata({ serverHash, mintRules: "..." });

// 2. After mint closes: reveal the seed and run the metadata shuffle.
const { serverSeed } = await fetch(
  "https://api.provable.io/api/reveal",
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "x-api-key": process.env.PROVABLE_KEY,
    },
    body: JSON.stringify({ commitId, clientSeed: `mint_${dropId}` }),
  }
).then((r) => r.json());

// 3. Run the trait shuffle for each token. Each token uses a deterministic
//    sub-seed derived from the revealed seed + tokenId, so the assignment
//    is reproducible by any holder.
async function assignTraits(tokenId, layers) {
  const out = {};
  for (const [layer, options] of Object.entries(layers)) {
    const url = new URL("https://api.provable.io/api/pick");
    url.searchParams.set("clientSeed", `${serverSeed}:${tokenId}:${layer}`);
    url.searchParams.set("items", options.map((o) => o.id).join(","));
    url.searchParams.set("weights", options.map((o) => o.weight).join(","));
    out[layer] = (await fetch(url).then((r) => r.json())).outcome;
  }
  return out;
}

Why this is fair

Common patterns

Where it fits

Related