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.
The pain point
Two questions kill NFT trust faster than anything: "did the team mint the legendaries first?" and "was metadata frozen before the reveal?" The on-chain answer is Chainlink VRF, which works but ships gas, an oracle dependency, and a contract upgrade. The off-chain answer is usually a hand-wave. This API gives you a third option — off-chain randomness with the same cryptographic guarantee you'd get from a published VRF proof.
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
- Pre-mint commitment. The
serverHashis published in the collection metadata before the mint window opens. The team cannot pick a seed that puts their wallets on the rare tokens. - Public reveal seed. After mint closes, the
serverSeedgoes on-chain (or on IPFS) alongside the assignment script. Anyone re-runs the deterministic shuffle and confirms the trait map. - Off-chain, but auditable. No gas costs, no oracle dependency, but the verification story is just as strong as a published VRF proof.
Common patterns
- Hidden metadata. Show a placeholder image until reveal; flip to the real
tokenURIonly after the seed is published. - Token-ID shuffle. Mint sequentially, then shuffle the mapping
mintIndex → tokenIdin one call so even sniped mint indices land on random tokens. - Layered traits. One
/api/pickcall per layer with published weights — players can replay the chain of pulls for any token. - Bonus / 1-of-1s. Reserve a fixed list of token IDs for special pieces, then shuffle the remainder — the reserved list and shuffle script are both public.
Where it fits
- PFP projects with layered trait rarities.
- Generative art drops where the seed seeds the renderer.
- Gaming NFT mints with stat distributions.
- Music / collectible drops with edition-number reveals.