@thurinlabs/identity-kit 0.7.5 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # @thurinlabs/identity-kit
2
2
 
3
- The shared library for Thurin identity — the single source of truth for looking up and **verifying** on-chain identity claims, PGP key proofs, social proofs, and EFP social graph data. It powers both the [Scry](https://thurin.id) explorer and the embeddable `ScryCard`, so a "verified" result means the same thing everywhere.
3
+ The shared library for Thurin identity — the single source of truth for looking up and **verifying** on-chain identity claims, PGP key proofs, social proofs, and EFP social graph data. It powers both the [thurin.id](https://thurin.id) explorer and the embeddable `ThurinCard`, so a "verified" result means the same thing everywhere.
4
4
 
5
5
  Three layers — use whichever fits:
6
6
 
7
7
  - **Core** — framework-agnostic functions (verify proofs, parse PGP keys, fetch EFP/claims). No React required.
8
8
  - **Hooks** — thin React wrappers around the core.
9
- - **ScryCard** — a drop-in identity card UI built on the hooks.
9
+ - **ThurinCard** — a drop-in identity card UI built on the hooks.
10
10
 
11
11
  A [Thurin Labs](https://thurin.id) project.
12
12
 
@@ -21,25 +21,25 @@ Peer dependencies: `react`, `react-dom`, `wagmi`, `viem`, `@tanstack/react-query
21
21
  ## Quick Start
22
22
 
23
23
  ```tsx
24
- import { IdentityKitProvider, ScryCard } from '@thurinlabs/identity-kit'
24
+ import { IdentityKitProvider, ThurinCard } from '@thurinlabs/identity-kit'
25
25
  import '@thurinlabs/identity-kit/styles'
26
26
 
27
27
  function App() {
28
28
  return (
29
29
  <IdentityKitProvider>
30
- <ScryCard ens="vitalik.eth" theme="thurin" />
30
+ <ThurinCard ens="vitalik.eth" theme="thurin" />
31
31
  </IdentityKitProvider>
32
32
  )
33
33
  }
34
34
  ```
35
35
 
36
- ## ScryCard
36
+ ## ThurinCard
37
37
 
38
38
  A self-contained identity card that fetches and displays all available identity data.
39
39
 
40
40
  ```tsx
41
- <ScryCard ens="vitalik.eth" theme="thurin" />
42
- <ScryCard address="0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" theme="dark" />
41
+ <ThurinCard ens="vitalik.eth" theme="thurin" />
42
+ <ThurinCard address="0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" theme="dark" />
43
43
  ```
44
44
 
45
45
  **Props:**
@@ -50,7 +50,7 @@ A self-contained identity card that fetches and displays all available identity
50
50
  | `address` | `string` | — | ETH address to look up |
51
51
  | `theme` | `'thurin' \| 'dark' \| 'light'` | `'thurin'` | Visual theme |
52
52
 
53
- **Displays:** ENS avatar, name, address, Signet seal count, verified proof count, EFP follower count, proof provider badges, and a link to the full [Scry](https://thurin.id) profile.
53
+ **Displays:** ENS avatar, name, address, on-chain attestation count, verified proof count, EFP follower count, proof provider badges, and a link to the full [thurin.id](https://thurin.id) profile.
54
54
 
55
55
  ## Provider
56
56
 
@@ -59,48 +59,51 @@ Wrap your app (or just the part using identity-kit) in `IdentityKitProvider`. If
59
59
  ```tsx
60
60
  // Zero config — uses public RPC, no Farcaster verification
61
61
  <IdentityKitProvider>
62
- <ScryCard ens="vitalik.eth" />
62
+ <ThurinCard ens="vitalik.eth" />
63
63
  </IdentityKitProvider>
64
64
 
65
65
  // With options
66
66
  <IdentityKitProvider
67
67
  rpcUrl="https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY"
68
68
  neynarApiKey="YOUR_NEYNAR_KEY"
69
- scryBaseUrl="https://thurin.id"
69
+ baseUrl="https://thurin.id"
70
70
  >
71
- <ScryCard ens="vitalik.eth" />
71
+ <ThurinCard ens="vitalik.eth" />
72
72
  </IdentityKitProvider>
73
73
  ```
74
74
 
75
75
  | Prop | Type | Default | Description |
76
76
  |------|------|---------|-------------|
77
- | `rpcUrl` | `string` | publicnode | Ethereum RPC endpoint |
77
+ | `rpcUrl` | `string` | publicnode | Ethereum RPC endpoint. Any RPC works — v2 needs only `eth_call` |
78
78
  | `neynarApiKey` | `string` | — | Neynar API key for Farcaster proof verification |
79
- | `scryBaseUrl` | `string` | `https://thurin.id` | Base URL for "View on Scry" links |
79
+ | `baseUrl` | `string` | `https://thurin.id` | Base URL for "View on Thurin" links |
80
+ | `network` | `'mainnet' \| 'sepolia' \| 'local'` | `'mainnet'` | Which chain to read the PGPRegistry on (`local` = a running anvil) |
81
+ | `registryAddress` | `string` | v2 address | Override the registry contract address |
80
82
 
81
83
  ## Hooks
82
84
 
83
- For custom UI, use the hooks directly instead of `ScryCard`.
85
+ For custom UI, use the hooks directly instead of `ThurinCard`.
84
86
 
85
- ### useScryIdentity
87
+ ### useThurinIdentity
86
88
 
87
- Combined identity data — ENS, Signet claims, PGP proofs, and EFP social graph.
89
+ Combined identity data — ENS, on-chain attestations, PGP proofs, and EFP social graph.
88
90
 
89
91
  ```tsx
90
- const identity = useScryIdentity('vitalik.eth')
92
+ const identity = useThurinIdentity('vitalik.eth')
91
93
  // or
92
- const identity = useScryIdentity('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045')
94
+ const identity = useThurinIdentity('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045')
93
95
  ```
94
96
 
95
- Returns: `ScryIdentity` with `address`, `ensName`, `ensAvatar`, `claims`, `totalClaims`, `activeClaims`, `currentFingerprint`, `pgpKeyInfo`, `proofs`, `efp`, `isLoading`, `error`.
97
+ Returns: `ThurinIdentity` with `address`, `ensName`, `ensAvatar`, `claims`, `totalClaims`, `activeClaims`, `currentFingerprint`, `pgpKeyInfo`, `proofs`, `efp`, `isLoading`, `error`.
96
98
 
97
- ### useSignetClaims
99
+ ### useAttestations
98
100
 
99
- On-chain attestation data from the PGPRegistry contract.
101
+ On-chain attestation data from the PGPRegistry v2 contract — the owner's history plus the stored signature and key for each claim, read with plain contract calls (no event logs), each verified off-chain.
100
102
 
101
103
  ```tsx
102
104
  const { claims, totalClaims, activeClaims, currentFingerprint, isLoading } =
103
- useSignetClaims('0xd8dA...')
105
+ useAttestations('0xd8dA...')
106
+ // claims[].{ index, fingerprint, createdAt, revoked, revokedAt, messageVersion, pgpSignature, pgpPublicKey, verification }
104
107
  ```
105
108
 
106
109
  ### useEFPGraph
@@ -114,16 +117,18 @@ const { efp, isLoading } = useEFPGraph('0xd8dA...')
114
117
 
115
118
  ### usePGPProofs
116
119
 
117
- PGP key info and verified social proofs from keyserver.
120
+ PGP key info and verified social proofs, read from the key stored in the identity's on-chain attestation. No keyserver is consulted.
118
121
 
119
122
  ```tsx
120
- const { keyInfo, proofs, isLoading } = usePGPProofs('03E53D807CE38C...')
123
+ const { keyInfo, proofs, isLoading } = usePGPProofs(fingerprint, attestation.pgpPublicKey)
121
124
  // proofs[].provider, proofs[].status, proofs[].displayUrl
122
125
  ```
123
126
 
127
+ `useThurinIdentity` wires this up for you from the current attestation.
128
+
124
129
  ## Core utilities (no React)
125
130
 
126
- The verification and data logic is exported as plain functions — no React, no provider. This is the layer the Scry explorer and the hooks both build on; use it directly when you need the validated data behind your own UI.
131
+ The verification and data logic is exported as plain functions — no React, no provider. This is the layer the thurin.id explorer and the hooks both build on; use it directly when you need the validated data behind your own UI.
127
132
 
128
133
  ### Proofs
129
134
 
@@ -142,7 +147,7 @@ const result = await verifyProof(proof, fingerprint, neynarApiKey /* only needed
142
147
  ### PGP
143
148
 
144
149
  ```ts
145
- import { parsePgpKey, verifyAttestation, fetchKeyByFingerprint, fetchKeyByKeyId } from '@thurinlabs/identity-kit'
150
+ import { parsePgpKey, verifyAttestation, stripEmailUserIDs, hasEmailUserID } from '@thurinlabs/identity-kit'
146
151
 
147
152
  const keyInfo = await parsePgpKey(armoredKey)
148
153
  // → { fingerprint, userIDs, algorithm, created, expires, notations, subkeys } | null
@@ -150,9 +155,17 @@ const keyInfo = await parsePgpKey(armoredKey)
150
155
  const verification = await verifyAttestation({ pgpPublicKey, pgpSignature, fingerprint, ethAddress })
151
156
  // → { verified: boolean, reason?: string }
152
157
 
153
- const armored = await fetchKeyByFingerprint(fingerprint) // from keys.openpgp.org
158
+ // Prepare a key for publishing: drop every user ID that contains an email address.
159
+ const stripped = await stripEmailUserIDs(armoredKey)
160
+ // → { armored, kept: ['thurin'], removed: ['Alice <alice@example.com>'] } | null (null = nothing would remain)
161
+
162
+ await hasEmailUserID(armoredKey) // → true if any user ID contains an @
154
163
  ```
155
164
 
165
+ **Published identity.** An attestation stores the armored key on-chain, permanently and publicly. Since 0.9.0 the intended shape is a key whose only user ID is a non-email one (any name — `thurin` is the suggestion), carrying the `proof@thurin.id` notations. `stripEmailUserIDs` produces that from a normal export; the stripped key still verifies (`verifyAttestation` needs at least one self-certified user ID, so a key with none is rejected) and keeps the notations on the user ID it retains. Proofs are then read from the on-chain key, never from a keyserver.
166
+
167
+ `fetchKeyByFingerprint` / `fetchKeyByKeyId` (keys.openpgp.org) remain exported for key-ID → fingerprint resolution, but note that keyserver serves unverified-email keys as bare packets and drops non-email user IDs, so it cannot supply a published identity.
168
+
156
169
  ### EFP & claims
157
170
 
158
171
  ```ts
@@ -162,19 +175,52 @@ const graph = await fetchEFPGraph(address)
162
175
  // → { followers, following, top8: string[], hasEfp } | null
163
176
  ```
164
177
 
165
- ### Contract constants
178
+ ### Contract
179
+
180
+ ```ts
181
+ import { REGISTRY_ADDRESS, REGISTRY_ABI, NETWORKS, getRegistry } from '@thurinlabs/identity-kit'
182
+
183
+ getRegistry('sepolia') // → { chainId: 11155111, address, deployBlock, explorerUrl, defaultRpcUrl }
184
+ ```
185
+
186
+ `REGISTRY_ABI` is the complete v2 ABI (reads and writes), so apps that publish claims use the same one. The v2 registry is deployed with CREATE2 and has the same address on every network.
187
+
188
+ ### Fingerprints and key IDs
189
+
190
+ The v2 registry takes raw fingerprint bytes and indexes by their hash and by long key ID:
166
191
 
167
192
  ```ts
168
- import { REGISTRY_ADDRESS, REGISTRY_ABI, CONTRACT_DEPLOY_BLOCK } from '@thurinlabs/identity-kit'
193
+ import { fingerprintToBytes, bytesToFingerprint, fingerprintHash, keyIdOf, keyIdToBytes } from '@thurinlabs/identity-kit'
194
+
195
+ fingerprintToBytes('6E00 5391 … 7FE7') // → '0x6e0053911942a889426c1866e34d9266098f7fe7' (attest / reattest arg)
196
+ bytesToFingerprint('0x6e00…7fe7') // → '6e0053911942a889426c1866e34d9266098f7fe7'
197
+ fingerprintHash(fp) // → keccak256 of the raw bytes (addressesFor arg)
198
+ keyIdOf(fp) // → '0xe34d9266098f7fe7' (fingerprintsForKeyId arg; v4 = last 8 bytes, v6 = first 8, per RFC 9580)
169
199
  ```
170
200
 
171
- Note `REGISTRY_ABI` here is read-only (events + `attestationCount` + `getAttestation`). Apps that write claims (the Signet flow) need their own ABI with the `attest`/`revoke` functions.
201
+ ### Authorized writes (EIP-712)
202
+
203
+ Every write has a `…For` twin that anyone can submit with the owner's signature — for cold wallets, a CLI, or a sponsor. The helpers build exactly the typed data the contract verifies:
204
+
205
+ ```ts
206
+ import { attestTypedData, authorizationDigest } from '@thurinlabs/identity-kit'
207
+ import { signTypedData } from '@wagmi/core'
208
+
209
+ const nonce = await readContract({ ..., functionName: 'nonces', args: [owner] })
210
+ const typedData = attestTypedData(chainId, registryAddress, {
211
+ owner, fingerprint, pgpSignature, pgpPublicKey, nonce, deadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
212
+ })
213
+ const signature = await signTypedData(config, typedData)
214
+ // anyone: attestFor(owner, fingerprintBytes, sigBytes, keyBytes, deadline, signature)
215
+ ```
216
+
217
+ Also `reattestTypedData`, `updateKeyTypedData`, `revokeTypedData`, `setRecordTypedData`, and `recordKind(name)` for the `bytes32 kind` of a record.
172
218
 
173
219
  ## Themes
174
220
 
175
- Three built-in themes: `thurin`, `dark`, `light`. All styles are scoped under `[data-scry-theme]` with `scry-` prefixed class names to avoid conflicts with your app's styles.
221
+ Three built-in themes: `thurin`, `dark`, `light`. All styles are scoped under `[data-thurin-theme]` with `thurin-` prefixed class names to avoid conflicts with your app's styles.
176
222
 
177
- Import styles when using `ScryCard`:
223
+ Import styles when using `ThurinCard`:
178
224
 
179
225
  ```tsx
180
226
  import '@thurinlabs/identity-kit/styles'
@@ -188,22 +234,25 @@ For static sites, Jekyll blogs, WordPress, or any HTML page — use the standalo
188
234
 
189
235
  ```html
190
236
  <div
191
- data-scry-card="bendoubleu.eth"
237
+ data-thurin-card="bendoubleu.eth"
192
238
  data-theme="thurin"
193
239
  data-rpc-url="https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY"
194
240
  ></div>
195
241
 
196
- <script src="https://cdn.jsdelivr.net/npm/@thurinlabs/identity-kit/dist/embed.global.js"></script>
242
+ <script src="https://cdn.jsdelivr.net/npm/@thurinlabs/identity-kit@0/dist/embed.global.js"></script>
197
243
  ```
198
244
 
199
245
  | Attribute | Description |
200
246
  |-----------|-------------|
201
- | `data-scry-card` | ENS name or ETH address to look up (required) |
202
- | `data-theme` | `thurin`, `dark`, or `light` (default: `thurin`) |
203
- | `data-rpc-url` | An Ethereum RPC that supports `eth_getLogs` — required to verify on-chain claims. The card reads the chain directly, so use your own node or any provider. (Public fallback RPCs throttle `getLogs`.) |
247
+ | `data-thurin-card` | ENS name or ETH address to look up (required) |
248
+ | `data-theme` | `thurin`, `dark`, or `light` (default: `thurin`). Change it after render and the card follows, so a page with a theme switch can keep the card in step. |
249
+ | `data-rpc-url` | Optional. Any Ethereum RPC; the card reads the v2 registry with plain calls, so the keyless public default works. |
204
250
  | `data-neynar-key` | Optional. A Neynar API key, only to verify Farcaster proofs. Without it, Farcaster shows as unverified. |
251
+ | `data-base-url` | Optional. Where the card's "View on Thurin" link points (default `https://thurin.id`). A page served from ENS can pass its own name so the link stays on ENS. |
252
+ | `data-network` | Optional. `sepolia` or `local` instead of mainnet. |
253
+ | `data-registry-address` | Optional. Override the registry contract address. |
205
254
 
206
- The card talks directly to Ethereum, keys.openpgp.org, and each proof platform — no intermediary. Cards render automatically on page load and for dynamically added elements.
255
+ The card talks directly to Ethereum and each proof platform — no intermediary, no keyserver. Cards render automatically on page load and for dynamically added elements.
207
256
 
208
257
  ## Supported Proof Providers
209
258
 
@@ -215,6 +264,47 @@ The card talks directly to Ethereum, keys.openpgp.org, and each proof platform
215
264
  | Codeberg | Repository description |
216
265
  | Mastodon | Profile metadata |
217
266
 
267
+ ## Migrating from 0.9.x
268
+
269
+ 1.0.0 reads the **PGPRegistry v2** contract. The v1 registry is no longer read.
270
+
271
+ | 0.9.x | 1.0.0 |
272
+ |-------|-------|
273
+ | `REGISTRY_ABI` (v1, reads only) | v2 ABI, reads + writes |
274
+ | `CONTRACT_DEPLOY_BLOCK` | removed (no log scans) |
275
+ | `Attestation.txHash` | removed; `revokedAt` and `messageVersion` added |
276
+ | `rpcUrl` needed `eth_getLogs` | any RPC |
277
+ | — | `local` network, `registryAddress` prop / `data-registry-address` |
278
+ | — | fingerprint helpers, EIP-712 authorization helpers |
279
+
280
+ ## Migrating from 0.8.x
281
+
282
+ 0.9.0 moves proofs to the on-chain key and adds key-preparation helpers.
283
+
284
+ | 0.8.x | 0.9.0 |
285
+ |-------|-------|
286
+ | `usePGPProofs(fingerprint)` — fetched the key from keys.openpgp.org | `usePGPProofs(fingerprint, armoredKey)` — parses the supplied (on-chain) key |
287
+ | — | `stripEmailUserIDs(armoredKey)`, `hasEmailUserID(armoredKey)` |
288
+ | mainnet only | `network` prop / `data-network` attribute; `NETWORKS`, `getRegistry()` |
289
+
290
+ `useThurinIdentity`, `ThurinCard`, and the embed need no changes; they pass the attestation's key through automatically.
291
+
292
+ ## Migrating from 0.7.x
293
+
294
+ 0.8.0 collapses the Scry / Signet sub-brands into Thurin. Renames only — no behaviour changed, and existing on-chain attestations verify exactly as before.
295
+
296
+ | 0.7.x | 0.8.0 |
297
+ |-------|-------|
298
+ | `ScryCard` / `ScryCardProps` | `ThurinCard` / `ThurinCardProps` |
299
+ | `useScryIdentity` | `useThurinIdentity` |
300
+ | `useSignetClaims` | `useAttestations` |
301
+ | `ScryIdentity` (type) | `ThurinIdentity` |
302
+ | `SignetClaim` (type) | `Attestation` |
303
+ | `scryBaseUrl` (provider prop) | `baseUrl` |
304
+ | `data-scry-card` (embed) | `data-thurin-card` |
305
+ | `[data-scry-theme]`, `.scry-*`, `--scry-*` | `[data-thurin-theme]`, `.thurin-*`, `--thurin-*` |
306
+ | `ScryEmbed` (IIFE global) | `ThurinEmbed` |
307
+
218
308
  ## Development
219
309
 
220
310
  ```bash
@@ -225,8 +315,8 @@ npm test
225
315
 
226
316
  ## Links
227
317
 
228
- - [Scry](https://thurin.id) — Identity explorer
229
- - [Signet](https://thurin.id/signet) — Create identity claims
318
+ - [Thurin](https://thurin.id) — Identity explorer
319
+ - [Attest](https://thurin.id/attest) — Create identity claims
230
320
  - [Documentation](https://docs.thurin.id)
231
321
  - [GitHub](https://github.com/thurinlabs/identity-kit)
232
322
  - [Codeberg](https://codeberg.org/thurinlabs/identity-kit)