@thurinlabs/identity-kit 1.3.5 → 1.3.7

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
@@ -57,15 +57,15 @@ A self-contained identity card that fetches and displays all available identity
57
57
  Wrap your app (or just the part using identity-kit) in `IdentityKitProvider`. If you already have a `WagmiProvider`, the SDK detects it and uses your existing config.
58
58
 
59
59
  ```tsx
60
- // Zero config — uses public RPC, no Farcaster verification
60
+ // Zero config — public RPC and a public Farcaster node, no keys
61
61
  <IdentityKitProvider>
62
62
  <ThurinCard ens="vitalik.eth" />
63
63
  </IdentityKitProvider>
64
64
 
65
65
  // With options
66
66
  <IdentityKitProvider
67
- rpcUrl="https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY"
68
- neynarApiKey="YOUR_NEYNAR_KEY"
67
+ rpcUrl="https://your-node.example"
68
+ farcasterHub="https://your-farcaster-node.example"
69
69
  baseUrl="https://thurin.id"
70
70
  >
71
71
  <ThurinCard ens="vitalik.eth" />
@@ -75,7 +75,8 @@ Wrap your app (or just the part using identity-kit) in `IdentityKitProvider`. If
75
75
  | Prop | Type | Default | Description |
76
76
  |------|------|---------|-------------|
77
77
  | `rpcUrl` | `string` | publicnode | Ethereum RPC endpoint. Any RPC works — v2 needs only `eth_call` |
78
- | `neynarApiKey` | `string` | — | Neynar API key for Farcaster proof verification |
78
+ | `farcasterHub` | `string` | Hypersnap public node | A Farcaster node's HTTP API for Farcaster proofs. The default, Quilibrium's `haatz.quilibrium.com`, needs no key; it sees the visitor's IP and which account was checked (1.3.7) |
79
+ | `neynarApiKey` | `string` | — | Optional: read Farcaster through Neynar's hub instead. Not needed since 1.3.7 |
79
80
  | `baseUrl` | `string` | `https://thurin.id` | Base URL for "View on Thurin" links |
80
81
  | `network` | `'mainnet' \| 'sepolia' \| 'local'` | `'mainnet'` | Which chain to read the PGPRegistry on (`local` = a running anvil) |
81
82
  | `registryAddress` | `string` | v2 address | Override the registry contract address |
@@ -98,7 +99,7 @@ Returns: `ThurinIdentity` with `address`, `ensName`, `ensAvatar`, `claims`, `tot
98
99
 
99
100
  When the identity can't be shown, `error` is a plain `Error` safe to display and `errorKind` says why: `'rpc'` (the RPC didn't answer, so nothing is known), `'not-found'` (the ENS name has no address), or `'read'` (the RPC answers but a registry read failed); `retry()` re-runs the lookups. `ThurinCard` shows these instead of zeros, and a follower count EFP didn't answer for shows `–` (1.3.4).
100
101
 
101
- `ensAvatar` is set only when loading it can't reveal the viewer to the name's owner: IPFS, Arweave, inline data, a content-addressed NFT, or `euc.li` (the ENS app's upload host). A plain `https://` avatar on the owner's own server is left out, since loading it would hand that server every viewer's IP (1.3.3; earlier versions loaded any avatar). The rule is `avatarUrl()` in the core; `useSafeAvatar(name, chainId)` is the hook.
102
+ `ensAvatar` is set only when loading it can't reveal the viewer to the name's owner: IPFS, Arweave, inline data, a content-addressed NFT, or `euc.li` (the ENS app's upload host). IPFS images load through `ipfs.filebase.io`, falling back to Pinata's public gateway if that fails (`IPFS_GATEWAYS`, `avatarFallbacks()`; 1.3.6). A plain `https://` avatar on the owner's own server is left out, since loading it would hand that server every viewer's IP (1.3.3; earlier versions loaded any avatar). The rule is `avatarUrl()` in the core; `useSafeAvatar(name, chainId)` is the hook.
102
103
 
103
104
  ### useAttestations
104
105
 
@@ -169,7 +170,7 @@ import { identifyProof, verifyProof, displayUrl, proofHref, proofSecondaryHref }
169
170
  const proof = identifyProof({ name: 'proof@thurin.id', value: 'https://gist.github.com/alice/abc123' })
170
171
  // → { provider: 'github', label: 'GitHub', user: 'alice', gistId: 'abc123', url }
171
172
 
172
- const result = await verifyProof(proof, fingerprint, neynarApiKey /* only needed for Farcaster */)
173
+ const result = await verifyProof(proof, fingerprint) // options: { farcasterHub?, neynarApiKey? }
173
174
  // → { verified: boolean, reason?: string }
174
175
  ```
175
176
 
@@ -244,7 +245,7 @@ const resolver = await publicClient.getEnsResolver({ name: call.name })
244
245
  ```ts
245
246
  import { avatarUrl, parseNftAvatar, nftAvatarImage } from '@thurinlabs/identity-kit/core'
246
247
 
247
- avatarUrl('ipfs://Qm…') // 'https://ipfs.io/ipfs/Qm…'
248
+ avatarUrl('ipfs://Qm…') // 'https://ipfs.filebase.io/ipfs/Qm…' (avatarFallbacks() → Pinata)
248
249
  avatarUrl('https://euc.li/vitalik.eth') // allowed: ENS Labs' host, not the owner's
249
250
  avatarUrl('https://example.com/me.png') // null: the owner's server would see every viewer
250
251
  ```
@@ -300,7 +301,7 @@ For static sites, Jekyll blogs, WordPress, or any HTML page — use the standalo
300
301
  <div
301
302
  data-thurin-card="bendoubleu.eth"
302
303
  data-theme="thurin"
303
- data-rpc-url="https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY"
304
+ data-rpc-url="https://your-node.example"
304
305
  ></div>
305
306
 
306
307
  <script src="https://cdn.jsdelivr.net/npm/@thurinlabs/identity-kit@0/dist/embed.global.js"></script>
@@ -311,7 +312,8 @@ For static sites, Jekyll blogs, WordPress, or any HTML page — use the standalo
311
312
  | `data-thurin-card` | ENS name or ETH address to look up (required) |
312
313
  | `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. |
313
314
  | `data-rpc-url` | Optional. Any Ethereum RPC; the card reads the v2 registry with plain calls, so the keyless public default works. |
314
- | `data-neynar-key` | Optional. A Neynar API key, only to verify Farcaster proofs. Without it, Farcaster shows as unverified. |
315
+ | `data-farcaster-hub` | Optional. A Farcaster node for Farcaster proofs; the default is a public node that needs no key. |
316
+ | `data-neynar-key` | Optional. Read Farcaster through Neynar with your key instead (not needed since 1.3.7). |
315
317
  | `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. |
316
318
  | `data-network` | Optional. `sepolia` or `local` instead of mainnet. |
317
319
  | `data-registry-address` | Optional. Override the registry contract address. |
@@ -324,7 +326,7 @@ The card talks directly to Ethereum and each proof platform — no intermediary,
324
326
  |----------|-------------|
325
327
  | GitHub | Public gist, or a repository description (the form an organisation can use) |
326
328
  | DNS | TXT record |
327
- | Farcaster | Public cast (requires Neynar API key) |
329
+ | Farcaster | Public cast (read from a public Farcaster node; no key) |
328
330
  | Codeberg | Repository description |
329
331
  | Mastodon | Profile metadata |
330
332
 
@@ -1627,29 +1627,27 @@ async function verifyDNS(proof, fingerprint) {
1627
1627
  return { verified: false, reason: `DNS fetch failed: ${err.message}` };
1628
1628
  }
1629
1629
  }
1630
+ var FARCASTER_HUB = "https://haatz.quilibrium.com";
1630
1631
  var NEYNAR_HUB = "https://hub-api.neynar.com";
1631
- async function resolveFid(username, apiKey) {
1632
- const resp = await fetch(
1633
- `${NEYNAR_HUB}/v1/userNameProofByName?name=${encodeURIComponent(username)}`,
1634
- { headers: { "x-api-key": apiKey } }
1635
- );
1636
- if (!resp.ok) return null;
1637
- const data = await resp.json();
1638
- return _nullishCoalesce(data.fid, () => ( null));
1632
+ function farcasterSource(opts) {
1633
+ if (opts.farcasterHub) return { base: opts.farcasterHub.replace(/\/+$/, ""), headers: {} };
1634
+ if (opts.neynarApiKey) return { base: NEYNAR_HUB, headers: { "x-api-key": opts.neynarApiKey } };
1635
+ return { base: FARCASTER_HUB, headers: {} };
1639
1636
  }
1640
- async function verifyFarcaster(proof, fingerprint, neynarApiKey) {
1641
- if (!neynarApiKey) {
1642
- return { verified: false, reason: "Farcaster verification requires a Neynar API key" };
1643
- }
1637
+ async function verifyFarcaster(proof, fingerprint, opts = {}) {
1638
+ const { base, headers } = farcasterSource(opts);
1639
+ const host = base.replace(/^https?:\/\//, "");
1644
1640
  try {
1645
- const fid = await resolveFid(proof.user, neynarApiKey);
1641
+ const nameResp = await fetch(`${base}/v1/userNameProofByName?name=${encodeURIComponent(proof.user)}`, { headers });
1642
+ if (nameResp.status === 404) return { verified: false, reason: `Could not resolve Farcaster user "${proof.user}"` };
1643
+ if (!nameResp.ok) return { verified: false, reason: `Couldn't check: the Farcaster node (${host}) returned ${nameResp.status}` };
1644
+ const fid = (await nameResp.json()).fid;
1646
1645
  if (!fid) return { verified: false, reason: `Could not resolve Farcaster user "${proof.user}"` };
1647
- const headers = { "x-api-key": neynarApiKey };
1648
1646
  let pageToken = "";
1649
1647
  for (let page = 0; page < 5; page++) {
1650
- const url = `${NEYNAR_HUB}/v1/castsByFid?fid=${fid}&pageSize=100&reverse=true${pageToken ? `&pageToken=${pageToken}` : ""}`;
1648
+ const url = `${base}/v1/castsByFid?fid=${fid}&pageSize=100&reverse=true${pageToken ? `&pageToken=${pageToken}` : ""}`;
1651
1649
  const resp = await fetch(url, { headers });
1652
- if (!resp.ok) return { verified: false, reason: `Farcaster Hub returned ${resp.status}` };
1650
+ if (!resp.ok) return { verified: false, reason: `Couldn't check: the Farcaster node (${host}) returned ${resp.status}` };
1653
1651
  const data = await resp.json();
1654
1652
  for (const msg of data.messages || []) {
1655
1653
  if (msg.hash && msg.hash.startsWith(proof.castHash)) {
@@ -1664,7 +1662,7 @@ async function verifyFarcaster(proof, fingerprint, neynarApiKey) {
1664
1662
  }
1665
1663
  return { verified: false, reason: "Cast not found" };
1666
1664
  } catch (err) {
1667
- return { verified: false, reason: `Farcaster fetch failed: ${err.message}` };
1665
+ return { verified: false, reason: `Couldn't check: the Farcaster node (${host}) didn't answer (${err.message})` };
1668
1666
  }
1669
1667
  }
1670
1668
  async function verifyCodeberg(proof, fingerprint) {
@@ -1717,14 +1715,15 @@ var verifiers = {
1717
1715
  codeberg: verifyCodeberg,
1718
1716
  mastodon: verifyMastodon
1719
1717
  };
1720
- async function verifyProof(proof, fingerprint, neynarApiKey) {
1718
+ async function verifyProof(proof, fingerprint, opts) {
1721
1719
  const fn = verifiers[proof.provider];
1722
1720
  if (!fn) return { verified: false, reason: "Unknown provider" };
1723
- return fn(proof, fingerprint, neynarApiKey);
1721
+ return fn(proof, fingerprint, typeof opts === "string" ? { neynarApiKey: opts } : _nullishCoalesce(opts, () => ( {})));
1724
1722
  }
1725
1723
 
1726
1724
  // src/core/avatar.ts
1727
- var IPFS_GATEWAY = "https://ipfs.io/ipfs/";
1725
+ var IPFS_GATEWAYS = ["https://ipfs.filebase.io/ipfs/", "https://gateway.pinata.cloud/ipfs/"];
1726
+ var IPFS_GATEWAY = IPFS_GATEWAYS[0];
1728
1727
  var ARWEAVE_GATEWAY = "https://arweave.net/";
1729
1728
  function avatarUrl(uri) {
1730
1729
  if (typeof uri !== "string") return null;
@@ -1739,9 +1738,14 @@ function avatarUrl(uri) {
1739
1738
  if (m) return IPFS_GATEWAY + m[1];
1740
1739
  return null;
1741
1740
  }
1741
+ function avatarFallbacks(url) {
1742
+ if (!url || !url.startsWith(IPFS_GATEWAYS[0])) return [];
1743
+ const path = url.slice(IPFS_GATEWAYS[0].length);
1744
+ return IPFS_GATEWAYS.slice(1).map((g) => g + path);
1745
+ }
1742
1746
  function parseNftAvatar(raw) {
1743
- const m = typeof raw === "string" && raw.trim().match(/^eip155:(\d+)\/(erc721|erc1155):(0x[0-9a-fA-F]{40})\/(\d+)$/);
1744
- return m ? { chainId: Number(m[1]), standard: m[2], contract: m[3], tokenId: BigInt(m[4]) } : null;
1747
+ const m = typeof raw === "string" && raw.trim().match(/^eip155:(\d+)\/(erc721|erc1155):(0x[0-9a-fA-F]{40})\/(\d+)$/i);
1748
+ return m ? { chainId: Number(m[1]), standard: m[2].toLowerCase(), contract: m[3], tokenId: BigInt(m[4]) } : null;
1745
1749
  }
1746
1750
  var NFT_AVATAR_ABI = [
1747
1751
  { type: "function", name: "tokenURI", stateMutability: "view", inputs: [{ type: "uint256" }], outputs: [{ type: "string" }] },
@@ -1755,9 +1759,17 @@ async function nftAvatarImage(tokenUri, tokenId, fetchFn = fetch) {
1755
1759
  else {
1756
1760
  const url = uri.startsWith("data:") ? null : avatarUrl(uri);
1757
1761
  if (!url) return null;
1758
- const res = await fetchFn(url);
1759
- if (!res.ok) return null;
1760
- meta = await res.json();
1762
+ for (const candidate of [url, ...avatarFallbacks(url)]) {
1763
+ try {
1764
+ const res = await fetchFn(candidate);
1765
+ if (res.ok) {
1766
+ meta = await res.json();
1767
+ break;
1768
+ }
1769
+ } catch (e8) {
1770
+ }
1771
+ }
1772
+ if (!meta) return null;
1761
1773
  }
1762
1774
  return avatarUrl(_nullishCoalesce(_optionalChain([meta, 'optionalAccess', _28 => _28.image]), () => ( _optionalChain([meta, 'optionalAccess', _29 => _29.image_url]))));
1763
1775
  }
@@ -1976,7 +1988,7 @@ async function parseRecord(kind, text, opts = {}) {
1976
1988
  const j = JSON.parse(t);
1977
1989
  if (_optionalChain([j, 'optionalAccess', _34 => _34.v]) !== 1 || typeof j.with !== "string" || !j.with) return invalid('Not a v1 affiliation: expected {"v":1,"with":"<address or name>"}');
1978
1990
  return { ...base, valid: true, data: { type: "affiliation", with: j.with, role: typeof j.role === "string" ? j.role : null } };
1979
- } catch (e8) {
1991
+ } catch (e9) {
1980
1992
  return invalid("Not JSON");
1981
1993
  }
1982
1994
  }
@@ -2001,7 +2013,7 @@ async function parseRecord(kind, text, opts = {}) {
2001
2013
  try {
2002
2014
  const { readMessage } = await Promise.resolve().then(() => _interopRequireWildcard(require("openpgp")));
2003
2015
  recipients = (await readMessage({ armoredMessage: t })).getEncryptionKeyIDs().length;
2004
- } catch (e9) {
2016
+ } catch (e10) {
2005
2017
  return invalid("PGP message does not parse");
2006
2018
  }
2007
2019
  return { ...base, valid: true, data: { type: "encrypted", recipients } };
@@ -2009,7 +2021,7 @@ async function parseRecord(kind, text, opts = {}) {
2009
2021
  case "thurin.pointer": {
2010
2022
  try {
2011
2023
  return { ...base, valid: true, data: { type: "pointer", releases: parsePointer(t).releases } };
2012
- } catch (e10) {
2024
+ } catch (e11) {
2013
2025
  return invalid("Not a v1 pointer record");
2014
2026
  }
2015
2027
  }
@@ -2088,5 +2100,8 @@ async function fetchRecords(client, registry, abi, owner, index, kinds = IDENTIT
2088
2100
 
2089
2101
 
2090
2102
 
2091
- exports.REGISTRY_ABI = REGISTRY_ABI; exports.REGISTRY_ADDRESS = REGISTRY_ADDRESS; exports.NETWORKS = NETWORKS; exports.isNetworkName = isNetworkName; exports.getRegistry = getRegistry; exports.chainFor = chainFor; exports.needsRpcProbe = needsRpcProbe; exports.identityErrorKind = identityErrorKind; exports.IDENTITY_ERROR_TEXT = IDENTITY_ERROR_TEXT; exports.normalizeFingerprint = normalizeFingerprint; exports.fingerprintToBytes = fingerprintToBytes; exports.bytesToFingerprint = bytesToFingerprint; exports.fingerprintHash = fingerprintHash; exports.keyIdOf = keyIdOf; exports.keyIdToBytes = keyIdToBytes; exports.parsePgpKey = parsePgpKey; exports.verifyClearsigned = verifyClearsigned; exports.verifyAttestation = verifyAttestation; exports.stripEmailUserIDs = stripEmailUserIDs; exports.hasEmailUserID = hasEmailUserID; exports.fetchEFPGraph = fetchEFPGraph; exports.identifyProof = identifyProof; exports.displayUrl = displayUrl; exports.proofHref = proofHref; exports.proofSecondaryHref = proofSecondaryHref; exports.verifyProof = verifyProof; exports.IPFS_GATEWAY = IPFS_GATEWAY; exports.ARWEAVE_GATEWAY = ARWEAVE_GATEWAY; exports.avatarUrl = avatarUrl; exports.parseNftAvatar = parseNftAvatar; exports.NFT_AVATAR_ABI = NFT_AVATAR_ABI; exports.nftAvatarImage = nftAvatarImage; exports.ENS_HINT_KEY = ENS_HINT_KEY; exports.ensHintFor = ensHintFor; exports.ensHintValue = ensHintValue; exports.ENS_TEXT_RESOLVER_ABI = ENS_TEXT_RESOLVER_ABI; exports.ensHintWrite = ensHintWrite; exports.fetchEnsHint = fetchEnsHint; exports.EIP712_NAME = EIP712_NAME; exports.EIP712_VERSION = EIP712_VERSION; exports.registryDomain = registryDomain; exports.AUTHORIZATION_TYPES = AUTHORIZATION_TYPES; exports.attestTypedData = attestTypedData; exports.reattestTypedData = reattestTypedData; exports.updateKeyTypedData = updateKeyTypedData; exports.revokeTypedData = revokeTypedData; exports.setRecordTypedData = setRecordTypedData; exports.authorizationDigest = authorizationDigest; exports.recordKind = recordKind; exports.MAX_RECORD_BYTES = MAX_RECORD_BYTES; exports.kindName = kindName; exports.KNOWN_KINDS = KNOWN_KINDS; exports.IDENTITY_KINDS = IDENTITY_KINDS; exports.encodeRecord = encodeRecord; exports.decodeRecord = decodeRecord; exports.parsePointer = parsePointer; exports.addPointer = addPointer; exports.renderPointer = renderPointer; exports.parseRecord = parseRecord; exports.fetchRecords = fetchRecords;
2092
- //# sourceMappingURL=chunk-YTO7Z5SI.cjs.map
2103
+
2104
+
2105
+
2106
+ exports.REGISTRY_ABI = REGISTRY_ABI; exports.REGISTRY_ADDRESS = REGISTRY_ADDRESS; exports.NETWORKS = NETWORKS; exports.isNetworkName = isNetworkName; exports.getRegistry = getRegistry; exports.chainFor = chainFor; exports.needsRpcProbe = needsRpcProbe; exports.identityErrorKind = identityErrorKind; exports.IDENTITY_ERROR_TEXT = IDENTITY_ERROR_TEXT; exports.normalizeFingerprint = normalizeFingerprint; exports.fingerprintToBytes = fingerprintToBytes; exports.bytesToFingerprint = bytesToFingerprint; exports.fingerprintHash = fingerprintHash; exports.keyIdOf = keyIdOf; exports.keyIdToBytes = keyIdToBytes; exports.parsePgpKey = parsePgpKey; exports.verifyClearsigned = verifyClearsigned; exports.verifyAttestation = verifyAttestation; exports.stripEmailUserIDs = stripEmailUserIDs; exports.hasEmailUserID = hasEmailUserID; exports.fetchEFPGraph = fetchEFPGraph; exports.identifyProof = identifyProof; exports.displayUrl = displayUrl; exports.proofHref = proofHref; exports.proofSecondaryHref = proofSecondaryHref; exports.FARCASTER_HUB = FARCASTER_HUB; exports.verifyProof = verifyProof; exports.IPFS_GATEWAYS = IPFS_GATEWAYS; exports.IPFS_GATEWAY = IPFS_GATEWAY; exports.ARWEAVE_GATEWAY = ARWEAVE_GATEWAY; exports.avatarUrl = avatarUrl; exports.avatarFallbacks = avatarFallbacks; exports.parseNftAvatar = parseNftAvatar; exports.NFT_AVATAR_ABI = NFT_AVATAR_ABI; exports.nftAvatarImage = nftAvatarImage; exports.ENS_HINT_KEY = ENS_HINT_KEY; exports.ensHintFor = ensHintFor; exports.ensHintValue = ensHintValue; exports.ENS_TEXT_RESOLVER_ABI = ENS_TEXT_RESOLVER_ABI; exports.ensHintWrite = ensHintWrite; exports.fetchEnsHint = fetchEnsHint; exports.EIP712_NAME = EIP712_NAME; exports.EIP712_VERSION = EIP712_VERSION; exports.registryDomain = registryDomain; exports.AUTHORIZATION_TYPES = AUTHORIZATION_TYPES; exports.attestTypedData = attestTypedData; exports.reattestTypedData = reattestTypedData; exports.updateKeyTypedData = updateKeyTypedData; exports.revokeTypedData = revokeTypedData; exports.setRecordTypedData = setRecordTypedData; exports.authorizationDigest = authorizationDigest; exports.recordKind = recordKind; exports.MAX_RECORD_BYTES = MAX_RECORD_BYTES; exports.kindName = kindName; exports.KNOWN_KINDS = KNOWN_KINDS; exports.IDENTITY_KINDS = IDENTITY_KINDS; exports.encodeRecord = encodeRecord; exports.decodeRecord = decodeRecord; exports.parsePointer = parsePointer; exports.addPointer = addPointer; exports.renderPointer = renderPointer; exports.parseRecord = parseRecord; exports.fetchRecords = fetchRecords;
2107
+ //# sourceMappingURL=chunk-OIE72R6J.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/ben/code/thurinlabs/identity-kit/dist/chunk-OIE72R6J.cjs","../src/core/abi.ts","../src/core/contract.ts","../src/core/identityError.ts","../src/core/fingerprint.ts","../src/core/pgp.ts","../src/core/efp.ts","../src/core/proofs.ts","../src/core/avatar.ts","../src/core/ensHint.ts","../src/core/authorization.ts","../src/core/records.ts"],"names":["stringToHex"],"mappings":"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AA;AACvB,QAAA;AAAqB,QAAA;AACE,QAAA;AACtC,MAAA;AAC6C,MAAA;AAC/C,IAAA;AACK,IAAA;AACqB,IAAA;AACuB,MAAA;AACf,MAAA;AAC5B,MAAA;AAC4C,QAAA;AACZ,QAAA;AAC5B,MAAA;AAA6C,QAAA;AAAE,MAAA;AACV,MAAA;AAC/C,IAAA;AACuB,IAAA;AACjB,MAAA;AAA+C,QAAA;AAC7C,MAAA;AAA0C,QAAA;AAAE,MAAA;AACpD,IAAA;AACA,IAAA;AAC+C,MAAA;AACjD,EAAA;AACF;AAaE;AAE6B,EAAA;AACH,EAAA;AACyB,IAAA;AACpB,IAAA;AACoB,IAAA;AACnD,EAAA;AACO,EAAA;AACT;AX+zDsD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/ben/code/thurinlabs/identity-kit/dist/chunk-OIE72R6J.cjs","sourcesContent":[null,"// Generated from pgp-registry: `forge inspect PGPRegistry abi --json`. Do not edit by hand.\nexport const REGISTRY_ABI = [\n {\n \"type\": \"function\",\n \"name\": \"ATTEST_TYPEHASH\",\n \"inputs\": [],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"DOMAIN_SEPARATOR\",\n \"inputs\": [],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"EIP712_NAME\",\n \"inputs\": [],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"string\",\n \"internalType\": \"string\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"EIP712_VERSION\",\n \"inputs\": [],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"string\",\n \"internalType\": \"string\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"MAX_KEY_BYTES\",\n \"inputs\": [],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"MAX_RECORD_BYTES\",\n \"inputs\": [],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"MAX_SIG_BYTES\",\n \"inputs\": [],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"MESSAGE_VERSION\",\n \"inputs\": [],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"uint8\",\n \"internalType\": \"uint8\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"REATTEST_TYPEHASH\",\n \"inputs\": [],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"REVOKE_TYPEHASH\",\n \"inputs\": [],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"SET_RECORD_TYPEHASH\",\n \"inputs\": [],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"UPDATE_KEY_TYPEHASH\",\n \"inputs\": [],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"VERSION\",\n \"inputs\": [],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"uint8\",\n \"internalType\": \"uint8\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"addressesFor\",\n \"inputs\": [\n {\n \"name\": \"fingerprintHash\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"address[]\",\n \"internalType\": \"address[]\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"addressesForCount\",\n \"inputs\": [\n {\n \"name\": \"fingerprintHash\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"addressesForRange\",\n \"inputs\": [\n {\n \"name\": \"fingerprintHash\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n },\n {\n \"name\": \"start\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"count\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"out\",\n \"type\": \"address[]\",\n \"internalType\": \"address[]\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"attest\",\n \"inputs\": [\n {\n \"name\": \"fingerprint\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"pgpSignature\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"pgpPublicKey\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"index\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n }\n ],\n \"stateMutability\": \"nonpayable\"\n },\n {\n \"type\": \"function\",\n \"name\": \"attestFor\",\n \"inputs\": [\n {\n \"name\": \"owner\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"fingerprint\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"pgpSignature\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"pgpPublicKey\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"deadline\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"signature\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"index\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n }\n ],\n \"stateMutability\": \"nonpayable\"\n },\n {\n \"type\": \"function\",\n \"name\": \"attestationCount\",\n \"inputs\": [\n {\n \"name\": \"owner\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"attestationsOf\",\n \"inputs\": [\n {\n \"name\": \"owner\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"tuple[]\",\n \"internalType\": \"struct PGPRegistry.Attestation[]\",\n \"components\": [\n {\n \"name\": \"fingerprint\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"createdAt\",\n \"type\": \"uint64\",\n \"internalType\": \"uint64\"\n },\n {\n \"name\": \"revokedAt\",\n \"type\": \"uint64\",\n \"internalType\": \"uint64\"\n },\n {\n \"name\": \"messageVersion\",\n \"type\": \"uint8\",\n \"internalType\": \"uint8\"\n },\n {\n \"name\": \"keyPtr\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"sigPtr\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n }\n ]\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"attestationsOfRange\",\n \"inputs\": [\n {\n \"name\": \"owner\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"start\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"count\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"out\",\n \"type\": \"tuple[]\",\n \"internalType\": \"struct PGPRegistry.Attestation[]\",\n \"components\": [\n {\n \"name\": \"fingerprint\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"createdAt\",\n \"type\": \"uint64\",\n \"internalType\": \"uint64\"\n },\n {\n \"name\": \"revokedAt\",\n \"type\": \"uint64\",\n \"internalType\": \"uint64\"\n },\n {\n \"name\": \"messageVersion\",\n \"type\": \"uint8\",\n \"internalType\": \"uint8\"\n },\n {\n \"name\": \"keyPtr\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"sigPtr\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n }\n ]\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"cancelAuthorization\",\n \"inputs\": [],\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\"\n },\n {\n \"type\": \"function\",\n \"name\": \"current\",\n \"inputs\": [\n {\n \"name\": \"owner\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"found\",\n \"type\": \"bool\",\n \"internalType\": \"bool\"\n },\n {\n \"name\": \"index\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"attestation\",\n \"type\": \"tuple\",\n \"internalType\": \"struct PGPRegistry.Attestation\",\n \"components\": [\n {\n \"name\": \"fingerprint\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"createdAt\",\n \"type\": \"uint64\",\n \"internalType\": \"uint64\"\n },\n {\n \"name\": \"revokedAt\",\n \"type\": \"uint64\",\n \"internalType\": \"uint64\"\n },\n {\n \"name\": \"messageVersion\",\n \"type\": \"uint8\",\n \"internalType\": \"uint8\"\n },\n {\n \"name\": \"keyPtr\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"sigPtr\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n }\n ]\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"fingerprintsForKeyId\",\n \"inputs\": [\n {\n \"name\": \"keyId\",\n \"type\": \"bytes8\",\n \"internalType\": \"bytes8\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"bytes[]\",\n \"internalType\": \"bytes[]\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"fingerprintsForKeyIdCount\",\n \"inputs\": [\n {\n \"name\": \"keyId\",\n \"type\": \"bytes8\",\n \"internalType\": \"bytes8\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"fingerprintsForKeyIdRange\",\n \"inputs\": [\n {\n \"name\": \"keyId\",\n \"type\": \"bytes8\",\n \"internalType\": \"bytes8\"\n },\n {\n \"name\": \"start\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"count\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"out\",\n \"type\": \"bytes[]\",\n \"internalType\": \"bytes[]\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"getAttestation\",\n \"inputs\": [\n {\n \"name\": \"owner\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"index\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"tuple\",\n \"internalType\": \"struct PGPRegistry.Attestation\",\n \"components\": [\n {\n \"name\": \"fingerprint\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"createdAt\",\n \"type\": \"uint64\",\n \"internalType\": \"uint64\"\n },\n {\n \"name\": \"revokedAt\",\n \"type\": \"uint64\",\n \"internalType\": \"uint64\"\n },\n {\n \"name\": \"messageVersion\",\n \"type\": \"uint8\",\n \"internalType\": \"uint8\"\n },\n {\n \"name\": \"keyPtr\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"sigPtr\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n }\n ]\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"getPayload\",\n \"inputs\": [\n {\n \"name\": \"owner\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"index\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"pgpSignature\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"pgpPublicKey\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"nonces\",\n \"inputs\": [\n {\n \"name\": \"\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"reattest\",\n \"inputs\": [\n {\n \"name\": \"revokeIndex\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"fingerprint\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"pgpSignature\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"pgpPublicKey\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"index\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n }\n ],\n \"stateMutability\": \"nonpayable\"\n },\n {\n \"type\": \"function\",\n \"name\": \"reattestFor\",\n \"inputs\": [\n {\n \"name\": \"owner\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"revokeIndex\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"fingerprint\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"pgpSignature\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"pgpPublicKey\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"deadline\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"signature\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"index\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n }\n ],\n \"stateMutability\": \"nonpayable\"\n },\n {\n \"type\": \"function\",\n \"name\": \"record\",\n \"inputs\": [\n {\n \"name\": \"owner\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"index\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"kind\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n }\n ],\n \"outputs\": [\n {\n \"name\": \"\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ],\n \"stateMutability\": \"view\"\n },\n {\n \"type\": \"function\",\n \"name\": \"revoke\",\n \"inputs\": [\n {\n \"name\": \"index\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n }\n ],\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\"\n },\n {\n \"type\": \"function\",\n \"name\": \"revokeFor\",\n \"inputs\": [\n {\n \"name\": \"owner\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"index\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"deadline\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"signature\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ],\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\"\n },\n {\n \"type\": \"function\",\n \"name\": \"setRecord\",\n \"inputs\": [\n {\n \"name\": \"index\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"kind\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n },\n {\n \"name\": \"value\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ],\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\"\n },\n {\n \"type\": \"function\",\n \"name\": \"setRecordFor\",\n \"inputs\": [\n {\n \"name\": \"owner\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"index\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"kind\",\n \"type\": \"bytes32\",\n \"internalType\": \"bytes32\"\n },\n {\n \"name\": \"value\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"deadline\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"signature\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ],\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\"\n },\n {\n \"type\": \"function\",\n \"name\": \"updateKey\",\n \"inputs\": [\n {\n \"name\": \"index\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"pgpPublicKey\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ],\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\"\n },\n {\n \"type\": \"function\",\n \"name\": \"updateKeyFor\",\n \"inputs\": [\n {\n \"name\": \"owner\",\n \"type\": \"address\",\n \"internalType\": \"address\"\n },\n {\n \"name\": \"index\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"pgpPublicKey\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"deadline\",\n \"type\": \"uint256\",\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"signature\",\n \"type\": \"bytes\",\n \"internalType\": \"bytes\"\n }\n ],\n \"outputs\": [],\n \"stateMutability\": \"nonpayable\"\n },\n {\n \"type\": \"event\",\n \"name\": \"Attested\",\n \"inputs\": [\n {\n \"name\": \"owner\",\n \"type\": \"address\",\n \"indexed\": true,\n \"internalType\": \"address\"\n },\n {\n \"name\": \"fingerprintHash\",\n \"type\": \"bytes32\",\n \"indexed\": true,\n \"internalType\": \"bytes32\"\n },\n {\n \"name\": \"index\",\n \"type\": \"uint256\",\n \"indexed\": true,\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"fingerprint\",\n \"type\": \"bytes\",\n \"indexed\": false,\n \"internalType\": \"bytes\"\n },\n {\n \"name\": \"messageVersion\",\n \"type\": \"uint8\",\n \"indexed\": false,\n \"internalType\": \"uint8\"\n },\n {\n \"name\": \"keyPtr\",\n \"type\": \"address\",\n \"indexed\": false,\n \"internalType\": \"address\"\n },\n {\n \"name\": \"sigPtr\",\n \"type\": \"address\",\n \"indexed\": false,\n \"internalType\": \"address\"\n },\n {\n \"name\": \"submitter\",\n \"type\": \"address\",\n \"indexed\": false,\n \"internalType\": \"address\"\n }\n ],\n \"anonymous\": false\n },\n {\n \"type\": \"event\",\n \"name\": \"KeyUpdated\",\n \"inputs\": [\n {\n \"name\": \"owner\",\n \"type\": \"address\",\n \"indexed\": true,\n \"internalType\": \"address\"\n },\n {\n \"name\": \"fingerprintHash\",\n \"type\": \"bytes32\",\n \"indexed\": true,\n \"internalType\": \"bytes32\"\n },\n {\n \"name\": \"index\",\n \"type\": \"uint256\",\n \"indexed\": true,\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"oldKeyPtr\",\n \"type\": \"address\",\n \"indexed\": false,\n \"internalType\": \"address\"\n },\n {\n \"name\": \"newKeyPtr\",\n \"type\": \"address\",\n \"indexed\": false,\n \"internalType\": \"address\"\n },\n {\n \"name\": \"submitter\",\n \"type\": \"address\",\n \"indexed\": false,\n \"internalType\": \"address\"\n }\n ],\n \"anonymous\": false\n },\n {\n \"type\": \"event\",\n \"name\": \"NonceUsed\",\n \"inputs\": [\n {\n \"name\": \"owner\",\n \"type\": \"address\",\n \"indexed\": true,\n \"internalType\": \"address\"\n },\n {\n \"name\": \"nonce\",\n \"type\": \"uint256\",\n \"indexed\": false,\n \"internalType\": \"uint256\"\n }\n ],\n \"anonymous\": false\n },\n {\n \"type\": \"event\",\n \"name\": \"RecordSet\",\n \"inputs\": [\n {\n \"name\": \"owner\",\n \"type\": \"address\",\n \"indexed\": true,\n \"internalType\": \"address\"\n },\n {\n \"name\": \"index\",\n \"type\": \"uint256\",\n \"indexed\": true,\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"kind\",\n \"type\": \"bytes32\",\n \"indexed\": true,\n \"internalType\": \"bytes32\"\n },\n {\n \"name\": \"submitter\",\n \"type\": \"address\",\n \"indexed\": false,\n \"internalType\": \"address\"\n }\n ],\n \"anonymous\": false\n },\n {\n \"type\": \"event\",\n \"name\": \"Revoked\",\n \"inputs\": [\n {\n \"name\": \"owner\",\n \"type\": \"address\",\n \"indexed\": true,\n \"internalType\": \"address\"\n },\n {\n \"name\": \"fingerprintHash\",\n \"type\": \"bytes32\",\n \"indexed\": true,\n \"internalType\": \"bytes32\"\n },\n {\n \"name\": \"index\",\n \"type\": \"uint256\",\n \"indexed\": true,\n \"internalType\": \"uint256\"\n },\n {\n \"name\": \"submitter\",\n \"type\": \"address\",\n \"indexed\": false,\n \"internalType\": \"address\"\n }\n ],\n \"anonymous\": false\n },\n {\n \"type\": \"error\",\n \"name\": \"AlreadyRevoked\",\n \"inputs\": []\n },\n {\n \"type\": \"error\",\n \"name\": \"AuthorizationExpired\",\n \"inputs\": []\n },\n {\n \"type\": \"error\",\n \"name\": \"DeploymentFailed\",\n \"inputs\": []\n },\n {\n \"type\": \"error\",\n \"name\": \"DuplicateActiveFingerprint\",\n \"inputs\": []\n },\n {\n \"type\": \"error\",\n \"name\": \"EmptyPublicKey\",\n \"inputs\": []\n },\n {\n \"type\": \"error\",\n \"name\": \"EmptySignature\",\n \"inputs\": []\n },\n {\n \"type\": \"error\",\n \"name\": \"IndexOutOfBounds\",\n \"inputs\": []\n },\n {\n \"type\": \"error\",\n \"name\": \"InvalidAuthorization\",\n \"inputs\": []\n },\n {\n \"type\": \"error\",\n \"name\": \"InvalidFingerprintLength\",\n \"inputs\": []\n },\n {\n \"type\": \"error\",\n \"name\": \"InvalidPointer\",\n \"inputs\": []\n },\n {\n \"type\": \"error\",\n \"name\": \"PublicKeyTooLarge\",\n \"inputs\": []\n },\n {\n \"type\": \"error\",\n \"name\": \"RecordTooLarge\",\n \"inputs\": []\n },\n {\n \"type\": \"error\",\n \"name\": \"SignatureTooLarge\",\n \"inputs\": []\n }\n] as const\n","import { mainnet, sepolia, foundry } from 'viem/chains'\nexport { REGISTRY_ABI } from './abi'\n\n/**\n * PGPRegistry v2 is deployed with CREATE2, so it has the same address on every\n * network. (v1 lived at 0xf7a45BC662A78a6fb417ED5f52b3766cbf13EbBb on mainnet and\n * is no longer read by Thurin.)\n */\nexport const REGISTRY_ADDRESS = '0x9302E02e2869e129aC8516fE5eFFd51EA3082c09' as const\n\n/** Networks the PGPRegistry is deployed on. `mainnet` is the default everywhere. */\nexport type NetworkName = 'mainnet' | 'sepolia' | 'local'\n\nexport interface RegistryDeployment {\n chainId: number\n address: `0x${string}`\n /** Block the registry was deployed in (informational; v2 needs no log scans). */\n deployBlock: bigint\n /** Block explorer base URL for address/tx links. Empty when there is none. */\n explorerUrl: string\n /** Keyless public RPC used when no rpcUrl is configured. */\n defaultRpcUrl: string\n}\n\nexport const NETWORKS: Record<NetworkName, RegistryDeployment> = {\n mainnet: {\n chainId: 1,\n address: REGISTRY_ADDRESS,\n deployBlock: 25962908n, // 2026-09-12, tx 0xda4c3a19cae4e71ffb02559a048403f664d5a7967c19d42158c39600ec63bad8\n explorerUrl: 'https://etherscan.io',\n defaultRpcUrl: 'https://ethereum.publicnode.com', // same service as ethereum-rpc.publicnode.com; this hostname resolves on more networks\n },\n sepolia: {\n chainId: 11155111,\n address: REGISTRY_ADDRESS,\n deployBlock: 11683667n, // 2026-09-11, tx 0x3e40e8f5b9fbf82a4b8943561bcc9b964b2987de10640ac8ff3a1b4ef0725322\n explorerUrl: 'https://sepolia.etherscan.io',\n defaultRpcUrl: 'https://ethereum-sepolia.publicnode.com',\n },\n /** A local anvil instance (`anvil`, chain id 31337). Deploy with pgp-registry's Deploy script. */\n local: {\n chainId: 31337,\n address: REGISTRY_ADDRESS,\n deployBlock: 0n,\n explorerUrl: '',\n defaultRpcUrl: 'http://127.0.0.1:8545',\n },\n}\n\nexport function isNetworkName(value: unknown): value is NetworkName {\n return value === 'mainnet' || value === 'sepolia' || value === 'local'\n}\n\n/**\n * The registry deployment for a network. `addressOverride` swaps the contract\n * address (e.g. a local deploy that landed somewhere else).\n */\nexport function getRegistry(network: NetworkName = 'mainnet', addressOverride?: string): RegistryDeployment {\n const r = NETWORKS[network]\n if (!r) throw new Error(`Unknown network: ${network}`)\n if (addressOverride && /^0x[0-9a-fA-F]{40}$/.test(addressOverride)) {\n return { ...r, address: addressOverride as `0x${string}` }\n }\n return r\n}\n\n/** The viem chain object for a network name. */\nexport function chainFor(network: NetworkName = 'mainnet') {\n if (network === 'sepolia') return sepolia\n if (network === 'local') return foundry\n return mainnet\n}\n","/**\n * Why an identity couldn't be shown. A dead RPC must never read as a fact about the identity:\n * viem's ENS lookup turns an unreachable RPC into \"no address\", and a failed registry read\n * leaves every count at zero. So when a lookup comes back empty or fails, the hook asks the\n * RPC for the block number and this decides which it was.\n *\n * - `rpc`: the RPC didn't answer; nothing about the identity is known\n * - `not-found`: the RPC answered and the ENS name has no address\n * - `read`: the RPC answers, but a registry read failed anyway\n */\nexport type IdentityErrorKind = 'rpc' | 'not-found' | 'read'\n\nexport interface IdentityLookupState {\n /** An ENS name was looked up and came back with no address. */\n ensEmpty: boolean\n /** The ENS lookup itself errored. */\n ensFailed: boolean\n /** Reading the owner's claims from the registry errored. */\n claimsFailed: boolean\n /** The block-number probe: undefined while not run or running. */\n rpcAnswered: boolean | undefined\n}\n\n/** Whether the lookup needs the RPC probe to be understood. */\nexport function needsRpcProbe(s: Omit<IdentityLookupState, 'rpcAnswered'>): boolean {\n return s.ensEmpty || s.ensFailed || s.claimsFailed\n}\n\n/** null when nothing went wrong or the probe hasn't answered yet. */\nexport function identityErrorKind(s: IdentityLookupState): IdentityErrorKind | null {\n if (!needsRpcProbe(s) || s.rpcAnswered === undefined) return null\n if (!s.rpcAnswered) return 'rpc'\n if (s.ensEmpty && !s.ensFailed) return 'not-found'\n return 'read'\n}\n\nexport const IDENTITY_ERROR_TEXT: Record<IdentityErrorKind, string> = {\n rpc: \"Couldn't reach the RPC, so nothing here is known yet.\",\n 'not-found': 'No address for this name.',\n read: \"Couldn't read the registry.\",\n}\n","import { keccak256, type Hex } from 'viem'\n\n/** 40 hex chars (OpenPGP v4) or 64 hex chars (v6), spaces/0x tolerated. */\nconst FINGERPRINT_HEX = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/\n\n/** Normalize a fingerprint to lowercase hex with no prefix or spaces. Returns null if malformed. */\nexport function normalizeFingerprint(input: string): string | null {\n const hex = input.replace(/^0x/i, '').replace(/\\s+/g, '').toLowerCase()\n return FINGERPRINT_HEX.test(hex) ? hex : null\n}\n\n/** Fingerprint → the raw `bytes` argument the v2 registry takes (0x-prefixed, 20 or 32 bytes). */\nexport function fingerprintToBytes(fingerprint: string): Hex {\n const hex = normalizeFingerprint(fingerprint)\n if (!hex) throw new Error(`Invalid PGP fingerprint: ${fingerprint}`)\n return `0x${hex}`\n}\n\n/** Raw `bytes` from the registry → lowercase hex fingerprint without 0x. */\nexport function bytesToFingerprint(bytes: Hex | string): string {\n return bytes.replace(/^0x/i, '').toLowerCase()\n}\n\n/** keccak256 of the raw fingerprint bytes — the `fingerprintHash` the registry indexes by. */\nexport function fingerprintHash(fingerprint: string): Hex {\n return keccak256(fingerprintToBytes(fingerprint))\n}\n\n/**\n * The long key ID as the `bytes8` the registry indexes by. Per RFC 9580 §5.5.4 it is the\n * low-order 8 bytes of a v4 (20-byte) fingerprint and the high-order 8 bytes of a v6\n * (32-byte) fingerprint — the same value `gpg --keyid-format long` prints.\n */\nexport function keyIdOf(fingerprint: string): Hex {\n const hex = normalizeFingerprint(fingerprint)\n if (!hex) throw new Error(`Invalid PGP fingerprint: ${fingerprint}`)\n return `0x${keyIdHex(hex)}`\n}\n\n/** Long key ID (16 lowercase hex chars) of a normalized fingerprint. */\nexport function keyIdHex(normalizedFingerprint: string): string {\n return normalizedFingerprint.length === 64 ? normalizedFingerprint.slice(0, 16) : normalizedFingerprint.slice(-16)\n}\n\n/** A 16-hex long key ID (spaces/0x tolerated) → `bytes8`. */\nexport function keyIdToBytes(keyId: string): Hex | null {\n const hex = keyId.replace(/^0x/i, '').replace(/\\s+/g, '').toLowerCase()\n return /^[0-9a-f]{16}$/.test(hex) ? `0x${hex}` : null\n}\n","import type { PGPKeyInfo, PGPVerification } from './types'\n\n/**\n * openpgp.js refuses secp256k1 by default because RFC 9580 does not list the\n * curve — a compatibility choice, not a security one. A secp256k1 PGP key is a\n * valid key (and, incidentally, doubles as an Ethereum key: the address falls out\n * of the same public point). Thurin verifies signatures over any curve openpgp.js\n * can compute; whether a holder reuses such a key as a wallet is their business.\n * In Node, openpgp.js needs the optional `eckey-utils` package for this curve;\n * the browser build does not.\n *\n * The low-level packet/key methods take a *complete* config (they index into\n * it), so this builds one from the library's defaults rather than passing a\n * partial object.\n */\nfunction pgpConfig(openpgp: typeof import('openpgp')) {\n // Accept secp256k1 only. Clearing the set would also accept anything openpgp.js adds to it\n // later for security reasons (terricola, 2026-09-23); removing one entry keeps that policy.\n const rejectCurves = new Set([...(openpgp.config.rejectCurves ?? [])].filter(c => c !== 'secp256k1'))\n return { ...openpgp.config, rejectCurves: rejectCurves as Set<never> }\n}\n\n/** Human-readable algorithm for a key or subkey packet: \"Ed25519\", \"Cv25519\", \"RSA 4096\", \"NIST P-256\", … */\nfunction algorithmName(keyOrSubkey: any): string {\n try {\n const info = keyOrSubkey.getAlgorithmInfo() as { algorithm: string; bits?: number; curve?: string }\n const curve = (info.curve || '').toLowerCase()\n if (curve.startsWith('ed25519') || info.algorithm === 'ed25519' || info.algorithm === 'eddsaLegacy') return 'Ed25519'\n if (curve.startsWith('curve25519') || info.algorithm === 'x25519') return 'Cv25519'\n if (curve.startsWith('ed448')) return 'Ed448'\n if (curve.startsWith('curve448') || info.algorithm === 'x448') return 'X448'\n if (curve.startsWith('nistp') || curve.startsWith('p')) return `NIST P-${curve.replace(/\\D/g, '')}`\n if (curve.startsWith('secp')) return curve\n if (curve.startsWith('brainpool')) return curve\n if (info.algorithm.startsWith('rsa')) return info.bits ? `RSA ${info.bits}` : 'RSA'\n if (info.algorithm.startsWith('dsa')) return info.bits ? `DSA ${info.bits}` : 'DSA'\n if (info.algorithm.startsWith('elgamal')) return info.bits ? `ElGamal ${info.bits}` : 'ElGamal'\n return info.curve || info.algorithm\n } catch {\n return String(keyOrSubkey.keyPacket?.algorithm ?? 'unknown')\n }\n}\n\nexport async function parsePgpKey(armoredKey: string): Promise<PGPKeyInfo | null> {\n try {\n const openpgp = await import('openpgp')\n const { readKey } = openpgp\n const config = pgpConfig(openpgp)\n const key = await readKey({ armoredKey })\n const fingerprint = key.getFingerprint().toUpperCase()\n const algorithm = algorithmName(key)\n const created = key.keyPacket.created?.toISOString() ?? null\n const expiration = await key.getExpirationTime()\n const expires =\n expiration && expiration !== Infinity\n ? new Date(expiration as number).toISOString()\n : null\n\n // openpgp does not verify certification signatures when a key is parsed —\n // it groups them by issuer key ID only. A key's fingerprint also covers\n // only the primary key packet, not its user IDs or their certifications.\n // Each self-certification is therefore verified against the primary key\n // here before its user ID and notations are treated as authoritative.\n const userIDs: string[] = []\n const notations: { name: string; value: string }[] = []\n const seen = new Set<string>()\n for (const user of key.users) {\n const certs = (user as any).selfCertifications\n if (!certs || certs.length === 0) continue\n\n let userVerified = false\n for (const cert of certs) {\n try {\n await cert.verify(\n key.keyPacket,\n cert.signatureType,\n { userID: (user as any).userID, key: key.keyPacket },\n undefined, // date: now\n undefined, // detached\n config,\n )\n } catch {\n continue // signature not made by this key — ignore this certification\n }\n userVerified = true\n\n if (cert.rawNotations) {\n for (const n of cert.rawNotations) {\n const name =\n typeof n.name === 'string' ? n.name : new TextDecoder().decode(n.name)\n const value =\n n.value instanceof Uint8Array\n ? new TextDecoder().decode(n.value)\n : typeof n.value === 'string'\n ? n.value\n : null\n if (value) {\n // Dedupe by value alone: the same proof target is often present\n // under both proof@thurin.id and proof@ariadne.id, and should\n // surface once, not once per namespace.\n if (!seen.has(value)) {\n seen.add(value)\n notations.push({ name, value })\n }\n }\n }\n }\n }\n\n if (userVerified) {\n const uid = (user as any).userID?.userID\n if (uid) userIDs.push(uid)\n }\n }\n\n const subkeys = key.subkeys.map((sk: any) => ({\n algorithm: algorithmName(sk),\n created: sk.keyPacket.created?.toISOString() ?? null,\n fingerprint: sk.getFingerprint().toUpperCase(),\n }))\n\n return { fingerprint, userIDs, algorithm, created, expires, notations, subkeys }\n } catch {\n return null\n }\n}\n\n/**\n * Verify a clearsigned message against an armored public key, the way gpg does: the signing\n * key (or subkey) must be valid *now* — bound to this primary, unrevoked, unexpired — and the\n * signature must be sound. We deliberately do not ask openpgp.verify(), which also demands\n * the key was valid at the instant the signature was made. Thurin's update flow tells people\n * to `export-minimal` after editing notations, which keeps only the newest self-certification;\n * when that postdates the signature (Ben's own key, 2026-09-14: notations edited 16 minutes\n * after signing), openpgp.verify() reports \"Could not find valid self-signature\" for a\n * signature gpg verifies fine. A key that has since been revoked or expired still fails here.\n * These are internals the .d.ts does not expose (the signature packet list, the\n * CRLF-normalised text, LiteralDataPacket.setText); they are stable across openpgp.js 6 and\n * exercised by the real-data tests in attestation.test.ts.\n *\n * Used for attestation statements and for clearsigned records such as `thurin.canary`.\n */\nexport async function verifyClearsigned({ armoredKey, clearsigned }: { armoredKey: string; clearsigned: string }): Promise<{ verified: boolean; reason?: string; text: string }> {\n try {\n const openpgp = await import('openpgp')\n const { readKey, readCleartextMessage, LiteralDataPacket } = openpgp\n const config = pgpConfig(openpgp)\n const publicKey = await readKey({ armoredKey })\n const message = await readCleartextMessage({ cleartextMessage: clearsigned })\n const msg = message as any\n const now = new Date()\n for (const sig of msg.signature.packets) {\n if (!publicKey.getKeys(sig.issuerKeyID).length) continue\n const signingKey = await publicKey.getSigningKey(sig.issuerKeyID, now, undefined, config)\n const literal: any = new LiteralDataPacket()\n literal.setText(msg.text ?? message.getText().replace(/\\r?\\n/g, '\\r\\n'))\n await sig.verify(signingKey.keyPacket, sig.signatureType, literal, now, true, config)\n return { verified: true, text: message.getText() }\n }\n return { verified: false, reason: 'Message is not signed by this key', text: message.getText() }\n } catch (err) {\n return { verified: false, reason: err instanceof Error ? err.message : 'Verification failed', text: '' }\n }\n}\n\nexport async function verifyAttestation({\n pgpPublicKey,\n pgpSignature,\n fingerprint,\n ethAddress,\n}: {\n pgpPublicKey: string | null\n pgpSignature: string | null\n fingerprint: string\n ethAddress: string\n}): Promise<PGPVerification> {\n try {\n if (!pgpPublicKey || !pgpSignature) {\n return { verified: false, reason: 'Missing PGP data' }\n }\n\n const openpgp = await import('openpgp')\n const { readKey } = openpgp\n\n const publicKey = await readKey({ armoredKey: pgpPublicKey })\n const keyFingerprint = publicKey.getFingerprint().toUpperCase()\n if (keyFingerprint !== fingerprint.toUpperCase()) {\n return { verified: false, reason: 'Key fingerprint mismatch' }\n }\n\n const v = await verifyClearsigned({ armoredKey: pgpPublicKey, clearsigned: pgpSignature })\n if (!v.verified) return { verified: false, reason: v.reason }\n\n const signedText = v.text\n if (!signedText.toLowerCase().includes(ethAddress.toLowerCase())) {\n return { verified: false, reason: 'Signed message does not contain ETH address' }\n }\n\n return { verified: true }\n } catch {\n return { verified: false, reason: 'Signature verification failed' }\n }\n}\n\nexport interface StrippedKey {\n /** Armored public key containing only the non-email user IDs. */\n armored: string\n /** User IDs kept (no `@`). */\n kept: string[]\n /** User IDs removed (contained an `@`). */\n removed: string[]\n}\n\nconst EMAIL_UID = /@/\n\n/**\n * Remove every user ID that contains an email address, keeping the rest (and\n * their self-certifications, so proof notations on a non-email user ID survive).\n * Returns null when nothing would remain — openpgp needs at least one\n * self-certified user ID to verify signatures, so such a key must not be\n * published. The key material and subkeys are untouched; the fingerprint is\n * unchanged.\n */\nexport async function stripEmailUserIDs(armoredKey: string): Promise<StrippedKey | null> {\n try {\n const { readKey } = await import('openpgp')\n const key = await readKey({ armoredKey })\n const kept: string[] = []\n const removed: string[] = []\n const users = (key as any).users.filter((u: any) => {\n const uid: string | undefined = u.userID?.userID\n if (!uid) return false\n if (EMAIL_UID.test(uid)) { removed.push(uid); return false }\n kept.push(uid)\n return true\n })\n if (users.length === 0) return null\n ;(key as any).users = users\n return { armored: key.armor(), kept, removed }\n } catch {\n return null\n }\n}\n\n/** True when any user ID on the key contains an email address. */\nexport async function hasEmailUserID(armoredKey: string): Promise<boolean> {\n try {\n const { readKey } = await import('openpgp')\n const key = await readKey({ armoredKey })\n return (key as any).users.some((u: any) => EMAIL_UID.test(u.userID?.userID ?? ''))\n } catch {\n return false\n }\n}\n","import type { EFPGraph } from './types'\n\nconst EFP_API = 'https://api.ethfollow.xyz/api/v1'\n\n/**\n * EFP social graph for an address. Followers are counted by EFP for any address,\n * whether or not it has published a list of its own; following and top-8 exist\n * only when it has (its \"primary list\"). An address with followers but no list\n * — a project account that has never followed anyone — is still on EFP.\n */\nexport async function fetchEFPGraph(address: string): Promise<EFPGraph | null> {\n try {\n const [statsResp, listsResp] = await Promise.all([\n fetch(`${EFP_API}/users/${address}/stats`),\n fetch(`${EFP_API}/users/${address}/lists`),\n ])\n if (!statsResp.ok && !listsResp.ok) return null\n\n const statsData = statsResp.ok ? await statsResp.json() : {}\n const listsData = listsResp.ok ? await listsResp.json() : {}\n const followers = parseInt(statsData.followers_count) || 0\n const listId = listsData.primary_list\n\n let following = 0\n let top8: string[] = []\n if (listId) {\n const [followingResp, top8Resp] = await Promise.all([\n fetch(`${EFP_API}/lists/${listId}/following?limit=100`),\n fetch(`${EFP_API}/lists/${listId}/following?limit=8&tags=top8`),\n ])\n const followingData = followingResp.ok ? await followingResp.json() : { following: [] }\n const top8Data = top8Resp.ok ? await top8Resp.json() : { following: [] }\n following = (followingData.following || []).length\n top8 = (top8Data.following || []).map((f: any) => f.data)\n }\n\n return { followers, following, top8, hasEfp: !!listId || followers > 0 }\n } catch {\n return null\n }\n}\n","import { keyIdHex } from './fingerprint'\nimport type { Notation, Proof, PGPVerification } from './types'\n\nconst PROVIDERS: {\n provider: string\n label: string\n pattern: RegExp\n parse: (m: RegExpMatchArray) => Record<string, string>\n}[] = [\n {\n provider: 'github',\n label: 'GitHub',\n pattern: /^https:\\/\\/gist\\.github\\.com\\/([^/]+)\\/([a-f0-9]+)$/i,\n parse: (m) => ({ user: m[1], gistId: m[2] }),\n },\n {\n // A repository's description — the form an organisation can use, since\n // gists belong to user accounts only. Same shape as the Codeberg proof.\n provider: 'github',\n label: 'GitHub',\n pattern: /^https:\\/\\/github\\.com\\/([A-Za-z0-9-]+)\\/([A-Za-z0-9._-]+)$/i,\n parse: (m) => ({ user: m[1], repo: m[2] }),\n },\n {\n provider: 'dns',\n label: 'DNS',\n // domain is restricted to a hostname (labels may include underscores for\n // records like _thurin.example.com); rejects slashes, userinfo, and spaces.\n pattern: /^dns:([a-z0-9._-]+)\\?type=TXT$/i,\n parse: (m) => ({ domain: m[1] }),\n },\n {\n provider: 'farcaster',\n label: 'Farcaster',\n pattern: /^https:\\/\\/farcaster\\.xyz\\/([^/]+)\\/(0x[a-f0-9]+)$/i,\n parse: (m) => ({ user: m[1], castHash: m[2] }),\n },\n {\n provider: 'codeberg',\n label: 'Codeberg',\n pattern: /^https:\\/\\/codeberg\\.org\\/([^/]+)\\/([^/]+)$/i,\n parse: (m) => ({ user: m[1], repo: m[2] }),\n },\n {\n provider: 'mastodon',\n label: 'Mastodon',\n // instance is restricted to a bare hostname (optional port) so a value\n // like \"mastodon.social@evil.com\" can't redirect the lookup elsewhere.\n pattern: /^https:\\/\\/([a-z0-9.-]+(?::\\d+)?)\\/@([^/@?#\\s]+)$/i,\n parse: (m) => ({ instance: m[1], user: m[2] }),\n },\n]\n\nexport function identifyProof(notation: Notation): Proof | null {\n if (notation.name !== 'proof@thurin.id') return null\n\n for (const { provider, label, pattern, parse } of PROVIDERS) {\n const m = notation.value.match(pattern)\n if (m) {\n return { provider, label, url: notation.value, ...parse(m) }\n }\n }\n\n return { provider: 'unknown', label: 'Unknown', url: notation.value }\n}\n\nexport function displayUrl(proof: Proof): string {\n if (proof.provider === 'dns') return proof.domain!\n if (proof.provider === 'github') return proof.user!\n if (proof.provider === 'farcaster') return `@${proof.user}`\n if (proof.provider === 'codeberg') return proof.user!\n if (proof.provider === 'mastodon') return `@${proof.user}@${proof.instance}`\n return proof.url\n}\n\nexport function proofHref(proof: Proof): string | null {\n if (proof.provider === 'dns') return `https://${proof.domain}`\n if (proof.provider === 'github') return `https://github.com/${proof.user}`\n if (proof.provider === 'farcaster') return `https://farcaster.xyz/${proof.user}`\n if (proof.provider === 'codeberg') return `https://codeberg.org/${proof.user}`\n if (proof.provider === 'mastodon') return `https://${proof.instance}/@${proof.user}`\n if (proof.url.startsWith('http')) return proof.url\n return null\n}\n\nexport function proofSecondaryHref(proof: Proof): string | null {\n if (proof.provider === 'github') return proof.url\n if (proof.provider === 'farcaster') return proof.url\n if (proof.provider === 'codeberg') return proof.url\n if (proof.provider === 'mastodon') return proof.url\n return null\n}\n\n// ─── Verification ──────────────────────────────────────────────────────────\n\nconst FPR_TOKEN = 'OPENPGP4FPR:'\n\nfunction containsFingerprint(text: string, fingerprint: string): boolean {\n return text.toUpperCase().includes(FPR_TOKEN + fingerprint.toUpperCase())\n}\n\nfunction containsFingerprintUrl(text: string, fingerprint: string): boolean {\n return text.toUpperCase().includes(fingerprint.toUpperCase())\n}\n\nasync function verifyGitHub(proof: Proof, fingerprint: string): Promise<PGPVerification> {\n if (proof.repo) return verifyGitHubRepo(proof, fingerprint)\n try {\n const resp = await fetch(`https://api.github.com/gists/${proof.gistId}`)\n if (!resp.ok) return { verified: false, reason: `GitHub API returned ${resp.status}` }\n const data = await resp.json()\n\n // A gist is identified solely by its globally-unique ID; the username in\n // the URL is not authoritative. Confirm the gist owner matches the claimed\n // account before trusting the token it contains.\n if (data.owner?.login?.toLowerCase() !== proof.user!.toLowerCase()) {\n return { verified: false, reason: 'Gist owner does not match claimed user' }\n }\n\n for (const file of Object.values(data.files || {}) as any[]) {\n if (file.content && containsFingerprint(file.content, fingerprint)) {\n return { verified: true }\n }\n }\n return { verified: false, reason: 'Fingerprint token not found in gist' }\n } catch (err: any) {\n return { verified: false, reason: `GitHub fetch failed: ${err.message}` }\n }\n}\n\nasync function verifyGitHubRepo(proof: Proof, fingerprint: string): Promise<PGPVerification> {\n try {\n const resp = await fetch(\n `https://api.github.com/repos/${encodeURIComponent(proof.user!)}/${encodeURIComponent(proof.repo!)}`,\n )\n if (!resp.ok) return { verified: false, reason: `GitHub API returned ${resp.status}` }\n const data = await resp.json()\n\n // GitHub follows renames and redirects, so the API may answer for a repo\n // under a different owner than the URL names. Only trust it if the owner\n // is the account being claimed.\n if (data.owner?.login?.toLowerCase() !== proof.user!.toLowerCase()) {\n return { verified: false, reason: 'Repository owner does not match claimed account' }\n }\n\n if (data.description && containsFingerprint(data.description, fingerprint)) {\n return { verified: true }\n }\n return { verified: false, reason: 'Fingerprint token not found in repository description' }\n } catch (err: any) {\n return { verified: false, reason: `GitHub fetch failed: ${err.message}` }\n }\n}\n\nasync function verifyDNS(proof: Proof, fingerprint: string): Promise<PGPVerification> {\n try {\n const resp = await fetch(\n `https://cloudflare-dns.com/dns-query?name=${encodeURIComponent(proof.domain!)}&type=TXT`,\n { headers: { Accept: 'application/dns-json' } },\n )\n if (!resp.ok) return { verified: false, reason: `DNS query returned ${resp.status}` }\n const data = await resp.json()\n\n for (const answer of data.Answer || []) {\n // TXT records only (type 16) — a CNAME or other record in the resolution\n // chain must not be eligible to match the token.\n if (answer.type !== 16) continue\n if (answer.data && containsFingerprint(answer.data, fingerprint)) {\n return { verified: true }\n }\n }\n return { verified: false, reason: 'Fingerprint token not found in TXT records' }\n } catch (err: any) {\n return { verified: false, reason: `DNS fetch failed: ${err.message}` }\n }\n}\n\n/**\n * Farcaster proofs are read from a Farcaster node's HTTP API (`/v1/userNameProofByName`,\n * `/v1/castsByFid`). The default is Quilibrium's public Hypersnap node, which needs no key and\n * allows browser requests; it sees the visitor's IP and which account was checked. Point\n * `farcasterHub` at your own node to avoid that, or pass a Neynar key to use Neynar's hub.\n */\nexport const FARCASTER_HUB = 'https://haatz.quilibrium.com'\nconst NEYNAR_HUB = 'https://hub-api.neynar.com'\n\nexport interface ProofOptions {\n /** A Farcaster node's HTTP API base URL. Default: FARCASTER_HUB (keyless). */\n farcasterHub?: string\n /** Optional: read Farcaster through Neynar's hub with this key instead. */\n neynarApiKey?: string\n}\n\nfunction farcasterSource(opts: ProofOptions): { base: string; headers: Record<string, string> } {\n if (opts.farcasterHub) return { base: opts.farcasterHub.replace(/\\/+$/, ''), headers: {} }\n if (opts.neynarApiKey) return { base: NEYNAR_HUB, headers: { 'x-api-key': opts.neynarApiKey } }\n return { base: FARCASTER_HUB, headers: {} }\n}\n\nasync function verifyFarcaster(\n proof: Proof,\n fingerprint: string,\n opts: ProofOptions = {},\n): Promise<PGPVerification> {\n const { base, headers } = farcasterSource(opts)\n const host = base.replace(/^https?:\\/\\//, '')\n try {\n const nameResp = await fetch(`${base}/v1/userNameProofByName?name=${encodeURIComponent(proof.user!)}`, { headers })\n if (nameResp.status === 404) return { verified: false, reason: `Could not resolve Farcaster user \"${proof.user}\"` }\n if (!nameResp.ok) return { verified: false, reason: `Couldn't check: the Farcaster node (${host}) returned ${nameResp.status}` }\n const fid = (await nameResp.json()).fid\n if (!fid) return { verified: false, reason: `Could not resolve Farcaster user \"${proof.user}\"` }\n\n let pageToken = ''\n for (let page = 0; page < 5; page++) {\n const url = `${base}/v1/castsByFid?fid=${fid}&pageSize=100&reverse=true${pageToken ? `&pageToken=${pageToken}` : ''}`\n const resp = await fetch(url, { headers })\n if (!resp.ok) return { verified: false, reason: `Couldn't check: the Farcaster node (${host}) returned ${resp.status}` }\n const data = await resp.json()\n\n for (const msg of data.messages || []) {\n if (msg.hash && msg.hash.startsWith(proof.castHash!)) {\n const text = msg.data?.castAddBody?.text || ''\n if (containsFingerprint(text, fingerprint)) {\n return { verified: true }\n }\n // A short cast-hash prefix can match multiple casts; keep scanning\n // rather than stopping at the first prefix match without the token.\n }\n }\n\n if (!data.nextPageToken) break\n pageToken = data.nextPageToken\n }\n\n return { verified: false, reason: 'Cast not found' }\n } catch (err: any) {\n return { verified: false, reason: `Couldn't check: the Farcaster node (${host}) didn't answer (${err.message})` }\n }\n}\n\nasync function verifyCodeberg(proof: Proof, fingerprint: string): Promise<PGPVerification> {\n try {\n const resp = await fetch(\n `https://codeberg.org/api/v1/repos/${encodeURIComponent(proof.user!)}/${encodeURIComponent(proof.repo!)}`,\n )\n if (!resp.ok) return { verified: false, reason: `Codeberg API returned ${resp.status}` }\n const data = await resp.json()\n\n if (data.description && containsFingerprint(data.description, fingerprint)) {\n return { verified: true }\n }\n\n return { verified: false, reason: 'Fingerprint token not found in repo description' }\n } catch (err: any) {\n return { verified: false, reason: `Codeberg fetch failed: ${err.message}` }\n }\n}\n\nasync function verifyMastodon(proof: Proof, fingerprint: string): Promise<PGPVerification> {\n try {\n // The instance host is user-supplied and interpolated into the request URL,\n // so restrict it to a bare hostname (optional port) before use — otherwise a\n // value such as \"mastodon.social@evil.com\" would send the lookup to another\n // server while still displaying as a legitimate handle.\n if (!proof.instance || !/^[a-z0-9.-]+(?::\\d+)?$/i.test(proof.instance)) {\n return { verified: false, reason: 'Invalid Mastodon instance host' }\n }\n const resp = await fetch(\n `https://${proof.instance}/api/v1/accounts/lookup?acct=${encodeURIComponent(proof.user!)}`,\n )\n if (!resp.ok) return { verified: false, reason: `Mastodon API returned ${resp.status}` }\n const data = await resp.json()\n\n const keyId = keyIdHex(fingerprint.replace(/^0x/i, '').replace(/\\s+/g, '').toLowerCase())\n\n for (const field of data.fields || []) {\n const text = field.value.replace(/<[^>]*>/g, '')\n if (containsFingerprintUrl(text, fingerprint) || containsFingerprintUrl(text, keyId)) {\n return { verified: true }\n }\n }\n\n if (data.note) {\n const text = data.note.replace(/<[^>]*>/g, '')\n if (containsFingerprintUrl(text, fingerprint) || containsFingerprintUrl(text, keyId)) {\n return { verified: true }\n }\n }\n\n return { verified: false, reason: 'Fingerprint not found in profile fields or bio' }\n } catch (err: any) {\n return { verified: false, reason: `Mastodon fetch failed: ${err.message}` }\n }\n}\n\ntype Verifier = (proof: Proof, fingerprint: string, opts?: ProofOptions) => Promise<PGPVerification>\n\nconst verifiers: Record<string, Verifier> = {\n github: verifyGitHub,\n dns: verifyDNS,\n farcaster: verifyFarcaster,\n codeberg: verifyCodeberg,\n mastodon: verifyMastodon,\n}\n\n/**\n * Check one proof. The third argument is options; a plain string is still accepted as a\n * Neynar API key (the pre-1.3.7 signature).\n */\nexport async function verifyProof(\n proof: Proof,\n fingerprint: string,\n opts?: ProofOptions | string,\n): Promise<PGPVerification> {\n const fn = verifiers[proof.provider]\n if (!fn) return { verified: false, reason: 'Unknown provider' }\n return fn(proof, fingerprint, typeof opts === 'string' ? { neynarApiKey: opts } : (opts ?? {}))\n}\n","/**\n * ENS avatars without the tracking pixel. The `avatar` record is a link the name's owner\n * chooses; loading an ordinary https:// link would tell that owner's server the IP and time of\n * everyone who views the card. So only images the owner can't watch load: content-addressed\n * ones (IPFS, Arweave, inline data, or an NFT whose metadata and image are content-addressed)\n * through public gateways, and euc.li, ENS Labs' host behind the ENS app's avatar upload\n * (most avatars; ENS Labs sees the view, the name's owner doesn't). Anything else: no avatar.\n */\n\n/**\n * Public IPFS gateways, tried in order: when an image fails to load, the next one is used.\n * Any single public gateway can stop serving (ipfs.io and dweb.link went \"service worker only\"\n * in 2026), so there is always a fallback. The gateway sees the viewer's IP and the avatar.\n */\nexport const IPFS_GATEWAYS = ['https://ipfs.filebase.io/ipfs/', 'https://gateway.pinata.cloud/ipfs/'] as const\n/** The first gateway in IPFS_GATEWAYS (kept for code that imported it before 1.3.6). */\nexport const IPFS_GATEWAY: string = IPFS_GATEWAYS[0]\nexport const ARWEAVE_GATEWAY = 'https://arweave.net/'\n\n/** An image URL safe to load for `uri`, or null if loading it would reach a host the owner picked. */\nexport function avatarUrl(uri: unknown): string | null {\n if (typeof uri !== 'string') return null\n const u = uri.trim()\n if (/^data:image\\/(png|jpe?g|gif|webp|avif|svg\\+xml)[;,]/i.test(u)) return u\n let m = u.match(/^ipfs:\\/\\/(?:ipfs\\/)?([a-zA-Z0-9]{46,}(?:\\/[^?#\\s]*)?)$/)\n if (m) return IPFS_GATEWAY + m[1]\n m = u.match(/^ar:\\/\\/([a-zA-Z0-9_-]{43}(?:\\/[^?#\\s]*)?)$/)\n if (m) return ARWEAVE_GATEWAY + m[1]\n if (/^https:\\/\\/euc\\.li\\/[^?#\\s]+$/.test(u)) return u\n // A gateway link someone pasted (https://any-gateway/ipfs/<cid>/…): same content, our gateway.\n m = u.match(/^https:\\/\\/[^/]+\\/ipfs\\/([a-zA-Z0-9]{46,}(?:\\/[^?#\\s]*)?)$/)\n if (m) return IPFS_GATEWAY + m[1]\n return null\n}\n\n/** The same content through the other IPFS gateways, for retrying a failed load. [] for non-IPFS URLs. */\nexport function avatarFallbacks(url: string | null | undefined): string[] {\n if (!url || !url.startsWith(IPFS_GATEWAYS[0])) return []\n const path = url.slice(IPFS_GATEWAYS[0].length)\n return IPFS_GATEWAYS.slice(1).map((g) => g + path)\n}\n\nexport interface NftAvatar { chainId: number; standard: 'erc721' | 'erc1155'; contract: `0x${string}`; tokenId: bigint }\n\n/** `eip155:1/erc721:0x…/123` (or erc1155; any case, as records in the wild vary) → its parts; null otherwise. */\nexport function parseNftAvatar(raw: unknown): NftAvatar | null {\n const m = typeof raw === 'string' && raw.trim().match(/^eip155:(\\d+)\\/(erc721|erc1155):(0x[0-9a-fA-F]{40})\\/(\\d+)$/i)\n return m ? { chainId: Number(m[1]), standard: m[2].toLowerCase() as NftAvatar['standard'], contract: m[3] as `0x${string}`, tokenId: BigInt(m[4]) } : null\n}\n\nexport const NFT_AVATAR_ABI = [\n { type: 'function', name: 'tokenURI', stateMutability: 'view', inputs: [{ type: 'uint256' }], outputs: [{ type: 'string' }] },\n { type: 'function', name: 'uri', stateMutability: 'view', inputs: [{ type: 'uint256' }], outputs: [{ type: 'string' }] },\n] as const\n\n/**\n * The image for an NFT avatar, given the token's metadata URI (from `tokenURI` / `uri`).\n * The metadata must be inline JSON or content-addressed, and so must its image.\n */\nexport async function nftAvatarImage(tokenUri: string, tokenId: bigint, fetchFn: typeof fetch = fetch): Promise<string | null> {\n // ERC-1155 metadata URIs carry an {id} placeholder: 64 hex chars, lowercase, no 0x.\n const uri = tokenUri.replace('{id}', tokenId.toString(16).padStart(64, '0'))\n let meta: any\n const inline = uri.match(/^data:application\\/json(;base64)?,([\\s\\S]*)$/)\n if (inline) meta = JSON.parse(inline[1] ? atob(inline[2]) : decodeURIComponent(inline[2]))\n else {\n const url = uri.startsWith('data:') ? null : avatarUrl(uri)\n if (!url) return null\n for (const candidate of [url, ...avatarFallbacks(url)]) {\n try {\n const res = await fetchFn(candidate)\n if (res.ok) { meta = await res.json(); break }\n } catch { /* try the next gateway */ }\n }\n if (!meta) return null\n }\n return avatarUrl(meta?.image ?? meta?.image_url)\n}\n","import { namehash, normalize } from 'viem/ens'\nimport { normalizeFingerprint } from './fingerprint'\n\n/**\n * The `id.thurin` ENS text record: a name points at the PGP key claimed on the Thurin\n * registry by the address the name resolves to. It is a discovery hint, not part of the\n * trust chain — the name owner set the address, the address signed the claim, so a reader\n * verifies by resolving the name and reading the registry. The record only says where to\n * look. `id.thurin` is a service key under ENSIP-5 (reverse-dot of thurin.id); ENSIP-18\n * says service-key values are bare, so the value is the 40-hex fingerprint, uppercase.\n */\nexport const ENS_HINT_KEY = 'id.thurin' as const\n\nexport type EnsHintState = 'match' | 'unset' | 'mismatch'\n\nexport interface EnsHint {\n key: typeof ENS_HINT_KEY\n state: EnsHintState\n /** The raw record value, or null when the name carries no record. */\n record: string | null\n /** The record as a lowercase fingerprint, or null when it does not parse as one. */\n fingerprint: string | null\n /** The verified claim's fingerprint (lowercase) the record is compared against, if any. */\n expected: string | null\n /** Why a record does not match; absent for `match` and `unset`. */\n reason?: string\n}\n\n/**\n * Compare a name's `id.thurin` record with the fingerprint the registry verifies for the\n * name's address. Parsing is lenient (case, spaces, 0x); what Thurin writes is strict.\n */\nexport function ensHintFor(record: string | null | undefined, verifiedFingerprint: string | null | undefined): EnsHint {\n const raw = record?.trim() ?? ''\n const expected = verifiedFingerprint ? normalizeFingerprint(verifiedFingerprint) : null\n if (!raw) return { key: ENS_HINT_KEY, state: 'unset', record: null, fingerprint: null, expected }\n const fingerprint = normalizeFingerprint(raw)\n if (!fingerprint) return { key: ENS_HINT_KEY, state: 'mismatch', record: raw, fingerprint: null, expected, reason: 'The record is not a PGP fingerprint' }\n if (!expected) return { key: ENS_HINT_KEY, state: 'mismatch', record: raw, fingerprint, expected, reason: 'The address has no verified claim' }\n if (fingerprint === expected) return { key: ENS_HINT_KEY, state: 'match', record: raw, fingerprint, expected }\n return { key: ENS_HINT_KEY, state: 'mismatch', record: raw, fingerprint, expected, reason: 'The record names a key this address has not claimed' }\n}\n\n/** The value Thurin writes: the fingerprint, uppercase, bare. */\nexport function ensHintValue(fingerprint: string): string {\n const fp = normalizeFingerprint(fingerprint)\n if (!fp) throw new Error(`Not a fingerprint: ${fingerprint}`)\n return fp.toUpperCase()\n}\n\n/** The `text` / `setText` pair every ENS resolver speaks (ENSIP-5); the same on ENSv2. */\nexport const ENS_TEXT_RESOLVER_ABI = [\n { type: 'function', name: 'text', stateMutability: 'view', inputs: [{ name: 'node', type: 'bytes32' }, { name: 'key', type: 'string' }], outputs: [{ name: '', type: 'string' }] },\n { type: 'function', name: 'setText', stateMutability: 'nonpayable', inputs: [{ name: 'node', type: 'bytes32' }, { name: 'key', type: 'string' }, { name: 'value', type: 'string' }], outputs: [] },\n] as const\n\n/**\n * The `setText` call that links a name to a claim. The resolver address is not part of it\n * on purpose: look it up at write time (`client.getEnsResolver({ name })`) — on ENSv2 every\n * account has its own resolver, so a cached or hardcoded one is the wrong contract.\n */\nexport function ensHintWrite(name: string, fingerprint: string) {\n const normalized = normalize(name)\n return {\n abi: ENS_TEXT_RESOLVER_ABI,\n functionName: 'setText' as const,\n args: [namehash(normalized), ENS_HINT_KEY, ensHintValue(fingerprint)] as const,\n name: normalized,\n }\n}\n\n/** Minimal client shape: viem's PublicClient has both. */\nexport interface EnsTextReader {\n getEnsText(args: { name: string; key: string }): Promise<string | null>\n}\n\n/** Read a name's `id.thurin` record and compare it with the verified fingerprint. */\nexport async function fetchEnsHint(client: EnsTextReader, name: string, verifiedFingerprint: string | null | undefined): Promise<EnsHint> {\n const record = await client.getEnsText({ name: normalize(name), key: ENS_HINT_KEY })\n return ensHintFor(record, verifiedFingerprint)\n}\n","import { hashTypedData, keccak256, stringToHex, type Hex, type TypedDataDomain } from 'viem'\nimport { fingerprintToBytes } from './fingerprint'\n\n/**\n * EIP-712 authorizations for the registry's relayed writes (`attestFor`, `reattestFor`,\n * `updateKeyFor`, `revokeFor`, `setRecordFor`). The owner signs one of these typed\n * structs; anyone can then submit the matching call and pay the gas. Every field is\n * bound, plus the owner's current `nonce` (read with `nonces(owner)`) and a `deadline`.\n *\n * The domain includes the chain id, so an authorization for one network cannot be\n * replayed on another even though the registry has the same address everywhere.\n */\nexport const EIP712_NAME = 'Thurin PGPRegistry'\nexport const EIP712_VERSION = '2'\n\nexport function registryDomain(chainId: number, verifyingContract: Hex): TypedDataDomain {\n return { name: EIP712_NAME, version: EIP712_VERSION, chainId, verifyingContract }\n}\n\nexport const AUTHORIZATION_TYPES = {\n Attest: [\n { name: 'owner', type: 'address' },\n { name: 'fingerprint', type: 'bytes' },\n { name: 'pgpSignature', type: 'bytes' },\n { name: 'pgpPublicKey', type: 'bytes' },\n { name: 'nonce', type: 'uint256' },\n { name: 'deadline', type: 'uint256' },\n ],\n Reattest: [\n { name: 'owner', type: 'address' },\n { name: 'revokeIndex', type: 'uint256' },\n { name: 'fingerprint', type: 'bytes' },\n { name: 'pgpSignature', type: 'bytes' },\n { name: 'pgpPublicKey', type: 'bytes' },\n { name: 'nonce', type: 'uint256' },\n { name: 'deadline', type: 'uint256' },\n ],\n UpdateKey: [\n { name: 'owner', type: 'address' },\n { name: 'index', type: 'uint256' },\n { name: 'pgpPublicKey', type: 'bytes' },\n { name: 'nonce', type: 'uint256' },\n { name: 'deadline', type: 'uint256' },\n ],\n Revoke: [\n { name: 'owner', type: 'address' },\n { name: 'index', type: 'uint256' },\n { name: 'nonce', type: 'uint256' },\n { name: 'deadline', type: 'uint256' },\n ],\n SetRecord: [\n { name: 'owner', type: 'address' },\n { name: 'index', type: 'uint256' },\n { name: 'kind', type: 'bytes32' },\n { name: 'value', type: 'bytes' },\n { name: 'nonce', type: 'uint256' },\n { name: 'deadline', type: 'uint256' },\n ],\n} as const\n\nexport type AuthorizationAction = keyof typeof AUTHORIZATION_TYPES\n\ninterface Common { owner: Hex; nonce: bigint; deadline: bigint }\nexport type AttestAuthorization = Common & { fingerprint: string; pgpSignature: string; pgpPublicKey: string }\nexport type ReattestAuthorization = AttestAuthorization & { revokeIndex: bigint }\nexport type UpdateKeyAuthorization = Common & { index: bigint; pgpPublicKey: string }\nexport type RevokeAuthorization = Common & { index: bigint }\nexport type SetRecordAuthorization = Common & { index: bigint; kind: Hex; value: Hex }\n\n/**\n * Build the object to pass to viem/wagmi `signTypedData`. `fingerprint` is the hex\n * fingerprint; `pgpSignature` / `pgpPublicKey` are the armored text exactly as they\n * will be submitted (they are hashed as UTF-8 bytes).\n */\nexport function attestTypedData(chainId: number, registry: Hex, a: AttestAuthorization) {\n return {\n domain: registryDomain(chainId, registry),\n types: { Attest: AUTHORIZATION_TYPES.Attest },\n primaryType: 'Attest' as const,\n message: {\n owner: a.owner,\n fingerprint: fingerprintToBytes(a.fingerprint),\n pgpSignature: stringToHex(a.pgpSignature),\n pgpPublicKey: stringToHex(a.pgpPublicKey),\n nonce: a.nonce,\n deadline: a.deadline,\n },\n }\n}\n\nexport function reattestTypedData(chainId: number, registry: Hex, a: ReattestAuthorization) {\n const base = attestTypedData(chainId, registry, a)\n return {\n domain: base.domain,\n types: { Reattest: AUTHORIZATION_TYPES.Reattest },\n primaryType: 'Reattest' as const,\n message: { ...base.message, revokeIndex: a.revokeIndex },\n }\n}\n\nexport function updateKeyTypedData(chainId: number, registry: Hex, a: UpdateKeyAuthorization) {\n return {\n domain: registryDomain(chainId, registry),\n types: { UpdateKey: AUTHORIZATION_TYPES.UpdateKey },\n primaryType: 'UpdateKey' as const,\n message: { owner: a.owner, index: a.index, pgpPublicKey: stringToHex(a.pgpPublicKey), nonce: a.nonce, deadline: a.deadline },\n }\n}\n\nexport function revokeTypedData(chainId: number, registry: Hex, a: RevokeAuthorization) {\n return {\n domain: registryDomain(chainId, registry),\n types: { Revoke: AUTHORIZATION_TYPES.Revoke },\n primaryType: 'Revoke' as const,\n message: { owner: a.owner, index: a.index, nonce: a.nonce, deadline: a.deadline },\n }\n}\n\nexport function setRecordTypedData(chainId: number, registry: Hex, a: SetRecordAuthorization) {\n return {\n domain: registryDomain(chainId, registry),\n types: { SetRecord: AUTHORIZATION_TYPES.SetRecord },\n primaryType: 'SetRecord' as const,\n message: { owner: a.owner, index: a.index, kind: a.kind, value: a.value, nonce: a.nonce, deadline: a.deadline },\n }\n}\n\n/** The digest the registry recovers the signer from — handy for tests and CLIs. */\nexport function authorizationDigest(typedData: ReturnType<typeof attestTypedData | typeof reattestTypedData | typeof updateKeyTypedData | typeof revokeTypedData | typeof setRecordTypedData>): Hex {\n return hashTypedData(typedData as any)\n}\n\n/** `bytes32 kind` for a record from a human-readable name, e.g. recordKind('thurin.vouch'). */\nexport function recordKind(name: string): Hex {\n return keccak256(stringToHex(name))\n}\n","import { hexToString, stringToHex, type Hex } from 'viem'\nimport { normalizeFingerprint } from './fingerprint'\nimport { recordKind } from './authorization'\nimport { verifyClearsigned } from './pgp'\nexport { recordKind }\n\n/**\n * Records: one small value per claim per kind, set only by the owner (or by anyone holding\n * the owner's `setRecordFor` authorization), readable by anyone, clearable. The contract looks\n * a record up by kind and cannot list them, so readers ask for the kinds they know.\n *\n * Conventions: kind = keccak256(\"thurin.<name>\"); simple kinds are UTF-8 text, structured\n * kinds small JSON with a `v`, encrypted kinds an armored PGP message; readers ignore unknown\n * fields. Third parties use reverse-dot names (`com.example.thing`) without asking. 1 KB max.\n */\nexport const MAX_RECORD_BYTES = 1024\n\n/** `thurin.` is the default namespace: kindName('canary') === 'thurin.canary'. Dotted names pass through. */\nexport function kindName(input: string): string {\n return input.includes('.') ? input : `thurin.${input}`\n}\n\n/** Every kind Thurin defines. `thurin.pointer` is the Thurin Labs release list (CLI only; not shown on identity pages). */\nexport const KNOWN_KINDS = [\n 'thurin.railgun', 'thurin.security', 'thurin.successor', 'thurin.affiliation', 'thurin.canary',\n 'thurin.private', 'thurin.disclosure', 'thurin.pointer',\n] as const\nexport type KnownKind = (typeof KNOWN_KINDS)[number]\n\n/** The kinds an identity page shows, in display order. */\nexport const IDENTITY_KINDS: readonly KnownKind[] = [\n 'thurin.railgun', 'thurin.security', 'thurin.successor', 'thurin.affiliation', 'thurin.canary',\n 'thurin.private', 'thurin.disclosure',\n]\n\nexport function encodeRecord(value: string): Hex {\n const bytes = new TextEncoder().encode(value).length\n if (bytes > MAX_RECORD_BYTES) throw new Error(`Record is ${bytes} bytes; the registry accepts up to ${MAX_RECORD_BYTES}`)\n return stringToHex(value)\n}\n\nexport function decodeRecord(hex: Hex): string {\n return hex === '0x' ? '' : hexToString(hex)\n}\n\n// ─── thurin.pointer (release list; Thurin Labs' own) ───────────────────────────────────────\n\nexport interface PointerEntry { name: string; sha256: string; date: string; url?: string }\nexport interface PointerRecord { v: 1; releases: PointerEntry[] }\n\nexport function parsePointer(text: string): PointerRecord {\n const p = JSON.parse(text)\n if (p?.v !== 1 || !Array.isArray(p.releases)) throw new Error('Not a v1 thurin.pointer record')\n return p\n}\n\n/** Add a release, newest first; drop the oldest until it fits the 1 KB slot. */\nexport function addPointer(existing: PointerRecord | null, entry: PointerEntry): { record: PointerRecord; dropped: PointerEntry[] } {\n if (!/^[0-9a-f]{64}$/i.test(entry.sha256)) throw new Error('sha256 must be 64 hex characters')\n const releases = [entry, ...(existing?.releases ?? []).filter(r => r.name !== entry.name)]\n const dropped: PointerEntry[] = []\n const record: PointerRecord = { v: 1, releases }\n while (new TextEncoder().encode(JSON.stringify(record)).length > MAX_RECORD_BYTES && releases.length > 1) dropped.push(releases.pop()!)\n return { record, dropped }\n}\n\nexport function renderPointer(p: PointerRecord): string {\n return p.releases.map(r => `${r.name.padEnd(22)} ${r.date} sha256 ${r.sha256}${r.url ? ` ${r.url}` : ''}`).join('\\n')\n}\n\n// ─── The user-facing kinds ─────────────────────────────────────────────────────────────────\n\n/** A record as the identity page shows it: what was stored, and what it means if it parses. */\nexport interface ParsedRecord {\n kind: string\n /** The raw UTF-8 value. */\n text: string\n bytes: number\n /** True when the value has the shape the kind defines. A malformed record is still shown, as text. */\n valid: boolean\n /** Why it is not valid, when it is not. */\n reason?: string\n /** Kind-specific fields, when valid. */\n data: RecordData\n}\n\nexport type RecordData =\n | { type: 'railgun'; address: string }\n | { type: 'security'; contact: string; url: string | null }\n | { type: 'successor'; fingerprint: string }\n | { type: 'affiliation'; with: string; role: string | null }\n | { type: 'canary'; date: string; statement: string; clearsigned: boolean; verified: boolean | null; reason?: string }\n | { type: 'encrypted'; recipients: number | null }\n | { type: 'pointer'; releases: PointerEntry[] }\n | { type: 'text' }\n\n/** Railgun 0zk addresses: bech32m with the `0zk` prefix (`0zk1…`, 127 chars). Charset and length only; the wallet checks the checksum. */\nconst RAILGUN_ADDRESS = /^0zk1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{60,200}$/\nconst ISO_DATE = /\\b(\\d{4}-\\d{2}-\\d{2})\\b/\n\n/**\n * Parse a record of a known kind. Never throws: a value that does not fit its kind comes back\n * with `valid: false` and a reason, so a page can still show what is there. Pass the claim's\n * `armoredKey` to check clearsigned kinds (`thurin.canary`) against it: `verified` is then\n * true or false, and null when no key was given.\n */\nexport async function parseRecord(kind: string, text: string, opts: { armoredKey?: string } = {}): Promise<ParsedRecord> {\n const bytes = new TextEncoder().encode(text).length\n const base = { kind, text, bytes }\n const invalid = (reason: string, data: RecordData = { type: 'text' }): ParsedRecord => ({ ...base, valid: false, reason, data })\n const t = text.trim()\n switch (kind) {\n case 'thurin.railgun': {\n if (!RAILGUN_ADDRESS.test(t)) return invalid('Not a 0zk address')\n return { ...base, valid: true, data: { type: 'railgun', address: t } }\n }\n case 'thurin.security': {\n if (!t) return invalid('Empty')\n const url = /^https?:\\/\\/\\S+$/.test(t) ? t : null\n return { ...base, valid: true, data: { type: 'security', contact: t, url } }\n }\n case 'thurin.successor': {\n const fp = normalizeFingerprint(t)\n if (!fp) return invalid('Not a PGP fingerprint')\n return { ...base, valid: true, data: { type: 'successor', fingerprint: fp } }\n }\n case 'thurin.affiliation': {\n try {\n const j = JSON.parse(t)\n if (j?.v !== 1 || typeof j.with !== 'string' || !j.with) return invalid('Not a v1 affiliation: expected {\"v\":1,\"with\":\"<address or name>\"}')\n return { ...base, valid: true, data: { type: 'affiliation', with: j.with, role: typeof j.role === 'string' ? j.role : null } }\n } catch { return invalid('Not JSON') }\n }\n case 'thurin.canary': {\n const date = t.match(ISO_DATE)?.[1]\n if (!date) return invalid('No date in the statement')\n const clearsigned = t.startsWith('-----BEGIN PGP SIGNED MESSAGE-----')\n let statement = clearsigned ? (t.split(/\\r?\\n\\r?\\n/)[1] ?? '').split('-----BEGIN PGP SIGNATURE-----')[0].trim() : t\n let verified: boolean | null = null, reason: string | undefined\n if (clearsigned && opts.armoredKey) {\n const v = await verifyClearsigned({ armoredKey: opts.armoredKey, clearsigned: t })\n verified = v.verified; reason = v.reason\n if (v.text) statement = v.text.trim()\n }\n return { ...base, valid: true, data: { type: 'canary', date, statement, clearsigned, verified, ...(reason ? { reason } : {}) } }\n }\n case 'thurin.private':\n case 'thurin.disclosure': {\n if (!t.startsWith('-----BEGIN PGP MESSAGE-----')) return invalid('Not a PGP message')\n let recipients: number | null = null\n try {\n const { readMessage } = await import('openpgp') // lazy, like pgp.ts: a static import breaks under jsdom\n recipients = (await readMessage({ armoredMessage: t })).getEncryptionKeyIDs().length\n } catch { return invalid('PGP message does not parse') }\n return { ...base, valid: true, data: { type: 'encrypted', recipients } }\n }\n case 'thurin.pointer': {\n try { return { ...base, valid: true, data: { type: 'pointer', releases: parsePointer(t).releases } } }\n catch { return invalid('Not a v1 pointer record') }\n }\n default:\n return { ...base, valid: true, data: { type: 'text' } }\n }\n}\n\n/** Minimal client shape: viem's PublicClient has it. */\nexport interface RecordReader {\n readContract(args: { address: `0x${string}`; abi: unknown; functionName: 'record'; args: readonly [`0x${string}`, bigint, Hex] }): Promise<unknown>\n}\n\n/**\n * Read and parse the given kinds on one claim. Kinds with no record are left out.\n * `registry` is the contract address; pass the kit's REGISTRY_ABI.\n */\nexport async function fetchRecords(\n client: RecordReader, registry: `0x${string}`, abi: unknown,\n owner: `0x${string}`, index: number | bigint, kinds: readonly string[] = IDENTITY_KINDS, opts: { armoredKey?: string } = {},\n): Promise<ParsedRecord[]> {\n const out: ParsedRecord[] = []\n for (const kind of kinds) {\n const hex = await client.readContract({ address: registry, abi, functionName: 'record', args: [owner, BigInt(index), recordKind(kind)] }) as Hex\n const text = decodeRecord(hex)\n if (text) out.push(await parseRecord(kind, text, opts))\n }\n return out\n}\n"]}