Skip to content

Repository files navigation

PokéVault — provably-fair pack openings

Pay for a pack → open it → keep or claim the cards. No cash-out. Every card's odds are published before you buy, and every result is mathematically verifiable after.

A complete Next.js application built around one idea: a pack opening should be something you can check, not something you have to trust. The server commits to a result before you see it, publishes the exact probability of every card on the buy screen, and reveals the seed afterwards so anyone can re-run the arithmetic.


The three guarantees

1 · The result is committed before you open it. A SHA-256 commit-reveal scheme. Before the open, the server generates a secret serverSeed and publishes only its hash — a commitment it cannot change afterwards. After the open it reveals the seed, so anyone can hash it, compare it to the pre-shown commitment, and re-derive the exact card. The /verify page does this for you; every opening stores the seeds needed to reproduce it independently.

2 · The odds on the buy screen are the odds the server rolls against. Both the displayed percentages and the server's weighted pick are derived from the same weight values, so they cannot disagree. A pack whose odds don't sum to 100% is refused rather than served — see assertValidPool in lib/odds.ts. The pack page also runs a live simulator: it executes the real pickCard thousands of times in your browser and charts observed against published frequency.

3 · The reveal matches what you actually pulled. The animation is rarity-scaled: a Common gets a clean flip, a Mythic gets the full screen-flash. The intensity is derived from the card's revealTier, never from a fake near-miss — the buildup can only reflect the result the server had already committed to.

The fairness math

serverSeedHash = sha256(serverSeed)                    // published BEFORE the open
roll = parseInt(sha256(`${serverSeed}:${clientSeed}:${nonce}`).slice(0, 13), 16) / 16 ** 13
card = pickCard(pool, roll)                            // walk the weighted pool

13 hex characters is exactly 2⁵², so the roll is representable in a JavaScript double without loss. Multi-card packs use nonce, nonce+1, … — one roll per slot, each independently verifiable. Your clientSeed is mixed into every roll and you can change it at any time, which is what stops the house from having pre-picked a seed to your disadvantage.


Quickstart

npm install                 # installs deps and generates the Prisma client
# point DATABASE_URL / DIRECT_URL at a Postgres instance, then:
npm run setup               # prisma generate + db push + seed
npm run dev                 # http://localhost:3000

Every visitor is issued an anonymous cookie session and 1000 demo coins. There is no sign-up, no password, and no personal data.

Script What it does
npm run dev / build / start Next dev server / production build / serve
npm test Vitest — odds validation, weighted-pick distribution, fairness reproducibility
npm run db:push Apply the Prisma schema to Postgres
npm run db:seed Seed the cards and the four packs (validates odds = 100%)
npm run setup All three of the above in order

The packs

Pack Price Cards per pack
Poké Ball 50 5
Great Ball 120 7
Ultra Ball 300 9
Master Ball 750 11

122 cards across six rarity tiers — Common, Uncommon, Rare, Epic, Legendary, Mythic. Each pack publishes its full per-card odds table, summing to a verified 100%, on the same screen as the buy button.

How it's organised

app/
  (marketing)/          landing
  store/                pack grid, and [packId] detail with odds + simulator
  open/[openingId]/     the reveal, and its share image
  collection/  wallet/  owned cards and completion; the demo wallet
  verify/  history/     the verifier and an append-only opening log
  pulls/  wall/         your luck vs expected; recent rare pulls
  fairness/  account/   how it works; username + client seed
  api/                  all endpoints
components/             PackCard, OddsTable, Reveal, HoloCard, VerifyForm, …
lib/
  fairness.ts           hash, roll, recompute
  odds.ts               weighted pick + 100% validation
  catalog.ts            packs, odds, and the card dataset
  queries.ts            server-side Prisma → DTO mapping
  db.ts                 Prisma singleton with cold-start retry
  session.ts            anonymous cookie session
prisma/                 schema.prisma, seed.ts
tests/                  odds and fairness specs

Stack

TypeScript · Next.js 15 (App Router) · Tailwind · Framer Motion · Prisma · Postgres · Node crypto (SHA-256) · Vitest · Vercel.

Deployment is covered in DEPLOYMENT.md.


Scope and limitations

  • Demo currency only. Balances are clearly-labelled demo coins with no monetary value, and there is no cash-out anywhere — by design. Claiming a physical copy is the only "exit", and it is a stub in this prototype: there is no fulfillment, address handling, or shipping.
  • The payment seam is isolated. app/api/wallet/deposit-demo/route.ts is the single place a real processor would slot into; the open → collect → verify flow would not change.
  • Rate limiting is in-memory, which is adequate for a single-instance demo but would need a shared store in a real deployment.
  • Pokémon names, card images and set names are © The Pokémon Company / Nintendo / Game Freak. They are used here only to populate a non-commercial demonstration of the fairness and odds engine, which is deliberately game-agnostic — the card source can be replaced without touching the engine. Any public or commercial use would require licensing.

Licence

MIT for the source code — see LICENSE. What that licence does not cover (the Pokémon card data and images) is set out in NOTICE.

About

Provably-fair pack openings: SHA-256 commit-reveal, published per-card odds that sum to 100%, and a rarity-scaled reveal. Next.js + Prisma/Postgres.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages