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

satchel-sdk

v0.1.0

Published

One passkey, many namespaced keys: seal data per app, grant and revoke readers through an epoch registry on Monad.

Readme

satchel-sdk

One passkey, many namespaced keys. Seal data per app, hand a second app a revocable read grant, and cut it off with an epoch bump on Monad.

import { openOrEnrol, ns } from "satchel-sdk";

// mode is read on a first visit only: "enrol" creates a passkey, "open" uses one the person already has
const s = await openOrEnrol({ rpId: location.hostname, appName: "My app", mode });
const memory = ns("memory/assistant");
const blob = await s.seal(memory, { likes: "metric units" });                 // Sealed, safe to store anywhere
const back = await s.unsealJson(memory, blob);                                // { likes: "metric units" }
npm i satchel-sdk @category-labs/[email protected]

What you get

| Export | Does | |---|---| | ns(path) | A branded namespace. HKDF info is satchel/ns/ plus the path. | | openOrEnrol(o) | One passkey prompt per call. With a remembered credential it opens it (a gone passkey clears the memory and throws PASSKEY_NOT_FOUND); on a first visit it runs mode: "open" or mode: "enrol", and throws CHOOSE_PASSKEY_MODE without a mode. Call it from the click of a "Create a passkey" or "I already have one" button. | | rememberedPasskey(o) | The credential openOrEnrol remembers for this rpId, or undefined on a first visit: one button or two. | | enrol(o) / open(o) | New passkey or existing passkey, one prompt, through Mera 0.2.0. | | fromPrfOutput(prf, o) | The same session from a 32 byte PRF output you hold (tests, server-side readers). Takes ownership of the array and zeroes it on close(). | | Satchel.seal / unseal / unsealJson | AES-256-GCM, per-epoch data key, header bound as AAD. | | Satchel.fingerprint(ns) | 8 hex characters that match across devices. | | Satchel.addPasskey | Wraps the root to a second passkey in your Store. | | Satchel.grant / regrant / revoke | HPKE (RFC 9180, P-256) wrap of the epoch key to an app, recorded in the registry; waits for a successful receipt. revoke moves the namespace to the next epoch for every reader, and regrant issues the fresh grant a remaining reader needs. | | createGrantee() | The reading app's own key. It never touches the passkey. | | fetchGrant({ txHash, registry }) | Reads the Granted event from the grant's transaction receipt, so a grant crosses origins as a tx hash. | | epochOf, nsHashOf, registryAbi | Read the registry directly. | | parseSealed, SatchelError, isSatchelError | Header inspection and errors that carry code, fix and docs. |

Sealed bytes: SAT1 | version | nsHash (32) | epoch u64 | iv (12) | ciphertext, AAD is the header.

Registry

Pass registry: { address } in the open options for epochs, grant and revoke. The default chain is Monad testnet (10143); pass chain and rpcUrl for any other EVM chain. The registry owner is an EOA derived from the passkey (session.registryOwner), which pays gas for grant, regrant and revoke. Both throw OWNER_UNFUNDED with the address and the shortfall before sending when it cannot pay.

Tests

Runner: bun test (Bun 1.3). bun run test runs unit tests, six fast-check properties and the registry tests against the real SatchelRegistry on a local anvil. bun run mutation breaks namespacing, the epoch source and the HPKE key handling in temp copies and confirms the matching properties go red, then green on the real tree. Results land in test-results/.