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

@thurinlabs/identity-kit

v2.2.1

Published

Check Thurin.id claims (PGP keys on Ethereum addresses) in Node, workers, or the browser

Readme

@thurinlabs/identity-kit

The library behind Thurin.id, the Thurin CLI, and the share cards. It reads PGP claims from the PGPRegistry contract, checks them with openpgp.js, and checks the proofs on the key. One core, so "verified" means the same thing everywhere.

Full reference: docs.thurin.id/#/sdk.

npm install @thurinlabs/identity-kit

Runs in Node, workers, and browsers. The only peer dependency is viem. @thurinlabs/identity-kit/core is the same entry, for older imports.

Read an address

import { createPublicClient, http } from 'viem'
import { mainnet } from 'viem/chains'
import { readClaims, keyStanding, claimCheckText } from '@thurinlabs/identity-kit'

const client = createPublicClient({ chain: mainnet, transport: http('https://ethereum.publicnode.com'), batch: { multicall: true } })

const claims = await readClaims(client, '0x539C7e1E454296Dc150B95a0acCC05bCa3b33538')
const { kind, claim } = keyStanding(claims)
// kind: verified · not-counted (active claims, none verify) · inactive (only ended claims) · none
if (kind === 'verified') console.log(claim.fingerprint, claim.pgpPublicKey)
if (kind === 'not-counted' && claim.verification) console.log(claimCheckText(claim.verification).label)

readClaims returns every claim, oldest first, and reads and verifies the newest 50 ({ limit } changes that; older ones come back with verification: null). keyStanding picks the newest active claim that verifies; keyStanding(claims, { fingerprint }) says the same for one key. findOwners(client, { fingerprint }) or { keyId } goes the other way: every address that ever claimed a key. ENS is left to you: resolve the name with viem first. batch: { multicall: true } makes the reads one request.

In React, wrap it in whatever you use for data, for example:

const { data: claims } = useQuery({ queryKey: ['claims', address], queryFn: () => readClaims(client, address) })

verifyAttestation checks that the key has the claimed fingerprint, that the signature is over exactly I control the Ethereum address: <lowercase address>, and that the key is valid today, as gpg judges it. When a claim doesn't count, kind says why (expired, revoked, compromised, unsupported, bad-signature, …) and claimCheckText turns it into words.

REGISTRY_ADDRESS is 0xFa6956c11163517249f8A67F5560a4406B519451, the same on Ethereum mainnet and Sepolia; getRegistry(network) gives each network's chain id, explorer, and default RPC. REGISTRY_ABI is the whole contract, writes included.

Is a key compromised? Ask the contract: keyStatus(owner, fingerprint). Don't read it off the newest claim, and never count "compromised" across owners: anyone can claim any fingerprint and mark it under their own address.

Check a key you already have

A key bundled in your page, pasted by a user, or fetched from a keyserver: is it the owner's?

import { checkKeyFor } from '@thurinlabs/identity-kit'

const r = await checkKeyFor(client, { key: armoredKey, owner: 'thurinlabs.eth' })
// r.status: verified · mismatch (don't encrypt to it) · unverified (nothing verified to compare with) · unreachable
console.log(r.reason)

owner is a 0x address or an ENS name. Any active, verified claim for the key counts, not only the newest. mismatch covers another verified key, a key the owner revoked, and input that isn't a key; a node failure is unreachable, never mismatch. The key is read before the node is asked anything.

For pages with no build step, dist/thurin-check.js (also on each GitHub release) is the same check as one readable file: ThurinCheck.checkKeyFor({ key, owner, rpc, network }). Keep thurin-check.LICENSES.txt beside it.

The rest, all covered in the docs:

  • Proofs: identifyProof, verifyProof (GitHub, DNS, Farcaster, Codeberg, Mastodon; a GitHub or Codeberg proof must belong to the account in its URL).
  • Claim history: claimFates, claimFateText, expiresSoon, expiresSoonText.
  • Records: kindName, checkKindName, checkRecordValue, fetchRecords, pickRecords, pageRecords, parseRecord, and the thurin.releases helpers.
  • Permissions: attestTypedData, reattestTypedData, updateKeyTypedData, revokeTypedData, setRecordTypedData, markCompromisedTypedData, for the registry's …For writes. To mark an already revoked claim compromised, sign markCompromisedTypedData alone. permissionSigned(client, typedData, signature, owner) judges a signed permission the way the registry does: the owner's key (EIP-7702 accounts too), else the owner account's own answer (EIP-1271, for Safes and smart-account wallets).
  • Keys: parsePgpKey, leanKey, claimSignature, sshKeys (SSH keys as authorized_keys lines), stripEmailUserIDs, fingerprint and key-ID helpers.
  • Encrypt: encryptionKeyFor (only the claim that counts, with a valid encryption subkey; says when the key arrived in the last 7 days), encryptTo (hides the recipient by default), encryptRefusalText, keyChangedText.
  • ENS: fetchEnsHint, ensHintWrite for the id.thurin record.
  • Avatars: avatarUrl, avatarFallbacks, only from places that can't see the viewer: IPFS, Arweave, inline data, a content-addressed NFT, or euc.li.

A card for your README

No library needed: thurin.id draws an image of any identity (name, key, and whether it's verified), https://thurin.id/card/ens/<name>.png. See the docs.

Key algorithms

Anything openpgp.js can verify: Ed25519, Cv25519, NIST P-256/384/521, brainpool, RSA, and secp256k1, which openpgp.js refuses by default and the kit allows. A secp256k1 PGP key is also an Ethereum key, so don't fund its address. In Node, secp256k1 needs eckey-utils, which the kit installs.

Upgrading from 1.x

2.0.0 reads PGPRegistry v3. Claims store the key and signature as raw bytes.

| 1.x | 2.0.0 | |---|---| | attestationsOf, getPayload | claimsOf, keyBytes, signatureBytes; readClaims does it for you | | Attestation | adds state, replacedBy, revokeReason; messageVersion 1 = detached, 0 = clearsigned | | typed data pgpSignature, pgpPublicKey | signature, key; Reattest adds keepRecords, Revoke adds reason, SetRecord takes text; new MarkCompromised | | encodeRecord, decodeRecord, bytes32 kinds | text records listed by recordsOf; checkKindName, checkRecordValue | | RegistryDeployment.deployBlock | removed | | IdentityKitProvider and the hooks (useThurinIdentity, useAttestations, …) | removed; readClaims, keyStanding, findOwners in any framework | | ThurinCard, the embed script, /styles, Theme, the baseUrl prop | removed; use the card image | | a statement containing the address verified | the signed text must be exactly the statement |

Development

npm install
npm run build
npm test

GitHub · Codeberg · MIT