npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@firstdeck/shuffle

v0.1.0

Published

Provably fair ordering derived from a Bitcoin block hash — and The First Deck, the first 52-card playing deck inscribed on Bitcoin.

Readme

@firstdeck/shuffle

Provably fair ordering derived from a Bitcoin block hash.

This is the derivation core of The First Deck Shuffle — a recursive ordinal inscription that shuffles The First Deck, the first complete 52-card playing deck ever inscribed on Bitcoin — extracted into a reusable package.

Same seed → same order, in every browser, in every language, forever.

  • Zero dependencies. SHA-256 via WebCrypto and nothing else.
  • No build step. ESM source is what ships. Node 20+, all modern bundlers, browsers.
  • Spec-pinned. Tested against the same frozen vectors the on-chain inscription was tested against before it was inscribed at block 964106.
npm install @firstdeck/shuffle

The generic primitive

Order any list from a block hash. Nothing about this is card-specific.

import { shuffle } from '@firstdeck/shuffle';

const { items, order, seed } = await shuffle(
  ['alice', 'bob', 'carol', 'dave'],
  { blockHash: '00000000000000000000ba3ace4b409b82faf1e4f4ddc8528588031251a27880' }
);

Add a commit when two parties need to agree that neither picked the block after seeing the outcome:

await shuffle(entrants, { blockHash, commit: myCommitmentHash });

permute(count, options) gives you the raw permutation of 0..count-1 if you would rather apply it yourself.

Verification is the point

Anyone can recompute your result from the block hash alone:

import { verify } from '@firstdeck/shuffle';

const { valid, expected } = await verify(publishedOrder, { blockHash, commit });

And trace: true returns every PRNG block, every raw draw, every rejected draw, and every swap, in order — enough to walk a skeptic through the derivation by hand:

const { trace } = await permute(52, { blockHash, trace: true });
trace.steps[0]; // { k, n, limit, rejected, draw, r, swapped }

The First Deck

import { shuffleDeck, card, contentUrl, FIRST_DECK } from '@firstdeck/shuffle';

const { codes, cards } = await shuffleDeck({ blockHash, commit });
codes[0];                       // '5D'
cards[0].inscriptionNumber;     // 139473
contentUrl('7D', 'https://ordinals.com');
// https://ordinals.com/content/cb60af7994e16c5a01fcdc599b908e5c9541fea25e4ac496a6fc6e705418d2efi0

FIRST_DECK carries all 52 cards in canonical order with their inscription IDs, so the art is always referenced through ord's recursive endpoints and never embedded.

Canonical order is Spades A–K (0–12), Hearts (13–25), Diamonds (26–38), Clubs (39–51).

The algorithm

seed_material = block_hash_hex || ":" || commit_hex     # ":" omitted if no commit
seed          = SHA256( UTF8(seed_material) )

stream(i)     = SHA256( seed || UINT32BE(i) )           # consumed as big-endian uint32

# draw r in [0, n) without modulo bias:
limit = floor(2^32 / n) * n
take v; if v >= limit reject and take the next; else r = v mod n

# Fisher-Yates, top down:
for k from count-1 down to 1:
    r = draw(k + 1)
    swap(deck[k], deck[r])

That is the whole thing, and it must never change: this is the derivation already inscribed on Bitcoin, so the specification is frozen by the chain rather than by this repository. The frozen vectors in vectors/vectors.json are the normative statement of it.

The one way to get this wrong

ord's /r/blockhash returns a JSON-quoted string:

"00000000000000000000ba3ace4b409b82faf1e4f4ddc8528588031251a27880"

Hashing it with the quotes attached produces a shuffle that looks completely plausible and is permanently wrong. This package refuses a quoted or uppercase hash rather than letting it through:

await shuffle(items, { blockHash: rawFromOrd });
// TypeError: blockHash must be 64 lowercase hex characters — it looks
// JSON-quoted; ord's /r/blockhash returns a quoted string, JSON.parse it first

JSON.parse it first. Always.

Reproducing the canonical shuffle

The inscription's pinned launch link freezes block 964106 and commits to the precursor inscription. You can reproduce exactly what it renders:

node examples/reproduce-launch.mjs
block hash     00000000000000000000ba3ace4b409b82faf1e4f4ddc8528588031251a27880
seed           2635ad436e13243c986ac80491be9f574852d7fe58ec1549dc99b3fa3fc964df
top of deck    5 of Diamonds

Compare against the inscription itself. They agree, or one of us is wrong — and the chain does not move.

Testing your own implementation

The frozen vectors are the real asset here. If you reimplement this spec in another language, hold it to this file:

import vectors from '@firstdeck/shuffle/vectors.json' with { type: 'json' };

It carries five vectors (seed, order and card codes for each) and four traps — inputs designed to catch the mistakes that produce plausible-but-wrong output. A stdlib-only Python reference lives in reference/derive.py; the test suite runs both implementations against 250 generated inputs and requires that they never disagree.

If an implementation disagrees with the vectors, fix the implementation. Never the vectors.

On chain

| | inscription | block | |---|---|---| | precursor — The First Card | 1df40bc6…i0 (#127,231,957) | 964096 | | The First Deck Shuffle | 5ba5a97e…i0 (#127,232,859) | 964106 |

Exposed as INSCRIPTIONS from the package.

License

MIT