{"_id":"@attestto/id-wallet-adapter","_rev":"5-61066aae9ee6f623f0666fc45f6d7ab0","name":"@attestto/id-wallet-adapter","dist-tags":{"latest":"0.7.0"},"versions":{"0.3.0":{"name":"@attestto/id-wallet-adapter","version":"0.3.0","keywords":["did","credential","wallet","discovery","chapi","verifiable-credentials","verifiable-presentation","w3c","ssi","identity","didcomm"],"author":{"name":"Eduardo Chongkan","email":"eduardo@attestto.com"},"license":"MIT","_id":"@attestto/id-wallet-adapter@0.3.0","maintainers":[{"name":"chongkan","email":"e.chongkan@gmail.com"}],"contributors":[{"name":"Guillermo Chavarria","email":"guillermocc@attestto.com"}],"homepage":"https://github.com/Attestto-com/id-wallet-adapter#readme","bugs":{"url":"https://github.com/Attestto-com/id-wallet-adapter/issues"},"dist":{"shasum":"2fc50856daa32940c10f81425a962d352fcfe1ba","tarball":"https://registry.npmjs.org/@attestto/id-wallet-adapter/-/id-wallet-adapter-0.3.0.tgz","fileCount":6,"integrity":"sha512-wFIkrzW+Moa8InpnFqCRc6yzojW7j7iqyw3KvIv4CMdfBtEGl2iLnLouyMhqzkaxym39J38qXidWA6pNyWWgGg==","signatures":[{"sig":"MEYCIQD0Rx8AljqHuw4h1v3dBAqP3kvviToB+B9HEJumalqEFAIhAIbdc2oWZ+wBv2ZjQqcsMUJJtUVAKhUV1ujso+ol6B+3","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":77039},"main":"dist/index.cjs","type":"module","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"54074356adee807727e54a5a419bdea6b57c8eaf","scripts":{"lint":"tsc --noEmit","build":"tsup src/index.ts --format esm,cjs --dts --clean","prepublishOnly":"npm run build"},"_npmUser":{"name":"chongkan","email":"e.chongkan@gmail.com"},"repository":{"url":"git+https://github.com/Attestto-com/id-wallet-adapter.git","type":"git"},"_npmVersion":"11.9.0","description":"Universal wallet adapter for credential wallet extensions — discovery, VP exchange, and cryptographic verification","directories":{},"_nodeVersion":"25.6.1","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","typescript":"^5.4.0"},"_npmOperationalInternal":{"tmp":"tmp/id-wallet-adapter_0.3.0_1775259794810_0.7906789587632308","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@attestto/id-wallet-adapter","version":"0.4.0","keywords":["did","credential","wallet","discovery","chapi","verifiable-credentials","verifiable-presentation","w3c","ssi","identity","didcomm"],"author":{"name":"Eduardo Chongkan","email":"eduardo@attestto.com"},"license":"MIT","_id":"@attestto/id-wallet-adapter@0.4.0","maintainers":[{"name":"chongkan","email":"e.chongkan@gmail.com"}],"contributors":[{"name":"Guillermo Chavarria","email":"guillermocc@attestto.com"}],"homepage":"https://github.com/Attestto-com/id-wallet-adapter#readme","bugs":{"url":"https://github.com/Attestto-com/id-wallet-adapter/issues"},"dist":{"shasum":"264f550ea8462746c0a7e0665f1110dc47df2c78","tarball":"https://registry.npmjs.org/@attestto/id-wallet-adapter/-/id-wallet-adapter-0.4.0.tgz","fileCount":6,"integrity":"sha512-6VeV/wK6YxfjhC6ocjTuJYfUY0BZYlC/KvXvaqYAYi/SwsmzIpYz74F8HJ3Z7GWZFpN81KoSPA4HPEAbu42uIw==","signatures":[{"sig":"MEQCIAX+dYu0V3gzZ3uXVdwJggJL47FJF0uIL9Ni7kKcwbL6AiAXdrDVXxfJarkeR4A/iGqD8Mvn/R3sAxotV3uuKvydaA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":96549},"main":"dist/index.cjs","type":"module","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"54e45b8c19c5c0894eff3d8c03403f0cda5672d4","scripts":{"lint":"tsc --noEmit","test":"vitest","build":"tsup src/index.ts --format esm,cjs --dts --clean","coverage":"vitest run --coverage","test:run":"vitest run","prepublishOnly":"npm run lint && npm run test:run && npm run build"},"_npmUser":{"name":"chongkan","email":"e.chongkan@gmail.com"},"repository":{"url":"git+https://github.com/Attestto-com/id-wallet-adapter.git","type":"git"},"_npmVersion":"11.9.0","description":"Universal wallet adapter for credential wallet extensions — discovery, VP exchange, and cryptographic verification","directories":{},"_nodeVersion":"25.6.1","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","jsdom":"^25.0.0","vitest":"^2.1.0","typescript":"^5.4.0","@vitest/coverage-v8":"^2.1.9"},"_npmOperationalInternal":{"tmp":"tmp/id-wallet-adapter_0.4.0_1775731243275_0.8980619273477481","host":"s3://npm-registry-packages-npm-production"}},"0.4.1":{"name":"@attestto/id-wallet-adapter","version":"0.4.1","keywords":["did","credential","wallet","discovery","chapi","verifiable-credentials","verifiable-presentation","w3c","ssi","identity","didcomm"],"author":{"name":"Eduardo Chongkan","email":"eduardo@attestto.com"},"license":"Apache-2.0","_id":"@attestto/id-wallet-adapter@0.4.1","maintainers":[{"name":"chongkan","email":"e.chongkan@gmail.com"}],"contributors":[{"name":"Guillermo Chavarria","email":"guillermocc@attestto.com"}],"homepage":"https://github.com/Attestto-com/id-wallet-adapter#readme","bugs":{"url":"https://github.com/Attestto-com/id-wallet-adapter/issues"},"dist":{"shasum":"617c956e8f87b8d7ad0d605d3e4d7608bb41eb62","tarball":"https://registry.npmjs.org/@attestto/id-wallet-adapter/-/id-wallet-adapter-0.4.1.tgz","fileCount":7,"integrity":"sha512-k2SZ1ZKiKQNin1Y/O+e6ylBdQxkaKkzc+vn8GK+YTI0R5DzDbRRLiTT3DxjPbssQbFXkDRQFq/NGkfOr+NJQBQ==","signatures":[{"sig":"MEUCIEqTJ8Y5ypn9eM1oDofPCsFYFWopwnLsO24FVcRq8L/uAiEAiAI9uBdsJiM2e2cADLrTFkmyo+BiN50AT3pS7HvfZ/U=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":103500},"main":"dist/index.cjs","type":"module","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"f4e8f7f14875ee6b87afbe6ab31e5a2b2ace5faf","scripts":{"lint":"tsc --noEmit","test":"vitest","build":"tsup src/index.ts --format esm,cjs --dts --clean","coverage":"vitest run --coverage","test:run":"vitest run","prepublishOnly":"npm run lint && npm run test:run && npm run build"},"_npmUser":{"name":"chongkan","email":"e.chongkan@gmail.com"},"repository":{"url":"git+https://github.com/Attestto-com/id-wallet-adapter.git","type":"git"},"_npmVersion":"11.9.0","description":"Universal wallet adapter for credential wallet extensions — discovery, VP exchange, and cryptographic verification","directories":{},"_nodeVersion":"25.6.1","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","jsdom":"^25.0.0","vitest":"^2.1.0","typescript":"^5.4.0","@vitest/coverage-v8":"^2.1.9"},"_npmOperationalInternal":{"tmp":"tmp/id-wallet-adapter_0.4.1_1775733328237_0.7915500628966348","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"@attestto/id-wallet-adapter","version":"0.5.0","keywords":["did","credential","wallet","discovery","chapi","verifiable-credentials","verifiable-presentation","w3c","ssi","identity","didcomm"],"author":{"name":"Eduardo Chongkan","email":"eduardo@attestto.com"},"license":"Apache-2.0","_id":"@attestto/id-wallet-adapter@0.5.0","maintainers":[{"name":"chongkan","email":"e.chongkan@gmail.com"}],"contributors":[{"name":"Guillermo Chavarria","email":"guillermocc@attestto.com"}],"homepage":"https://attestto.org","bugs":{"url":"https://github.com/Attestto-com/id-wallet-adapter/issues"},"dist":{"shasum":"8a56d6e0f8c68b7c11ca68b415bd0450abaf60c8","tarball":"https://registry.npmjs.org/@attestto/id-wallet-adapter/-/id-wallet-adapter-0.5.0.tgz","fileCount":7,"integrity":"sha512-qzELtsQPcVGLUxnBBy6qfhrkXQRmRBdn2iTK2gQ1DLBKJBB/H8ApqQBTK+RfUC/bFzSGl0+1Pn8iTBH/5d0fSQ==","signatures":[{"sig":"MEQCIBH/adnjZnNO8lCeVaYVebxgRDRWdxRKWq95x9rd9+mIAiBjbxgDXCLc8C7fajjPXqrHH739IGCM50pBmfwJNt4UTw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":120069},"main":"dist/index.cjs","type":"module","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"b515abaf2ed87347b782c3102a2724224f5fdb68","scripts":{"lint":"tsc --noEmit","test":"vitest","build":"tsup src/index.ts --format esm,cjs --dts --clean","coverage":"vitest run --coverage","test:run":"vitest run","prepublishOnly":"npm run lint && npm run test:run && npm run build"},"_npmUser":{"name":"chongkan","email":"e.chongkan@gmail.com"},"repository":{"url":"git+https://github.com/Attestto-com/id-wallet-adapter.git","type":"git"},"_npmVersion":"11.9.0","description":"Universal wallet adapter for credential wallet extensions — discovery, VP exchange, and cryptographic verification","directories":{},"_nodeVersion":"25.6.1","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","jsdom":"^25.0.0","vitest":"^4.1.2","typescript":"^5.4.0","@vitest/coverage-v8":"^4.1.2"},"_npmOperationalInternal":{"tmp":"tmp/id-wallet-adapter_0.5.0_1782423511158_0.8603406011920016","host":"s3://npm-registry-packages-npm-production"}},"0.7.0":{"name":"@attestto/id-wallet-adapter","version":"0.7.0","description":"Universal wallet adapter for credential wallet extensions — discovery, VP exchange, and cryptographic verification","homepage":"https://attestto.org","type":"module","main":"dist/index.cjs","module":"dist/index.js","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"scripts":{"build":"tsup src/index.ts --format esm,cjs --dts --clean","lint":"tsc --noEmit","test":"vitest","test:run":"vitest run","coverage":"vitest run --coverage","prepublishOnly":"npm run lint && npm run test:run && npm run build"},"keywords":["did","credential","wallet","discovery","chapi","verifiable-credentials","verifiable-presentation","w3c","ssi","identity","didcomm"],"license":"Apache-2.0","repository":{"type":"git","url":"git+https://github.com/Attestto-com/id-wallet-adapter.git"},"author":{"name":"Eduardo Chongkan","email":"eduardo@attestto.com"},"contributors":[{"name":"Guillermo Chavarria","email":"guillermocc@attestto.com"}],"devDependencies":{"@vitest/coverage-v8":"^4.1.2","jsdom":"^25.0.0","tsup":"^8.0.0","typescript":"^5.4.0","vitest":"^4.1.2"},"gitHead":"86d99a7f7daae19f0eeecb59107e5e347145c81a","_id":"@attestto/id-wallet-adapter@0.7.0","bugs":{"url":"https://github.com/Attestto-com/id-wallet-adapter/issues"},"_nodeVersion":"25.6.1","_npmVersion":"11.9.0","dist":{"integrity":"sha512-HG6QM6JY6xT73YZyNeMv9mfKb+eDp+53XYrC2kwYFUMDO2kncpAIqj07nKd7ikr/eaSeJMTxhJ4k7gMtlfe5GQ==","shasum":"7d594726db64c5f1d13ac969c0b0218ed317499b","tarball":"https://registry.npmjs.org/@attestto/id-wallet-adapter/-/id-wallet-adapter-0.7.0.tgz","fileCount":7,"unpackedSize":158244,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCItNBkQ8kGOYSoMmkfhqGuKq6jyTOHKjhTya4DqDhL6gIhAK+YukfnM/nOjj/upOjnh4hcTVcsgbOsnpXHoK4gINd7"}]},"_npmUser":{"name":"chongkan","email":"e.chongkan@gmail.com"},"directories":{},"maintainers":[{"name":"chongkan","email":"e.chongkan@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/id-wallet-adapter_0.7.0_1784494153363_0.9021214755099047"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-03T23:43:14.724Z","modified":"2026-07-19T20:49:13.669Z","0.3.0":"2026-04-03T23:43:15.000Z","0.4.0":"2026-04-09T10:40:43.457Z","0.4.1":"2026-04-09T11:15:28.417Z","0.5.0":"2026-06-25T21:38:31.317Z","0.7.0":"2026-07-19T20:49:13.501Z"},"bugs":{"url":"https://github.com/Attestto-com/id-wallet-adapter/issues"},"author":{"name":"Eduardo Chongkan","email":"eduardo@attestto.com"},"license":"Apache-2.0","homepage":"https://attestto.org","keywords":["did","credential","wallet","discovery","chapi","verifiable-credentials","verifiable-presentation","w3c","ssi","identity","didcomm"],"repository":{"type":"git","url":"git+https://github.com/Attestto-com/id-wallet-adapter.git"},"description":"Universal wallet adapter for credential wallet extensions — discovery, VP exchange, and cryptographic verification","contributors":[{"name":"Guillermo Chavarria","email":"guillermocc@attestto.com"}],"maintainers":[{"name":"chongkan","email":"e.chongkan@gmail.com"}],"readme":"# @attestto/id-wallet-adapter\n\n[![npm version](https://img.shields.io/npm/v/@attestto/id-wallet-adapter.svg)](https://www.npmjs.com/package/@attestto/id-wallet-adapter)\n\nPart of the [Attestto](https://attestto.org) identity infrastructure. [Documentation](https://attestto.org/docs)\n\n> Discovery and verification layer for credential wallet browser extensions — like EIP-6963 but for W3C identity wallets.\n\n**What's new in v0.5.0** (2026-06-25) — DID-based authentication (`requestAuth`) + `trustedIssuers` filter on sign/auth requests + new \"Trust model\" section below. Additive, no breaking changes. See [CHANGELOG.md](./CHANGELOG.md).\n\nA website needs to verify a user's identity — KYC status, a university degree, a vLEI credential from GLEIF, a government-issued ID. The user has a credential wallet (browser extension or mobile app) that holds these credentials. This package handles discovery (which wallet does the user have), requests (ask for a Verifiable Presentation with selective disclosure), and verification (validate the cryptographic proof chain).\n\n## Architecture\n\n```mermaid\nflowchart LR\n    A[\"App / Site\"] --> B[\"id-wallet-adapter\"]\n    B -->|discoverWallets| C[\"Extensions in MAIN world\"]\n    B -->|registerWallet| D[\"Wallet Extension A\"]\n    B -->|registerWallet| E[\"Wallet Extension B\"]\n    B -->|registerWallet| F[\"Wallet Extension C\"]\n    D --> G[\"User's DIDs\"]\n    E --> G\n    F --> G\n    G -->|verifyPresentation| H[\"Trust Chain<br/>DID resolution<br/>Signature verification<br/>Issuer trust<br/>Revocation check\"]\n    \n    style A fill:#1a1a2e,stroke:#7c3aed,color:#e0e0e0\n    style B fill:#1a1a2e,stroke:#10b981,color:#e0e0e0\n    style H fill:#1a1a2e,stroke:#06b6d4,color:#e0e0e0\n```\n\nThe identity space has wire protocols (OID4VP, DIDComm v2) that define how credentials move between wallets and sites. But before those conversations can happen, you need a discovery layer. **id-wallet-adapter is that discovery layer.** Without it, sites hardcode detection for every wallet (\"Connect with Wallet X\" buttons) — the NASCAR problem. EIP-6963 solved this for Ethereum wallets. id-wallet-adapter solves it for identity wallets.\n\n### Why this matters\n\nFor any regulated use case — FATF Travel Rule, eIDAS 2.0, AML compliance — you need cryptographic proof that a trusted issuer attested something about a person. A password or an OAuth token won't cut it. Verifiable Credentials are the standard. This package is the discovery and verification layer that connects the site to the user's credential wallet.\n\n### Where this fits in the ecosystem\n\nThe identity space has wire protocols (how credentials move) and discovery protocols (how you find the wallet). Most projects focus on the wire. We focus on the discovery.\n\n**Wire protocols** define the conversation:\n\n| Protocol | What it does | Limitation |\n|---|---|---|\n| [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html) | VP exchange via OAuth 2.0 flows, QR codes, deep links | Assumes you already know the wallet. No browser extension discovery. |\n| [DIDComm v2](https://identity.foundation/didcomm-messaging/spec/) | Encrypted peer-to-peer messaging between agents | Designed for server/mobile agents, not browser extensions. |\n| [WACI-DIDComm](https://identity.foundation/wallet-and-credential-interactions/) | Challenge-response credential exchange via QR/deep links | Defines the exchange flow, not wallet discovery. Assumes wallet is already known. |\n| [W3C CHAPI](https://chapi.io/) | Browser mediator for `navigator.credentials` | Central mediator dependency. No late-arrival handling. No custom UI. |\n\n**Discovery protocols** find who to talk to:\n\n| Protocol | What it does | Limitation |\n|---|---|---|\n| [W3C Digital Credentials API](https://w3c-fedid.github.io/digital-credentials/) | Routes to OS wallets (Apple Wallet, Google Wallet) | No browser extension discovery. |\n| [DIF Wallet Rendering](https://identity.foundation/wallet-rendering/) | Standardizes how credentials look (icons, colors, labels) | Not about discovery — complementary. |\n| [Aries RFC 0031](https://identity.foundation/aries-rfcs/latest/features/0031-discover-features/) | Agent-to-agent feature/protocol negotiation via query/disclose messages | Runtime negotiation between connected agents. Not browser discovery. |\n| [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963) | Multi-provider discovery for Ethereum wallets | Ethereum-only. Not for identity wallets. |\n| **id-wallet-adapter** | Discovers browser extension credential wallets, provides interactive picker with late-arrival support | Browser-first. Mobile deep links are out of scope (OID4VP handles that). |\n\n**id-wallet-adapter is the application-layer discovery that triggers wire protocols.** It doesn't replace OID4VP or DIDComm — it finds the wallet, then the site uses whatever wire protocol it needs.\n\nThink of it like DNS vs HTTP. OID4VP is the conversation. We're the lookup.\n\n### The NASCAR problem\n\nWithout a discovery protocol, sites end up with a wall of \"Connect with [Wallet X]\" buttons — the [NASCAR problem](https://indieweb.org/NASCAR_problem). EIP-6963 solved this for Ethereum. id-wallet-adapter solves it for identity wallets.\n\n```ts\n// Before: hardcode every wallet\nif (window.attesttoId) { /* ... */ }\nif (window.credible) { /* ... */ }\nif (window.trinsic) { /* ... */ }\n\n// After: discover all, let the user pick\nconst wallet = await pickWallet()\n```\n\n## Quick start\n\n### Prerequisites\n\n- Node.js 16+\n- Browser with support for `CustomEvent` (all modern browsers)\n- Credential wallet browser extension installed (optional for initial testing)\n\n### Install\n\n```bash\nnpm install @attestto/id-wallet-adapter\n```\n\n### Try it\n\n#### Three tiers of usage\n\n```ts\nimport { discoverWallets, pickWallet, verifyPresentation } from '@attestto/id-wallet-adapter'\n\n// ── Tier 1: Headless — build your own UI ──────────────────\nconst wallets = await discoverWallets()\n// You render however you want\n\n// ── Tier 2: Default modal — zero config, vanilla JS ───────\nconst wallet = await pickWallet()\n// Built-in modal, framework-agnostic, works everywhere\n\n// ── Tier 3: Custom renderer — bring your own UI ───────────\nconst wallet = await pickWallet({\n  render: (onSelect, onCancel) => ({\n    update: (wallets) => { /* re-render your list */ },\n    destroy: () => { /* cleanup DOM */ },\n  })\n})\n```\n\nLate-arriving wallets are pushed to the renderer via `update()` — no stale lists.\n\n### Full example (Tier 2 + verification)\n\n```ts\nimport { pickWallet, verifyPresentation } from '@attestto/id-wallet-adapter'\n\n// 1. User picks a wallet from the built-in modal\nconst wallet = await pickWallet()\nif (!wallet) return // user cancelled\n\n// 2. Request a credential via standard CHAPI\nconst credential = await navigator.credentials.get({\n  web: {\n    VerifiablePresentation: {\n      query: { type: 'DIDAuthentication' },\n      challenge: crypto.randomUUID(),\n      domain: window.location.origin,\n    },\n  },\n})\n\n// 3. Verify the returned VP cryptographically\nconst result = await verifyPresentation(credential, wallet, {\n  resolverUrl: 'https://your-backend.com/api/resolver',\n  trustedIssuers: ['did:web:attestto.com'],\n})\n\nif (result.valid) {\n  console.log('Verified holder:', result.holderDid)\n} else {\n  console.error('Verification failed:', result.errors)\n}\n```\n\n### Wallet-side (your browser extension)\n\n```ts\nimport { registerWallet } from '@attestto/id-wallet-adapter'\n\n// Call once in your content script (MAIN world)\nregisterWallet({\n  did: 'did:web:yourorg.com:wallets:your-wallet',\n  name: 'Your Wallet',\n  icon: 'https://yourorg.com/icon-64.svg',\n  version: '1.0.0',\n  protocols: ['chapi', 'didcomm-v2'],\n  goals: ['verify-identity', 'issue-credential', 'present-proof'],\n  maintainer: {\n    name: 'Your Org',\n    did: 'did:web:yourorg.com',\n    url: 'https://yourorg.com',\n  },\n})\n```\n\n## API\n\n### `discoverWallets(timeoutMs?: number): Promise<WalletAnnouncement[]>`\n\nDiscover all credential wallet extensions. Dispatches a discover event and collects announcements within the timeout window (default 800ms).\n\n### `registerWallet(wallet: WalletAnnouncement): void`\n\nRegister your wallet extension to respond to discovery events. Call once in your content script's MAIN world.\n\n### `pickWallet(options?): Promise<WalletAnnouncement | null>`\n\nInteractive wallet picker with three usage tiers. Returns the selected wallet, or `null` if cancelled / no wallets found.\n\n```ts\n// Default modal — zero config\nconst wallet = await pickWallet()\n\n// Filter by protocol — only show OID4VP-capable wallets\nconst wallet = await pickWallet({ requiredProtocols: ['oid4vp'] })\n\n// Filter by goal — only wallets that handle government IDs\nconst wallet = await pickWallet({ requiredGoals: ['verify-identity'] })\n\n// Custom renderer — bring your own UI\nconst wallet = await pickWallet({\n  render: (onSelect, onCancel) => ({\n    update: (wallets) => { /* called on each new wallet discovery */ },\n    destroy: () => { /* cleanup */ },\n  })\n})\n\n// QR fallback — show QR code when no browser extensions found\nconst wallet = await pickWallet({\n  qrFallback: {\n    url: 'https://your-backend.com/oid4vp/request/abc123',\n    label: 'Scan with your mobile wallet',\n  }\n})\n```\n\n**Options:**\n\n| Option | Type | Default | Description |\n|---|---|---|---|\n| `timeoutMs` | `number` | `2000` | How long to wait for wallet announcements |\n| `requiredProtocols` | `WalletProtocol[]` | `[]` | Only show wallets supporting all listed protocols |\n| `requiredGoals` | `string[]` | `[]` | Only show wallets supporting all listed goal codes |\n| `qrFallback` | `QrFallbackOptions` | — | QR code shown when no extensions found (see below) |\n| `render` | `function` | built-in modal | Custom render function (see below) |\n\n**QR fallback options:**\n\n| Option | Type | Default | Description |\n|---|---|---|---|\n| `url` | `string` | (required) | URL encoded in the QR code (e.g. OID4VP request URI) |\n| `label` | `string` | `\"Scan with mobile wallet\"` | Text shown below the QR code |\n| `onResponse` | `function` | — | Called when your backend signals the mobile wallet responded |\n\nWhen no browser extension wallets are discovered within the timeout, the default modal switches from \"Discovering wallets...\" to the QR code. If a late-arriving extension announces after the QR is shown, the modal updates to show both.\n\n**Custom renderer contract:**\n\n```ts\ninterface PickerRenderer {\n  update: (wallets: WalletAnnouncement[]) => void  // Called on each new discovery\n  destroy: () => void                                // Called to tear down UI\n}\n```\n\n**Late-arriving wallets:** Browser extensions inject content scripts at `document_start`, but some take a few extra milliseconds. If the picker modal is already open and a new wallet announces itself, `update()` fires with the full cumulative list — the UI adds the new wallet in real-time instead of showing a stale list. No reload needed.\n\n### `verifyPresentation(vp, wallet, options): Promise<VerifyResult>`\n\nVerify a Verifiable Presentation returned by a credential wallet. Performs the full trust chain:\n\n1. **Wallet trust check** — is this wallet in your trusted wallets list?\n2. **Holder extraction** — extract the holder DID from the VP\n3. **DID resolution** — resolve the holder's DID Document from your resolver\n4. **Signature verification** — verify the VP signature against the DID Document\n5. **Issuer trust check** — are all VC issuers in your trusted issuers list?\n6. **Revocation check** — query each VC's Bitstring Status List for revocation\n\n```ts\nconst result = await verifyPresentation(vp, wallet, {\n  resolverUrl: 'https://your-backend.com/api/resolver',  // DID resolver endpoint (required)\n  trustedIssuers: ['did:web:attestto.com'],               // Trusted VC issuers (required)\n  trustedWallets: ['did:web:attestto.com:wallets:attestto-creds'], // Optional wallet allowlist\n  checkRevocation: true,                                   // Check Bitstring Status List (default true)\n  signal: abortController.signal,                          // Optional AbortSignal\n})\n```\n\n**Returns:**\n\n```ts\ninterface VerifyResult {\n  valid: boolean                         // true if zero errors\n  holderDid: string | null               // The holder's DID extracted from the VP\n  errors: VerifyError[]                  // All verification failures\n  didDocument: Record<string, unknown> | null  // Resolved DID Document\n}\n\ninterface VerifyError {\n  code: VerifyErrorCode                  // Machine-readable error code\n  message: string                        // Human-readable description\n}\n\n// Package-defined error codes — use these to handle failures programmatically\ntype VerifyErrorCode =\n  | 'NO_HOLDER'           // VP has no holder DID\n  | 'RESOLUTION_FAILED'   // Could not resolve the holder's DID\n  | 'SIGNATURE_INVALID'   // VP signature verification failed\n  | 'ISSUER_UNTRUSTED'    // VC issuer not in trustedIssuers list\n  | 'CREDENTIAL_REVOKED'  // VC revoked via Bitstring Status List\n  | 'WALLET_UNTRUSTED'    // Wallet DID not in trustedWallets list\n```\n\nThese codes are defined by this package (not a W3C or DIF standard). They map to the six verification steps. The `message` field provides human-readable context for logging or UI display.\n\n### `WalletAnnouncement`\n\n```ts\ninterface WalletAnnouncement {\n  did: string              // Wallet's own DID\n  name: string             // Human-readable name\n  icon: string             // Icon URL (SVG or PNG, 64x64)\n  version: string          // Semantic version\n  protocols: WalletProtocol[]  // Supported protocols\n  goals?: string[]             // Capability goal codes (Aries RFC 0519)\n  maintainer: WalletMaintainer\n  url?: string             // Homepage / docs\n}\n\ntype WalletProtocol = 'chapi' | 'didcomm-v2' | 'oid4vp' | 'waci-didcomm'\n\ninterface WalletMaintainer {\n  name: string\n  did?: string\n  url?: string\n}\n```\n\n### `requestSignature(wallet, request, options?): Promise<SignResponse | null>`\n\nRequest a document signature from a specific wallet. The wallet extension receives the request, shows a consent popup, and responds with the DID signature. Returns `null` if the user rejects or the timeout elapses.\n\n```ts\nimport { discoverWallets, requestSignature } from '@attestto/id-wallet-adapter'\n\nconst [wallet] = await discoverWallets()\nconst result = await requestSignature(wallet, {\n  hash: 'abc123...',           // SHA-256 hex\n  fileName: 'contract.pdf',\n  hashAlgorithm: 'SHA-256',\n  fileSize: 9912,              // optional\n})\n\nif (result?.approved) {\n  console.log('Signed by', result.did)\n  console.log('Signature:', result.signature)\n}\n```\n\n**Request:**\n\n| Field | Type | Description |\n|---|---|---|\n| `hash` | `string` | Content hash (hex) to sign |\n| `fileName` | `string` | Human-readable document name (shown in consent popup) |\n| `hashAlgorithm` | `string` | Hash algorithm used (e.g. `'SHA-256'`) |\n| `fileSize` | `number?` | Optional file size in bytes (for display) |\n| `trustedIssuers` | `string[]?` | _(v0.5.0+)_ DIDs of issuers you accept. Wallet filters/highlights matching identities so users don't sign with an identity you'd reject. Omit to accept any identity. |\n\n**Response:**\n\n| Field | Type | Description |\n|---|---|---|\n| `approved` | `boolean` | Whether the user approved the signature |\n| `did` | `string?` | Signer's DID (present when approved) |\n| `signature` | `string?` | Base64url-encoded signature value |\n| `publicKeyJwk` | `JsonWebKey?` | Signer's public key in JWK format |\n| `timestamp` | `string?` | ISO 8601 timestamp of signature creation |\n\n**Options:**\n\n| Option | Type | Default | Description |\n|---|---|---|---|\n| `timeoutMs` | `number` | `120000` | How long to wait for the wallet to respond |\n\n**Protocol:** Uses the same nonce-based CustomEvent pattern as discovery:\n1. Site dispatches `credential-wallet:sign` with nonce + request\n2. Wallet extension shows consent popup\n3. Wallet responds with `credential-wallet:sign-response` + nonce + result\n\n### `requestAuth(wallet, request, options?): Promise<UnverifiedAuthResponse | null>`\n\n_(v0.5.0+)_ DID-based authentication — the \"Sign in with Attestto\" primitive. The wallet proves control of an identity DID against a site-issued nonce. Mirrors `requestSignature` but with login semantics (`nonce` + `audience` + `origin`) instead of a document hash. Returns `null` if the user rejects or the timeout elapses.\n\n> **SECURITY (v0.7.0+):** the result arrives over a page `window` event that any script can forge, so `approved`/`did` are **not** proof of authentication. You **must** pass the result to [`verifyAuth`](#verifyauthresponse-options-promiseauthverifyresult) and trust only `authenticated === true`.\n\n```ts\nimport { discoverWallets, requestAuth, verifyAuth } from '@attestto/id-wallet-adapter'\n\nconst [wallet] = await discoverWallets()\nconst nonce = crypto.randomUUID()\nconst audience = window.location.origin\nconst origin = window.location.origin\n\nconst result = await requestAuth(wallet, { nonce, audience, origin })\nif (result) {\n  const verified = await verifyAuth(result, {\n    expectedNonce: nonce,\n    expectedAudience: audience,\n    expectedOrigin: origin,\n    resolverUrl: 'https://resolver.attestto.org',\n  })\n  if (verified.authenticated) {\n    console.log('Signed in as', verified.did) // only trustworthy after verifyAuth\n  } else {\n    console.warn('Auth rejected:', verified.errors)\n  }\n}\n```\n\n### `verifyAuth(response, options): Promise<AuthVerifyResult>`\n\n_(v0.7.0+)_ The mandatory verifier for `requestAuth`. Fail-closed, it (1) verifies the signature over the canonical payload (`canonicalAuthMessage`, version `attestto-did-auth-v1`) with zero-dependency WebCrypto (Ed25519 / ES256), (2) resolves the DID and confirms the signing key is in the DID Document's `authentication` relationship, and (3) checks nonce/audience/origin binding + freshness (`maxAgeSeconds`, default 300). Returns `{ authenticated, did, errors }`.\n\n**Request:**\n\n| Field | Type | Description |\n|---|---|---|\n| `nonce` | `string` | Server-generated single-use challenge — verify it matches what you issued |\n| `audience` | `string` | Your verifier identifier (typically your URL or DID) — wallet signs over this |\n| `origin` | `string` | Page origin (typically `window.location.origin`) |\n| `trustedIssuers` | `string[]?` | DIDs of issuers you accept. Wallet uses this to filter and highlight matching identities so the user doesn't sign in with an identity you'd reject. Omit to accept any identity. |\n\n**Response:** same shape as `requestSignature` (`approved`, `did`, `signature`, `publicKeyJwk`, `timestamp`).\n\n**Options:**\n\n| Option | Type | Default | Description |\n|---|---|---|---|\n| `timeoutMs` | `number` | `120000` | How long to wait for the wallet to respond |\n\n**Protocol:** nonce-based CustomEvent pattern:\n1. Site dispatches `credential-wallet:auth` with nonce + request\n2. Wallet shows consent popup (user picks identity if multiple, filtered by `trustedIssuers`)\n3. Wallet responds with `credential-wallet:auth-response` + nonce + result\n\n**Backend verification:** verify the returned signature against the public key in the holder's DID Document. If you set `trustedIssuers`, also verify that an issuer in your list attested the identity.\n\n### Event Constants\n\n```ts\nimport {\n  DISCOVER_EVENT,         // 'credential-wallet:discover'\n  ANNOUNCE_EVENT,         // 'credential-wallet:announce'\n  SIGN_EVENT,             // 'credential-wallet:sign'\n  SIGN_RESPONSE_EVENT,    // 'credential-wallet:sign-response'\n  AUTH_EVENT,             // 'credential-wallet:auth'              (v0.5.0+)\n  AUTH_RESPONSE_EVENT,    // 'credential-wallet:auth-response'     (v0.5.0+)\n} from '@attestto/id-wallet-adapter'\n```\n\n## Writing a Custom Wallet Integration\n\nFollow these steps to make your browser extension discoverable via this protocol.\n\n### Step 1 — Install and register\n\nAdd 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).\n\n```ts\n// content-script.ts (MAIN world)\nimport { registerWallet } from '@attestto/id-wallet-adapter'\n\nregisterWallet({\n  did: 'did:web:yourorg.com:wallets:your-wallet',\n  name: 'Your Wallet',\n  icon: 'https://yourorg.com/icon-64.svg',\n  version: '1.0.0',\n  protocols: ['chapi'],\n  maintainer: { name: 'Your Org', did: 'did:web:yourorg.com' },\n})\n```\n\nYour `did` field must be a real, resolvable DID. The protocol eats its own dog food — wallets identify themselves the same way users do.\n\n### Step 2 — Handle CHAPI requests\n\nOverride `navigator.credentials.get()` in the MAIN world to intercept credential requests:\n\n```ts\nconst originalGet = navigator.credentials.get.bind(navigator.credentials)\n\nnavigator.credentials.get = async function (options) {\n  // Check if this is a VP request\n  const vpRequest = (options as any)?.web?.VerifiablePresentation\n  if (!vpRequest) return originalGet(options)\n\n  // Show your consent UI to the user\n  const userConsented = await showConsentDialog(vpRequest)\n  if (!userConsented) throw new DOMException('User denied', 'NotAllowedError')\n\n  // Build and return the VP\n  return buildVerifiablePresentation(vpRequest)\n}\n```\n\n### Step 3 — Return a Verifiable Presentation\n\nThe VP you return must include:\n\n- **`holder`** — the user's DID (string or `{ id: 'did:...' }`)\n- **`verifiableCredential`** — array of VCs\n- **`proof`** — cryptographic signature over the VP\n\n```ts\nfunction buildVerifiablePresentation(request) {\n  return {\n    '@context': ['https://www.w3.org/2018/credentials/v1'],\n    type: ['VerifiablePresentation'],\n    holder: 'did:web:user.example.com',\n    verifiableCredential: [/* user's selected VCs */],\n    proof: {\n      type: 'Ed25519Signature2020',\n      created: new Date().toISOString(),\n      challenge: request.challenge,\n      domain: request.domain,\n      verificationMethod: 'did:web:user.example.com#key-1',\n      proofValue: '...',  // Sign with the user's private key\n    },\n  }\n}\n```\n\n### Step 4 — Handle signing requests\n\nListen for `credential-wallet:sign` events and respond with the signature:\n\n```ts\n// content-script.ts (MAIN world)\nwindow.addEventListener('credential-wallet:sign', async (e) => {\n  const { nonce, walletDid, request } = e.detail\n  if (walletDid !== YOUR_WALLET_DID) return\n\n  // Show consent popup to the user\n  const approved = await showSignConsent(request.fileName, request.hash)\n\n  // Sign and respond\n  const signature = approved ? await signWithUserKey(request.hash) : null\n  window.dispatchEvent(new CustomEvent('credential-wallet:sign-response', {\n    detail: {\n      nonce,\n      response: {\n        approved,\n        did: approved ? userDid : undefined,\n        signature: approved ? signature : undefined,\n        timestamp: approved ? new Date().toISOString() : undefined,\n      }\n    }\n  }))\n})\n```\n\n### Step 5 — Verify your DID is resolvable\n\nThe 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:\n\n- The holder's DID resolves to a valid DID Document\n- The DID Document contains the public key referenced in `proof.verificationMethod`\n- The `proof.challenge` and `proof.domain` match what the site sent\n\n## Supported Protocols\n\n<table>\n<tr>\n<th width=\"160\">Protocol</th>\n<th>Description</th>\n</tr>\n<tr>\n<td><code>chapi</code></td>\n<td>W3C Credential Handler API — <code>navigator.credentials.get()</code></td>\n</tr>\n<tr>\n<td><code>didcomm-v2</code></td>\n<td>DIDComm v2 Present Proof 3.0</td>\n</tr>\n<tr>\n<td><code>oid4vp</code></td>\n<td>OpenID for Verifiable Presentations</td>\n</tr>\n<tr>\n<td><code>waci-didcomm</code></td>\n<td>Wallet And Credential Interaction via DIDComm</td>\n</tr>\n</table>\n\n## Ecosystem\n\nRelated repos in the Attestto ecosystem:\n\n| Package | Purpose | Repo |\n|---|---|---|\n| **@attestto/wallet-identity-resolver** | Given a wallet address, resolve all DIDs, credentials, SBTs attached to it | [GitHub](https://github.com/Attestto-com/wallet-identity-resolver) |\n| **@attestto/verify** | Web Components for wallet discovery, signing, and VP verification | [GitHub](https://github.com/Attestto-com/verify) |\n| **@attestto/vc-sdk** | Issue and verify W3C Verifiable Credentials | [GitHub](https://github.com/Attestto-com/vc-sdk) |\n| **did-sns-spec** | `did:sns` DID method spec — Solana domain to DID resolution | [GitHub](https://github.com/Attestto-com/did-sns-spec) |\n| **vLEI-Solana-Bridge** | Write and verify vLEI attestations from GLEIF on Solana | [GitHub](https://github.com/Attestto-com/vLEI-Solana-Bridge) |\n\n## Security\n\nSee [SECURITY.md](SECURITY.md) for:\n\n- **Wallet discovery spoofing** — discovery is untrusted metadata; trust is established via VP verification\n- **Trusted wallet allowlist** — restrict which wallet DIDs your app accepts\n- **Cross-origin considerations** — CORS for DID resolution and revocation checks\n- **API key exposure** — always use a backend proxy for resolver calls\n- **Trust chain** — the DID method spec defines where to resolve, not the VC\n\n## Trust model (for site developers)\n\nThis package implements a **bilateral trust model**. Each side declares what it accepts; neither side is asked to trust the other implicitly.\n\n### Site → wallet: declare which issuers you accept\n\nPass `trustedIssuers: string[]` on any `requestSignature` or `requestAuth` call:\n\n```ts\nawait requestAuth(wallet, {\n  nonce, audience, origin,\n  trustedIssuers: [\n    'did:sns:attestto.attestto.sol',  // any identity issued by Attestto root\n    'did:web:your-tenant.example',    // OR identities issued by your own tenant\n  ],\n})\n```\n\nThe wallet uses this to filter and highlight matching identities in the consent popup. If the user has multiple identities, they default-see the one your site will actually accept — no \"user signs, server rejects, user retries\" loop.\n\n**Empty / omitted** → the wallet shows all the user's identities; you accept whatever they return (verify on the backend).\n\n### Wallet → site: per-origin consent and identity memory\n\nThese are wallet-side properties — your site doesn't configure them, but you should know they exist so you understand the UX:\n\n- **First-sight consent** — the first time your origin requests a sign-in or pushes an identity-sync payload, the wallet asks the user to approve your origin. Subsequent requests from the same origin proceed without re-prompting. This means a site that's never been visited cannot silently get an identity signature.\n- **Per-origin identity preference** — when the user picks an identity for your origin, the wallet remembers it. Next time you call `requestAuth`, the wallet default-selects the same identity. The user can override per-call.\n- **User-visible revocation** — the user can revoke your origin's trust (and forget the preference) from the wallet's Settings → Trusted Sites pane.\n\n### Backend verification responsibility\n\nThe adapter never trusts a `requestAuth` / `requestSignature` response on its own. Always verify on your backend:\n\n1. The returned `signature` is valid over `nonce + audience + origin` using the `publicKeyJwk` from the response.\n2. The `publicKeyJwk` matches a verification method published in the holder DID's DID Document (resolve via your resolver).\n3. If you set `trustedIssuers`: an issuer in your list actually attested the returned `did` (look for a corresponding VC chain, OR trust the wallet's filtering and skip — depending on your assurance level).\n\nThe `nonce` must be single-use and bound to the user's session. Replay protection is the verifier's job, not the wallet's.\n\n## Build with an LLM\n\nThis repo ships a [`llms.txt`](./llms.txt) context file — a machine-readable summary of the API, data structures, and integration patterns designed to be read by AI coding assistants.\n\n### Recommended setup\n\nUse the [`attestto-dev-mcp`](../attestto-dev-mcp) server to give your LLM active access to the ecosystem:\n\n```bash\ncd ../attestto-dev-mcp\nnpm install && npm run build\n```\n\nThen add it to your Claude / Cursor / Windsurf config and ask:\n\n> *\"Explore the Attestto ecosystem and scaffold me an on-chain identity resolver\"*\n\n### Which model?\n\nWe recommend **[Claude](https://claude.ai) Pro** (5× usage vs free) or higher. Long context and strong TypeScript reasoning handle this codebase well. The MCP server works with any LLM that supports tool use.\n\n> **Quick start:** Ask your LLM to read `llms.txt` in this repo, then describe what you want to build. It will find the right archetype, generate boilerplate, and walk you through the first run.\n\n## Roadmap\n\n### v0.3.0 — Protocol negotiation\n\nMulti-protocol wallets declare `protocols: ['oid4vp', 'chapi', 'didcomm-v2']`. The site needs a standard way to pick the best mutual protocol and act on it.\n\n```ts\nimport { pickWallet, negotiateProtocol } from '@attestto/id-wallet-adapter'\n\nconst wallet = await pickWallet()\nconst protocol = negotiateProtocol(wallet, ['oid4vp', 'chapi'])\n// Returns 'oid4vp' if wallet supports it, falls back to 'chapi', or null\n```\n\nInspired by [Aries RFC 0031 Discover Features](https://identity.foundation/aries-rfcs/latest/features/0031-discover-features/) — simplified for browser context.\n\n### v0.4.0 — Protocol execution\n\n`pickWallet()` returns a `ConnectedWallet` with protocol-specific request methods:\n\n```ts\nconst wallet = await pickWallet()\nconst vp = await wallet.request('oid4vp', { presentationDefinition })\n// or\nconst vp = await wallet.request('chapi', { query, challenge, domain })\n```\n\nOne return object, multiple wire protocols. The wallet adapter becomes the unified interface between the site and whatever protocol the wallet speaks.\n\n## See It In Action\n\n| Demo | What | Link |\n|------|------|------|\n| **Verify & Sign** | Live playground with the actual components — drop a PDF, sign it | [verify.attestto.com/docs](https://verify.attestto.com/docs) |\n| **@attestto/verify** | Web Components that use this adapter for wallet discovery + signing | [GitHub](https://github.com/attestto/verify) |\n| **DID Landscape Explorer** | Self-assessment wizard with wallet picker and CHAPI flow | [GitHub](https://github.com/chongkan/did-landscape-explorer) |\n\n**Debug logging:** Open the console on any page using this adapter and run `Attestto.debug = true` to see the full discovery and signing flow with numbered steps.\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md).\n\n## License\n\n[Apache 2.0](./LICENSE) — see also [`NOTICE`](./NOTICE).\n\nThis package ships with Apache 2.0's explicit patent grant (§3): every contributor grants a perpetual, worldwide, royalty-free, irrevocable patent license for their contributions, plus the §3 retaliation clause that terminates that license for anyone who weaponizes patents against the project. We chose Apache 2.0 specifically because protocol-shaped infrastructure deserves a license that says something explicit about patents. MIT does not.\n","readmeFilename":"README.md"}