@thurinlabs/identity-kit 1.3.6 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/core.d.ts CHANGED
@@ -19,9 +19,24 @@ interface Attestation {
19
19
  pgpPublicKey: string | null;
20
20
  verification: PGPVerification | null;
21
21
  }
22
+ /** Why a claim does or doesn't count, in a form a UI can word (see CLAIM_CHECK_LABEL). Since 1.4.0. */
23
+ type ClaimCheckKind = 'verified' | 'expired' | 'signing-key-expired' | 'revoked' | 'compromised' | 'signing-key-revoked' | 'unsupported' | 'bad-signature';
22
24
  interface PGPVerification {
23
25
  verified: boolean;
26
+ /** The underlying library message, for logs; show `kind` to people instead. */
24
27
  reason?: string;
28
+ /** Since 1.4.0. */
29
+ kind?: ClaimCheckKind;
30
+ /** ISO date that goes with `kind`: when the key or subkey expired or was revoked. */
31
+ at?: string | null;
32
+ /** The owner's revocation reason (their words, or the reason code's name). */
33
+ revocationReason?: string | null;
34
+ /** Fingerprint of the key or subkey that made the claim's signature. */
35
+ signingKey?: string | null;
36
+ /** When verified: the earlier expiry of the key and the signing subkey (ISO), or null if neither expires. */
37
+ expiresAt?: string | null;
38
+ /** For `unsupported`: the algorithm, e.g. "DSA 2048". */
39
+ algorithm?: string | null;
25
40
  }
26
41
  interface PGPKeyInfo {
27
42
  fingerprint: string;
@@ -90,7 +105,10 @@ interface ThurinIdentity {
90
105
  type Theme = 'thurin' | 'dark' | 'light';
91
106
  interface IdentityKitConfig {
92
107
  rpcUrl?: string;
108
+ /** Optional: read Farcaster proofs through Neynar's hub with this key. Not needed since 1.3.7. */
93
109
  neynarApiKey?: string;
110
+ /** A Farcaster node's HTTP API for Farcaster proofs. Default: Quilibrium's public Hypersnap node (keyless). */
111
+ farcasterHub?: string;
94
112
  baseUrl?: string;
95
113
  /** Which PGPRegistry deployment to read: 'mainnet' (default) or 'sepolia'. */
96
114
  network?: 'mainnet' | 'sepolia' | 'local';
@@ -1715,7 +1733,24 @@ declare function identifyProof(notation: Notation): Proof | null;
1715
1733
  declare function displayUrl(proof: Proof): string;
1716
1734
  declare function proofHref(proof: Proof): string | null;
1717
1735
  declare function proofSecondaryHref(proof: Proof): string | null;
1718
- declare function verifyProof(proof: Proof, fingerprint: string, neynarApiKey?: string): Promise<PGPVerification>;
1736
+ /**
1737
+ * Farcaster proofs are read from a Farcaster node's HTTP API (`/v1/userNameProofByName`,
1738
+ * `/v1/castsByFid`). The default is Quilibrium's public Hypersnap node, which needs no key and
1739
+ * allows browser requests; it sees the visitor's IP and which account was checked. Point
1740
+ * `farcasterHub` at your own node to avoid that, or pass a Neynar key to use Neynar's hub.
1741
+ */
1742
+ declare const FARCASTER_HUB = "https://haatz.quilibrium.com";
1743
+ interface ProofOptions {
1744
+ /** A Farcaster node's HTTP API base URL. Default: FARCASTER_HUB (keyless). */
1745
+ farcasterHub?: string;
1746
+ /** Optional: read Farcaster through Neynar's hub with this key instead. */
1747
+ neynarApiKey?: string;
1748
+ }
1749
+ /**
1750
+ * Check one proof. The third argument is options; a plain string is still accepted as a
1751
+ * Neynar API key (the pre-1.3.7 signature).
1752
+ */
1753
+ declare function verifyProof(proof: Proof, fingerprint: string, opts?: ProofOptions | string): Promise<PGPVerification>;
1719
1754
 
1720
1755
  declare function parsePgpKey(armoredKey: string): Promise<PGPKeyInfo | null>;
1721
1756
  /**
@@ -1802,6 +1837,54 @@ declare function needsRpcProbe(s: Omit<IdentityLookupState, 'rpcAnswered'>): boo
1802
1837
  declare function identityErrorKind(s: IdentityLookupState): IdentityErrorKind | null;
1803
1838
  declare const IDENTITY_ERROR_TEXT: Record<IdentityErrorKind, string>;
1804
1839
 
1840
+ /**
1841
+ * Plain words for a claim's state, shared by thurin.id, the CLI, and anyone embedding the kit,
1842
+ * so every surface says the same thing. `sentence` is for visitors; `fix` is for the owner only
1843
+ * (thurin.id shows it when the connected wallet owns the claim).
1844
+ */
1845
+ /** Badge text for each check result. */
1846
+ declare const CLAIM_CHECK_LABEL: Record<ClaimCheckKind, string>;
1847
+ /** "Mar 5, 2029", in UTC so every viewer sees the same day. */
1848
+ declare function formatClaimDate(isoOrSeconds: string | number | null | undefined): string;
1849
+ interface ClaimCheckText {
1850
+ kind: ClaimCheckKind;
1851
+ label: string;
1852
+ sentence: string;
1853
+ /** What the owner can do about it, when there is something. */
1854
+ fix: string | null;
1855
+ }
1856
+ /** Label, visitor sentence, and owner fix for a verification result. */
1857
+ declare function claimCheckText(v: PGPVerification, formatDate?: (iso: string | null | undefined) => string): ClaimCheckText;
1858
+ /** Days until a verified claim's key (or signing subkey) expires, when that is within `withinDays`. */
1859
+ declare function expiresSoon(v: PGPVerification | null | undefined, now?: number, withinDays?: number): {
1860
+ days: number;
1861
+ at: string;
1862
+ } | null;
1863
+ /** "Key expires in 12 days (Mar 6, 2027)." */
1864
+ declare function expiresSoonText(soon: {
1865
+ days: number;
1866
+ at: string;
1867
+ }, formatDate?: (iso: string) => string): string;
1868
+ type ClaimFate = {
1869
+ state: 'active';
1870
+ } | {
1871
+ state: 'revoked';
1872
+ at: number;
1873
+ } | {
1874
+ state: 'replaced';
1875
+ at: number;
1876
+ by: number;
1877
+ };
1878
+ /**
1879
+ * Active, revoked, or replaced, for each of one owner's claims (keyed by index). `reattest`
1880
+ * revokes and attests in one transaction, so a claim revoked at the very second a newer claim
1881
+ * of the same owner was created counts as replaced by it. The contract has no "superseded"
1882
+ * event; this reads the same fact from the stored timestamps.
1883
+ */
1884
+ declare function claimFates(claims: Pick<Attestation, 'index' | 'createdAt' | 'revokedAt'>[]): Map<number, ClaimFate>;
1885
+ /** "Revoked by its owner on Oct 3, 2026." / "Replaced by claim #2 on Oct 3, 2026." */
1886
+ declare function claimFateText(fate: ClaimFate, formatDate?: (seconds: number) => string): string | null;
1887
+
1805
1888
  /**
1806
1889
  * ENS avatars without the tracking pixel. The `avatar` record is a link the name's owner
1807
1890
  * chooses; loading an ordinary https:// link would tell that owner's server the IP and time of
@@ -1875,4 +1958,4 @@ declare function keyIdOf(fingerprint: string): Hex;
1875
1958
  /** A 16-hex long key ID (spaces/0x tolerated) → `bytes8`. */
1876
1959
  declare function keyIdToBytes(keyId: string): Hex | null;
1877
1960
 
1878
- export { ARWEAVE_GATEWAY, AUTHORIZATION_TYPES, type AttestAuthorization, type Attestation, type AuthorizationAction, type EFPGraph, EIP712_NAME, EIP712_VERSION, ENS_HINT_KEY, ENS_TEXT_RESOLVER_ABI, type EnsHint, type EnsHintState, type EnsTextReader, IDENTITY_ERROR_TEXT, IDENTITY_KINDS, IPFS_GATEWAY, IPFS_GATEWAYS, type IdentityErrorKind, type IdentityKitConfig, type IdentityLookupState, KNOWN_KINDS, type KnownKind, MAX_RECORD_BYTES, NETWORKS, NFT_AVATAR_ABI, type NetworkName, type NftAvatar, type Notation, type PGPKeyInfo, type PGPVerification, type ParsedRecord, type PointerEntry, type PointerRecord, type Proof, type ProofResult, REGISTRY_ABI, REGISTRY_ADDRESS, type ReattestAuthorization, type RecordData, type RecordReader, type RegistryDeployment, type RevokeAuthorization, type SetRecordAuthorization, type StrippedKey, type SubkeyInfo, type Theme, type ThurinIdentity, type UpdateKeyAuthorization, addPointer, attestTypedData, authorizationDigest, avatarFallbacks, avatarUrl, bytesToFingerprint, chainFor, decodeRecord, displayUrl, encodeRecord, ensHintFor, ensHintValue, ensHintWrite, fetchEFPGraph, fetchEnsHint, fetchRecords, fingerprintHash, fingerprintToBytes, getRegistry, hasEmailUserID, identifyProof, identityErrorKind, isNetworkName, keyIdOf, keyIdToBytes, kindName, needsRpcProbe, nftAvatarImage, normalizeFingerprint, parseNftAvatar, parsePgpKey, parsePointer, parseRecord, proofHref, proofSecondaryHref, reattestTypedData, recordKind, registryDomain, renderPointer, revokeTypedData, setRecordTypedData, stripEmailUserIDs, updateKeyTypedData, verifyAttestation, verifyClearsigned, verifyProof };
1961
+ export { ARWEAVE_GATEWAY, AUTHORIZATION_TYPES, type AttestAuthorization, type Attestation, type AuthorizationAction, CLAIM_CHECK_LABEL, type ClaimCheckKind, type ClaimCheckText, type ClaimFate, type EFPGraph, EIP712_NAME, EIP712_VERSION, ENS_HINT_KEY, ENS_TEXT_RESOLVER_ABI, type EnsHint, type EnsHintState, type EnsTextReader, FARCASTER_HUB, IDENTITY_ERROR_TEXT, IDENTITY_KINDS, IPFS_GATEWAY, IPFS_GATEWAYS, type IdentityErrorKind, type IdentityKitConfig, type IdentityLookupState, KNOWN_KINDS, type KnownKind, MAX_RECORD_BYTES, NETWORKS, NFT_AVATAR_ABI, type NetworkName, type NftAvatar, type Notation, type PGPKeyInfo, type PGPVerification, type ParsedRecord, type PointerEntry, type PointerRecord, type Proof, type ProofOptions, type ProofResult, REGISTRY_ABI, REGISTRY_ADDRESS, type ReattestAuthorization, type RecordData, type RecordReader, type RegistryDeployment, type RevokeAuthorization, type SetRecordAuthorization, type StrippedKey, type SubkeyInfo, type Theme, type ThurinIdentity, type UpdateKeyAuthorization, addPointer, attestTypedData, authorizationDigest, avatarFallbacks, avatarUrl, bytesToFingerprint, chainFor, claimCheckText, claimFateText, claimFates, decodeRecord, displayUrl, encodeRecord, ensHintFor, ensHintValue, ensHintWrite, expiresSoon, expiresSoonText, fetchEFPGraph, fetchEnsHint, fetchRecords, fingerprintHash, fingerprintToBytes, formatClaimDate, getRegistry, hasEmailUserID, identifyProof, identityErrorKind, isNetworkName, keyIdOf, keyIdToBytes, kindName, needsRpcProbe, nftAvatarImage, normalizeFingerprint, parseNftAvatar, parsePgpKey, parsePointer, parseRecord, proofHref, proofSecondaryHref, reattestTypedData, recordKind, registryDomain, renderPointer, revokeTypedData, setRecordTypedData, stripEmailUserIDs, updateKeyTypedData, verifyAttestation, verifyClearsigned, verifyProof };
package/dist/core.js CHANGED
@@ -1,10 +1,12 @@
1
1
  import {
2
2
  ARWEAVE_GATEWAY,
3
3
  AUTHORIZATION_TYPES,
4
+ CLAIM_CHECK_LABEL,
4
5
  EIP712_NAME,
5
6
  EIP712_VERSION,
6
7
  ENS_HINT_KEY,
7
8
  ENS_TEXT_RESOLVER_ABI,
9
+ FARCASTER_HUB,
8
10
  IDENTITY_ERROR_TEXT,
9
11
  IDENTITY_KINDS,
10
12
  IPFS_GATEWAY,
@@ -22,17 +24,23 @@ import {
22
24
  avatarUrl,
23
25
  bytesToFingerprint,
24
26
  chainFor,
27
+ claimCheckText,
28
+ claimFateText,
29
+ claimFates,
25
30
  decodeRecord,
26
31
  displayUrl,
27
32
  encodeRecord,
28
33
  ensHintFor,
29
34
  ensHintValue,
30
35
  ensHintWrite,
36
+ expiresSoon,
37
+ expiresSoonText,
31
38
  fetchEFPGraph,
32
39
  fetchEnsHint,
33
40
  fetchRecords,
34
41
  fingerprintHash,
35
42
  fingerprintToBytes,
43
+ formatClaimDate,
36
44
  getRegistry,
37
45
  hasEmailUserID,
38
46
  identifyProof,
@@ -61,14 +69,16 @@ import {
61
69
  verifyAttestation,
62
70
  verifyClearsigned,
63
71
  verifyProof
64
- } from "./chunk-HDBHNN4I.js";
72
+ } from "./chunk-ZAQ2EPF5.js";
65
73
  export {
66
74
  ARWEAVE_GATEWAY,
67
75
  AUTHORIZATION_TYPES,
76
+ CLAIM_CHECK_LABEL,
68
77
  EIP712_NAME,
69
78
  EIP712_VERSION,
70
79
  ENS_HINT_KEY,
71
80
  ENS_TEXT_RESOLVER_ABI,
81
+ FARCASTER_HUB,
72
82
  IDENTITY_ERROR_TEXT,
73
83
  IDENTITY_KINDS,
74
84
  IPFS_GATEWAY,
@@ -86,17 +96,23 @@ export {
86
96
  avatarUrl,
87
97
  bytesToFingerprint,
88
98
  chainFor,
99
+ claimCheckText,
100
+ claimFateText,
101
+ claimFates,
89
102
  decodeRecord,
90
103
  displayUrl,
91
104
  encodeRecord,
92
105
  ensHintFor,
93
106
  ensHintValue,
94
107
  ensHintWrite,
108
+ expiresSoon,
109
+ expiresSoonText,
95
110
  fetchEFPGraph,
96
111
  fetchEnsHint,
97
112
  fetchRecords,
98
113
  fingerprintHash,
99
114
  fingerprintToBytes,
115
+ formatClaimDate,
100
116
  getRegistry,
101
117
  hasEmailUserID,
102
118
  identifyProof,
@@ -30400,13 +30400,14 @@ ${prettyStateOverride(stateOverride)}`;
30400
30400
  children,
30401
30401
  rpcUrl,
30402
30402
  neynarApiKey,
30403
+ farcasterHub,
30403
30404
  baseUrl = "https://thurin.id",
30404
30405
  network = "mainnet",
30405
30406
  registryAddress
30406
30407
  }) {
30407
30408
  const config = (0, import_react8.useMemo)(
30408
- () => ({ rpcUrl, neynarApiKey, baseUrl, network, registryAddress }),
30409
- [rpcUrl, neynarApiKey, baseUrl, network, registryAddress]
30409
+ () => ({ rpcUrl, neynarApiKey, farcasterHub, baseUrl, network, registryAddress }),
30410
+ [rpcUrl, neynarApiKey, farcasterHub, baseUrl, network, registryAddress]
30410
30411
  );
30411
30412
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(IdentityKitContext.Provider, { value: config, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(WagmiDetector, { rpcUrl, network, children }) });
30412
30413
  }
@@ -31341,6 +31342,61 @@ ${prettyStateOverride(stateOverride)}`;
31341
31342
  return { verified: false, reason: err instanceof Error ? err.message : "Verification failed", text: "" };
31342
31343
  }
31343
31344
  }
31345
+ var REVOCATION_REASON = {
31346
+ // 0 = "no reason specified", gpg's default: say nothing rather than "(reason: no reason given)"
31347
+ 1: "replaced by a newer key",
31348
+ 2: "compromised",
31349
+ 3: "no longer used",
31350
+ 32: "a user ID is no longer valid"
31351
+ };
31352
+ var iso = (d2) => d2 instanceof Date ? d2.toISOString() : typeof d2 === "number" && Number.isFinite(d2) ? new Date(d2).toISOString() : null;
31353
+ function revocationOf(sigs) {
31354
+ const sig = [...sigs ?? []].sort((a2, b2) => (b2.created?.getTime?.() ?? 0) - (a2.created?.getTime?.() ?? 0))[0];
31355
+ const flag = sig?.reasonForRevocationFlag ?? null;
31356
+ return {
31357
+ at: iso(sig?.created),
31358
+ compromised: flag === 2,
31359
+ revocationReason: sig?.reasonForRevocationString || (flag !== null ? REVOCATION_REASON[flag] ?? null : null)
31360
+ };
31361
+ }
31362
+ async function keyLife(openpgp, key, clearsigned, config) {
31363
+ const now = /* @__PURE__ */ new Date();
31364
+ let issuer = null;
31365
+ try {
31366
+ issuer = (await openpgp.readCleartextMessage({ cleartextMessage: clearsigned })).signature.packets[0]?.issuerKeyID ?? null;
31367
+ } catch {
31368
+ }
31369
+ const signer = issuer && !key.getKeyID().equals(issuer) ? key.subkeys.find((s2) => s2.getKeyID().equals(issuer)) ?? null : key;
31370
+ const signingKey = signer ? signer.getFingerprint().toUpperCase() : null;
31371
+ const safe = async (f2, fallback2) => {
31372
+ try {
31373
+ return await f2();
31374
+ } catch {
31375
+ return fallback2;
31376
+ }
31377
+ };
31378
+ const keyRevoked = await safe(() => key.isRevoked(void 0, void 0, now, config), false);
31379
+ const keyExp = await safe(() => key.getExpirationTime(void 0, config), null);
31380
+ const isSub = signer && signer !== key;
31381
+ const subRevoked = isSub ? await safe(() => signer.isRevoked(null, key.keyPacket, now, config), false) : false;
31382
+ const subExp = isSub ? await safe(() => signer.getExpirationTime(null, config), null) : null;
31383
+ const finite = (d2) => d2 instanceof Date && Number.isFinite(d2.getTime()) ? d2 : null;
31384
+ const kExp = finite(keyExp), sExp = finite(subExp);
31385
+ let problem = null;
31386
+ if (keyRevoked) {
31387
+ const r2 = revocationOf(key.revocationSignatures);
31388
+ problem = { kind: r2.compromised ? "compromised" : "revoked", at: r2.at, revocationReason: r2.revocationReason };
31389
+ } else if (kExp && kExp <= now) {
31390
+ problem = { kind: "expired", at: kExp.toISOString() };
31391
+ } else if (subRevoked) {
31392
+ const r2 = revocationOf(signer.revocationSignatures);
31393
+ problem = { kind: r2.compromised ? "compromised" : "signing-key-revoked", at: r2.at, revocationReason: r2.revocationReason };
31394
+ } else if (sExp && sExp <= now) {
31395
+ problem = { kind: "signing-key-expired", at: sExp.toISOString() };
31396
+ }
31397
+ const earliest = [kExp, sExp].filter(Boolean).sort((a2, b2) => a2 - b2)[0];
31398
+ return { problem, signingKey, expiresAt: earliest ? earliest.toISOString() : null, algorithm: algorithmName(signer ?? key) };
31399
+ }
31344
31400
  async function verifyAttestation({
31345
31401
  pgpPublicKey,
31346
31402
  pgpSignature,
@@ -31349,24 +31405,31 @@ ${prettyStateOverride(stateOverride)}`;
31349
31405
  }) {
31350
31406
  try {
31351
31407
  if (!pgpPublicKey || !pgpSignature) {
31352
- return { verified: false, reason: "Missing PGP data" };
31408
+ return { verified: false, reason: "Missing PGP data", kind: "bad-signature" };
31353
31409
  }
31354
31410
  const openpgp = await Promise.resolve().then(() => (init_openpgp_min(), openpgp_min_exports));
31355
31411
  const { readKey } = openpgp;
31356
31412
  const publicKey = await readKey({ armoredKey: pgpPublicKey });
31357
31413
  const keyFingerprint = publicKey.getFingerprint().toUpperCase();
31358
31414
  if (keyFingerprint !== fingerprint.toUpperCase()) {
31359
- return { verified: false, reason: "Key fingerprint mismatch" };
31415
+ return { verified: false, reason: "Key fingerprint mismatch", kind: "bad-signature" };
31360
31416
  }
31417
+ const life = await keyLife(openpgp, publicKey, pgpSignature, pgpConfig(openpgp));
31361
31418
  const v2 = await verifyClearsigned({ armoredKey: pgpPublicKey, clearsigned: pgpSignature });
31362
- if (!v2.verified) return { verified: false, reason: v2.reason };
31419
+ if (!v2.verified) {
31420
+ if (life.problem) return { verified: false, reason: v2.reason, signingKey: life.signingKey, ...life.problem };
31421
+ if (/too weak|reject|unsupported|not supported|unknown (curve|algorithm)/i.test(v2.reason || "")) {
31422
+ return { verified: false, reason: v2.reason, kind: "unsupported", algorithm: life.algorithm, signingKey: life.signingKey };
31423
+ }
31424
+ return { verified: false, reason: v2.reason, kind: "bad-signature", signingKey: life.signingKey };
31425
+ }
31363
31426
  const signedText = v2.text;
31364
31427
  if (!signedText.toLowerCase().includes(ethAddress.toLowerCase())) {
31365
- return { verified: false, reason: "Signed message does not contain ETH address" };
31428
+ return { verified: false, reason: "Signed message does not contain ETH address", kind: "bad-signature", signingKey: life.signingKey };
31366
31429
  }
31367
- return { verified: true };
31430
+ return { verified: true, kind: "verified", signingKey: life.signingKey, expiresAt: life.expiresAt };
31368
31431
  } catch {
31369
- return { verified: false, reason: "Signature verification failed" };
31432
+ return { verified: false, reason: "Signature verification failed", kind: "bad-signature" };
31370
31433
  }
31371
31434
  }
31372
31435
 
@@ -31640,29 +31703,27 @@ ${prettyStateOverride(stateOverride)}`;
31640
31703
  return { verified: false, reason: `DNS fetch failed: ${err.message}` };
31641
31704
  }
31642
31705
  }
31706
+ var FARCASTER_HUB = "https://haatz.quilibrium.com";
31643
31707
  var NEYNAR_HUB = "https://hub-api.neynar.com";
31644
- async function resolveFid(username, apiKey) {
31645
- const resp = await fetch(
31646
- `${NEYNAR_HUB}/v1/userNameProofByName?name=${encodeURIComponent(username)}`,
31647
- { headers: { "x-api-key": apiKey } }
31648
- );
31649
- if (!resp.ok) return null;
31650
- const data = await resp.json();
31651
- return data.fid ?? null;
31652
- }
31653
- async function verifyFarcaster(proof, fingerprint, neynarApiKey) {
31654
- if (!neynarApiKey) {
31655
- return { verified: false, reason: "Farcaster verification requires a Neynar API key" };
31656
- }
31708
+ function farcasterSource(opts) {
31709
+ if (opts.farcasterHub) return { base: opts.farcasterHub.replace(/\/+$/, ""), headers: {} };
31710
+ if (opts.neynarApiKey) return { base: NEYNAR_HUB, headers: { "x-api-key": opts.neynarApiKey } };
31711
+ return { base: FARCASTER_HUB, headers: {} };
31712
+ }
31713
+ async function verifyFarcaster(proof, fingerprint, opts = {}) {
31714
+ const { base, headers } = farcasterSource(opts);
31715
+ const host = base.replace(/^https?:\/\//, "");
31657
31716
  try {
31658
- const fid = await resolveFid(proof.user, neynarApiKey);
31717
+ const nameResp = await fetch(`${base}/v1/userNameProofByName?name=${encodeURIComponent(proof.user)}`, { headers });
31718
+ if (nameResp.status === 404) return { verified: false, reason: `Could not resolve Farcaster user "${proof.user}"` };
31719
+ if (!nameResp.ok) return { verified: false, reason: `Couldn't check: the Farcaster node (${host}) returned ${nameResp.status}` };
31720
+ const fid = (await nameResp.json()).fid;
31659
31721
  if (!fid) return { verified: false, reason: `Could not resolve Farcaster user "${proof.user}"` };
31660
- const headers = { "x-api-key": neynarApiKey };
31661
31722
  let pageToken = "";
31662
31723
  for (let page = 0; page < 5; page++) {
31663
- const url = `${NEYNAR_HUB}/v1/castsByFid?fid=${fid}&pageSize=100&reverse=true${pageToken ? `&pageToken=${pageToken}` : ""}`;
31724
+ const url = `${base}/v1/castsByFid?fid=${fid}&pageSize=100&reverse=true${pageToken ? `&pageToken=${pageToken}` : ""}`;
31664
31725
  const resp = await fetch(url, { headers });
31665
- if (!resp.ok) return { verified: false, reason: `Farcaster Hub returned ${resp.status}` };
31726
+ if (!resp.ok) return { verified: false, reason: `Couldn't check: the Farcaster node (${host}) returned ${resp.status}` };
31666
31727
  const data = await resp.json();
31667
31728
  for (const msg of data.messages || []) {
31668
31729
  if (msg.hash && msg.hash.startsWith(proof.castHash)) {
@@ -31677,7 +31738,7 @@ ${prettyStateOverride(stateOverride)}`;
31677
31738
  }
31678
31739
  return { verified: false, reason: "Cast not found" };
31679
31740
  } catch (err) {
31680
- return { verified: false, reason: `Farcaster fetch failed: ${err.message}` };
31741
+ return { verified: false, reason: `Couldn't check: the Farcaster node (${host}) didn't answer (${err.message})` };
31681
31742
  }
31682
31743
  }
31683
31744
  async function verifyCodeberg(proof, fingerprint) {
@@ -31730,10 +31791,10 @@ ${prettyStateOverride(stateOverride)}`;
31730
31791
  codeberg: verifyCodeberg,
31731
31792
  mastodon: verifyMastodon
31732
31793
  };
31733
- async function verifyProof(proof, fingerprint, neynarApiKey) {
31794
+ async function verifyProof(proof, fingerprint, opts) {
31734
31795
  const fn2 = verifiers[proof.provider];
31735
31796
  if (!fn2) return { verified: false, reason: "Unknown provider" };
31736
- return fn2(proof, fingerprint, neynarApiKey);
31797
+ return fn2(proof, fingerprint, typeof opts === "string" ? { neynarApiKey: opts } : opts ?? {});
31737
31798
  }
31738
31799
 
31739
31800
  // src/hooks/usePGPProofs.ts
@@ -31750,7 +31811,7 @@ ${prettyStateOverride(stateOverride)}`;
31750
31811
  const identified = keyInfo.notations.map((n2) => identifyProof(n2)).filter((p2) => p2 !== null && p2.provider !== "unknown");
31751
31812
  const proofs = await Promise.all(
31752
31813
  identified.map(async (proof) => {
31753
- const result = await verifyProof(proof, fingerprint, config.neynarApiKey);
31814
+ const result = await verifyProof(proof, fingerprint, { neynarApiKey: config.neynarApiKey, farcasterHub: config.farcasterHub });
31754
31815
  return {
31755
31816
  provider: proof.provider,
31756
31817
  label: proof.label,
@@ -32054,6 +32115,7 @@ ${prettyStateOverride(stateOverride)}`;
32054
32115
  const theme = el2.dataset.theme || "thurin";
32055
32116
  const rpcUrl = el2.dataset.rpcUrl;
32056
32117
  const neynarApiKey = el2.dataset.neynarKey;
32118
+ const farcasterHub = el2.dataset.farcasterHub;
32057
32119
  const baseUrl = el2.dataset.baseUrl;
32058
32120
  const network = isNetworkName(el2.dataset.network) ? el2.dataset.network : "mainnet";
32059
32121
  const registryAddress = el2.dataset.registryAddress;
@@ -32066,7 +32128,7 @@ ${prettyStateOverride(stateOverride)}`;
32066
32128
  shadow.appendChild(container);
32067
32129
  const root = (0, import_client.createRoot)(container);
32068
32130
  const render = (t2) => root.render(
32069
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(IdentityKitProvider, { rpcUrl, neynarApiKey, network, registryAddress, baseUrl, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ThurinCard, { ...props, theme: t2 }) })
32131
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(IdentityKitProvider, { rpcUrl, neynarApiKey, farcasterHub, network, registryAddress, baseUrl, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ThurinCard, { ...props, theme: t2 }) })
32070
32132
  );
32071
32133
  render(theme);
32072
32134
  new MutationObserver(() => {