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

@erc8426/conformance

v0.4.0

Published

Conformance suite for ERC-8426: check any contract and pass server against every MUST

Readme

@erc8426/conformance

Check a token contract and the pass server its passURI points at against every requirement of ERC-8426 that can be observed from outside. Use it in CI for your issuer, or to vet a collection before integrating it.

npx @erc8426/conformance --rpc https://rpc.example --contract 0x5F9B...c2e1 --token 412

With the owner's key, the suite also proves the owner path of a gated issuer (200 with Cache-Control: no-store, and a replayed proof refused). The key is only read from an environment variable, never from a flag, so it stays out of shell history, process listings and the report:

export ERC8426_OWNER_KEY=0x...    # or any name, with --owner-key-env NAME
npx @erc8426/conformance --rpc $RPC --contract $CONTRACT --token 412 --nonexistent-token 999999

The owner key signs only a challenge that passes the check @erc8426/client applies before it signs: it comes from the origin that serves the manifest, names that host as its verifier, and is scoped to this token, the owner's account and the acquire action, with an Expiration Time at most one hour ahead. A challenge that does not is never signed; the owner checks are skipped (gated.owner-200, gated.no-store, gated.replay-refused) with the reason as their detail.

Exit codes: 0 conforms to every MUST checked, 1 a MUST check failed, 2 usage error. SHOULD failures are reported as warnings and do not change the exit code. --json prints the full report.

What is checked

Each check has a stable id, the spec section it enforces, and its level.

| id | level | section | check | | - | - | - | - | | contract.erc165 | MUST | Contract interface | supportsInterface(0x01ffc9a7) is true | | contract.erc165-invalid | MUST | Contract interface | supportsInterface(0xffffffff) is false | | contract.interface-id | MUST | Contract interface | supportsInterface(0xef5f1e71) is true | | issuer.commitment | MUST | Issuer identity commitment | only for a contract whose supportsInterface(0x470be7e9) is true (the interface is optional; skipped otherwise): passIssuer() returns a JSON object with the shape of the manifest's issuer member. A supportsInterface read that fails is reported as a failure, not as a contract without the interface | | manifest.issuer-commitment | MUST | Issuer identity commitment | when the contract commits: the manifest carries issuer, equal member for member to passIssuer(). Reported as gated.manifest.issuer-commitment on the owner's gated manifest, and skipped when no manifest could be resolved to compare | | contract.passuri | MUST | Contract interface | passURI(tokenId) returns a URI | | contract.passuri-nonexistent | MUST | Contract interface | passURI reverts for a nonexistent token | | manifest.reachable | MUST | Pass manifest | the endpoint answers 200 (public) or 401 (gated) | | manifest.media-type | MUST | Pass manifest | the manifest is served as application/json | | manifest.valid | MUST | Pass manifest | non-empty formats, https acquisition URLs, updatedAt integer seconds; a version other than 1 fails (a Client MUST NOT act on a version it does not understand) | | manifest.version | MUST | Pass manifest | the manifest carries "version": 1 ("version is REQUIRED and is the integer 1"). A Client reads an absent version as 1, so the other manifest checks still run, but the Pass Issuer that omits it fails this check | | manifest.lint | SHOULD | Pass manifest | likely mistakes: updatedAt in milliseconds, a google URL that is not a Save link | | manifest.apple-media-type | SHOULD | Pass manifest | the apple URL is served as application/vnd.apple.pkpass; advisory, since the standard leaves the media type to the platform, but Apple devices expect it | | manifest.apple-reachable | SHOULD | Pass manifest | reported only when the apple URL cannot be fetched | | mirror.valid | MUST | Metadata mirror | a wallet_pass in tokenURI metadata is a valid manifest | | mirror.gated-no-urls | MUST | Metadata mirror | a gated issuer mirrors no acquisition URLs | | gated.401-proof-required | MUST | Gated acquisition | no proof: 401, error: "proof_required", a challenge URI | | gated.401-no-urls | MUST | Gated acquisition | the 401 body carries no acquisition URLs | | challenge.missing-address | MUST | Gated acquisition | 400 without address | | challenge.invalid-address | MUST | Gated acquisition | 400 for an invalid address | | challenge.issued | MUST | Gated acquisition | a JSON body with a message for a valid address | | challenge.siwe | SHOULD | Authorization | the challenge is an ERC-4361 message | | challenge.domain | MUST | Authorization | the verifier identity is present | | challenge.domain-host | SHOULD | Authorization | the domain is the serving host, so clients can check it | | challenge.address | MUST | Authorization | the claimed account echoes the request | | challenge.chain-id | MUST | Authorization | the chain id is the token's chain | | challenge.nonce | MUST | Authorization | a nonce is present | | challenge.expiration | MUST | Authorization | an Expiration Time in the future | | challenge.token-resource | MUST | Authorization | the first resource is this token's CAIP-19 id | | challenge.action-resource | MUST | Authorization | the action resource is urn:wallet-pass:action:acquire | | challenge.fresh-nonce | MUST | Gated acquisition | two requests yield two nonces | | gated.garbage-proof | MUST | Gated acquisition | a garbage proof is refused, and not with 403 | | gated.non-owner-403 | MUST | Gated acquisition | a valid proof from a fresh non-owner key gets exactly 403 | | gated.owner-200 | MUST | Gated acquisition | the owner's proof resolves the manifest (owner key required) | | gated.no-store | MUST | Gated acquisition | the verified response has Cache-Control: no-store | | gated.manifest.* | MUST | Pass manifest | the manifest checks, on the owner's gated manifest | | gated.replay-refused | MUST | Gated acquisition | the same proof a second time is refused, and not with 403 | | gated.expired-proof, gated.wrong-domain-proof | MUST | Authorization | always skipped: a valid expired or wrong-domain proof cannot be produced without the server, so the issuer's own tests must cover them |

Rotation on a new owner's first claim is not observable without a transfer and is not checked.

Library

import { runConformance, formatReport } from "@erc8426/conformance";

const report = await runConformance({
  rpcUrl: process.env.RPC_URL,          // or publicClient
  contract: "0x5F9B...c2e1",
  tokenId: 412n,
  nonexistentTokenId: 999999n,          // default: max uint256
  ownerPrivateKey: process.env.OWNER_KEY as `0x${string}` | undefined,
});
console.log(formatReport(report));
if (!report.ok) process.exit(1);

runConformance(options): Promise<ConformanceReport> where the report has chainId, contract, tokenId, configuration ("public" | "gated" | "unknown"), passUri, manifestUrl, ownerAddress, checks ({ id, title, section, level, status, detail? }), summary ({ pass, fail, warn, skip }) and ok. Options also take fetch, ipfsGateway, arweaveGateway and timeoutMs. runCli(argv, io) and parseArgs(argv) are exported for embedding the CLI.

License

MIT