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

identity-bridge

v0.1.0

Published

Identity middleware for credential wallet discovery, VP exchange, and cryptographic verification — the identity layer crypto wallets are missing

Readme

identity-bridge

Universal discovery protocol for credential wallet browser extensions — like EIP-6963 but for W3C identity wallets.

Sites broadcast a discovery event, installed wallet extensions announce themselves with their DID identity and metadata. Multiple wallets can coexist — the user always chooses.

sequenceDiagram
    participant Site as Website
    participant A as Extension A<br>(Attestto Creds)
    participant B as Extension B<br>(Credible)

    Site->>Site: dispatchEvent('credential-wallet:discover')
    A-->>Site: announce → did:web:attestto.com:wallets:attestto-creds
    B-->>Site: announce → did:web:credible.dev:wallets:credible
    Note over Site: Collect announcements (800ms window)
    Site->>Site: Show wallet picker → user chooses
    Site->>A: navigator.credentials.get({ VerifiablePresentation })
    A-->>Site: Returns signed VP
    Site->>Site: Verify VP → resolve DID → check signature → issuer trust → revocation

Identity Middleware — not a wallet connector

WalletConnect, Dynamic, and Wagmi are crypto wallet connectors. They connect MetaMask, Phantom, and Ledger to dApps for transaction signing — send ETH, swap tokens, call contracts. They prove "this person controls this private key." That's where they stop.

This package is the credential exchange layer that comes after. It connects credential wallets — Attestto Creds, Credible, Trinsic — to sites for Verifiable Presentations. It proves "a trusted issuer attested this about you" with field-level selective disclosure and cryptographic verification.

Why this matters

A crypto wallet connector tells you someone owns address 0xabc.... It cannot tell you that person passed KYC, holds a vLEI credential from GLEIF, or has a verified institutional identity. For any regulated use case — FATF Travel Rule, eIDAS 2.0, AML compliance — you need the identity layer, not just the key.

What you get vs. what exists

The full stack

  1. WalletConnect → connect Solana/Ethereum wallet → get address
  2. identity-resolver → resolve that address → find SNS domain, Attestto credentials, Civic pass, vLEI attestation
  3. identity-bridge → discover credential wallet extensions → request VP → verify cryptographically

How this relates to existing standards and tools

Several projects touch parts of this problem. None cover the same surface.

Where identity-bridge fits: The W3C Digital Credentials API routes to OS-level wallets. identity-bridge discovers browser extension wallets — the same gap EIP-6963 filled for Ethereum wallets when window.ethereum only supported one provider at a time. As DC-API matures for OS wallets and CHAPI standardizes the browser API, identity-bridge provides the missing extension discovery layer with built-in VP verification that neither standard includes.

Install

npm install identity-bridge

Quick Start

Site-side (your web app)

import { discoverWallets, verifyPresentation } from 'identity-bridge'

// 1. Discover installed credential wallets
const wallets = await discoverWallets()

if (wallets.length === 0) {
  // No wallet found — show install prompts
} else if (wallets.length === 1) {
  // One wallet — auto-select
  console.log('Using', wallets[0].name, wallets[0].did)
} else {
  // Multiple wallets — show picker
  wallets.forEach(w => console.log(w.name, w.did, w.protocols))
}

// 2. After user picks a wallet, request a credential via standard CHAPI
const credential = await navigator.credentials.get({
  web: {
    VerifiablePresentation: {
      query: { type: 'DIDAuthentication' },
      challenge: crypto.randomUUID(),
      domain: window.location.origin,
    },
  },
})

// 3. Verify the returned VP cryptographically
const result = await verifyPresentation(credential, wallets[0], {
  resolverUrl: 'https://your-backend.com/api/resolver',
  trustedIssuers: ['did:web:attestto.com'],
})

if (result.valid) {
  console.log('Verified holder:', result.holderDid)
  console.log('DID Document:', result.didDocument)
} else {
  console.error('Verification failed:', result.errors)
}

Wallet-side (your browser extension)

import { registerWallet } from 'identity-bridge'

// Call once in your content script (MAIN world)
registerWallet({
  did: 'did:web:yourorg.com:wallets:your-wallet',
  name: 'Your Wallet',
  icon: 'https://yourorg.com/icon-64.svg',
  version: '1.0.0',
  protocols: ['chapi', 'didcomm-v2'],
  maintainer: {
    name: 'Your Org',
    did: 'did:web:yourorg.com',
    url: 'https://yourorg.com',
  },
})

API

discoverWallets(timeoutMs?: number): Promise<WalletAnnouncement[]>

Discover all credential wallet extensions. Dispatches a discover event and collects announcements within the timeout window (default 800ms).

registerWallet(wallet: WalletAnnouncement): void

Register your wallet extension to respond to discovery events. Call once in your content script's MAIN world.

verifyPresentation(vp, wallet, options): Promise<VerifyResult>

Verify a Verifiable Presentation returned by a credential wallet. Performs the full trust chain:

  1. Wallet trust check — is this wallet in your trusted wallets list?
  2. Holder extraction — extract the holder DID from the VP
  3. DID resolution — resolve the holder's DID Document from your resolver
  4. Signature verification — verify the VP signature against the DID Document
  5. Issuer trust check — are all VC issuers in your trusted issuers list?
  6. Revocation check — query each VC's Bitstring Status List for revocation
const result = await verifyPresentation(vp, wallet, {
  resolverUrl: 'https://your-backend.com/api/resolver',  // DID resolver endpoint (required)
  trustedIssuers: ['did:web:attestto.com'],               // Trusted VC issuers (required)
  trustedWallets: ['did:web:attestto.com:wallets:attestto-creds'], // Optional wallet allowlist
  checkRevocation: true,                                   // Check Bitstring Status List (default true)
  signal: abortController.signal,                          // Optional AbortSignal
})

Returns:

interface VerifyResult {
  valid: boolean                         // true if zero errors
  holderDid: string | null               // The holder's DID extracted from the VP
  errors: VerifyError[]                  // All verification failures
  didDocument: Record<string, unknown> | null  // Resolved DID Document
}

interface VerifyError {
  code: VerifyErrorCode                  // Machine-readable error code
  message: string                        // Human-readable description
}

type VerifyErrorCode =
  | 'NO_HOLDER'           // VP has no holder DID
  | 'RESOLUTION_FAILED'   // Could not resolve the holder's DID
  | 'SIGNATURE_INVALID'   // VP signature verification failed
  | 'ISSUER_UNTRUSTED'    // VC issuer not in trustedIssuers list
  | 'CREDENTIAL_REVOKED'  // VC revoked via Bitstring Status List
  | 'WALLET_UNTRUSTED'    // Wallet DID not in trustedWallets list

WalletAnnouncement

interface WalletAnnouncement {
  did: string              // Wallet's own DID
  name: string             // Human-readable name
  icon: string             // Icon URL (SVG or PNG, 64x64)
  version: string          // Semantic version
  protocols: WalletProtocol[]  // Supported protocols
  maintainer: WalletMaintainer
  url?: string             // Homepage / docs
}

type WalletProtocol = 'chapi' | 'didcomm-v2' | 'oid4vp' | 'waci-didcomm'

interface WalletMaintainer {
  name: string
  did?: string
  url?: string
}

Event Constants

import { DISCOVER_EVENT, ANNOUNCE_EVENT } from 'identity-bridge'
// 'credential-wallet:discover'
// 'credential-wallet:announce'

Writing a Custom Wallet Integration

Follow these steps to make your browser extension discoverable via this protocol.

Step 1 — Install and register

Add the package to your extension and call registerWallet() in a content script that runs in the page's MAIN world (not the isolated extension world).

// content-script.ts (MAIN world)
import { registerWallet } from 'identity-bridge'

registerWallet({
  did: 'did:web:yourorg.com:wallets:your-wallet',
  name: 'Your Wallet',
  icon: 'https://yourorg.com/icon-64.svg',
  version: '1.0.0',
  protocols: ['chapi'],
  maintainer: { name: 'Your Org', did: 'did:web:yourorg.com' },
})

Your did field must be a real, resolvable DID. The protocol eats its own dog food — wallets identify themselves the same way users do.

Step 2 — Handle CHAPI requests

Override navigator.credentials.get() in the MAIN world to intercept credential requests:

const originalGet = navigator.credentials.get.bind(navigator.credentials)

navigator.credentials.get = async function (options) {
  // Check if this is a VP request
  const vpRequest = (options as any)?.web?.VerifiablePresentation
  if (!vpRequest) return originalGet(options)

  // Show your consent UI to the user
  const userConsented = await showConsentDialog(vpRequest)
  if (!userConsented) throw new DOMException('User denied', 'NotAllowedError')

  // Build and return the VP
  return buildVerifiablePresentation(vpRequest)
}

Step 3 — Return a Verifiable Presentation

The VP you return must include:

  • holder — the user's DID (string or { id: 'did:...' })
  • verifiableCredential — array of VCs
  • proof — cryptographic signature over the VP
function buildVerifiablePresentation(request) {
  return {
    '@context': ['https://www.w3.org/2018/credentials/v1'],
    type: ['VerifiablePresentation'],
    holder: 'did:web:user.example.com',
    verifiableCredential: [/* user's selected VCs */],
    proof: {
      type: 'Ed25519Signature2020',
      created: new Date().toISOString(),
      challenge: request.challenge,
      domain: request.domain,
      verificationMethod: 'did:web:user.example.com#key-1',
      proofValue: '...',  // Sign with the user's private key
    },
  }
}

Step 4 — Verify your DID is resolvable

The site will verify your VP by resolving the holder's DID and checking the signature against the public key in the DID Document. Make sure:

  • The holder's DID resolves to a valid DID Document
  • The DID Document contains the public key referenced in proof.verificationMethod
  • The proof.challenge and proof.domain match what the site sent

Supported Protocols

Security

See SECURITY.md for:

  • Wallet discovery spoofing — discovery is untrusted metadata; trust is established via VP verification
  • Trusted wallet allowlist — restrict which wallet DIDs your app accepts
  • Cross-origin considerations — CORS for DID resolution and revocation checks
  • API key exposure — always use a backend proxy for resolver calls
  • Trust chain — the DID method spec defines where to resolve, not the VC

See It In Action

The DID Landscape Explorer uses this package in its self-assessment wizard. The Identity step discovers installed wallets and lets users present their DID.

Contributing

See CONTRIBUTING.md.

License

MIT