@neus/sdk 1.0.1 → 1.0.3

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/client.js CHANGED
@@ -5,7 +5,32 @@
5
5
  */
6
6
 
7
7
  import { ApiError, ValidationError, NetworkError, ConfigurationError } from './errors.js';
8
- import { constructVerificationMessage, validateWalletAddress, NEUS_CONSTANTS } from './utils.js';
8
+ import { constructVerificationMessage, validateWalletAddress, validateUniversalAddress, signMessage, NEUS_CONSTANTS } from './utils.js';
9
+
10
+ const FALLBACK_PUBLIC_VERIFIERS = [
11
+ 'ownership-basic',
12
+ 'ownership-pseudonym',
13
+ 'ownership-dns-txt',
14
+ 'ownership-social',
15
+ 'ownership-org-oauth',
16
+ 'contract-ownership',
17
+ 'nft-ownership',
18
+ 'token-holding',
19
+ 'wallet-link',
20
+ 'wallet-risk',
21
+ 'proof-of-human',
22
+ 'agent-identity',
23
+ 'agent-delegation',
24
+ 'ai-content-moderation'
25
+ ];
26
+
27
+ const INTERACTIVE_VERIFIERS = new Set([
28
+ 'ownership-social',
29
+ 'ownership-org-oauth',
30
+ 'proof-of-human'
31
+ ]);
32
+
33
+ const EVM_ADDRESS_RE = /^0x[a-fA-F0-9]{40}$/;
9
34
 
10
35
  // Validation for supported verifiers
11
36
  const validateVerifierData = (verifierId, data) => {
@@ -13,21 +38,76 @@ const validateVerifierData = (verifierId, data) => {
13
38
  return { valid: false, error: 'Data object is required' };
14
39
  }
15
40
 
16
- // Validate wallet address if present
17
- // Validate owner/ownerAddress fields based on verifier type
18
- const ownerField = (verifierId === 'nft-ownership' || verifierId === 'token-holding') ? 'ownerAddress' : 'owner';
19
- if (data[ownerField] && !validateWalletAddress(data[ownerField])) {
20
- return { valid: false, error: `Invalid ${ownerField} address` };
21
- }
22
-
23
41
  // Format validation for supported verifiers
24
42
  switch (verifierId) {
25
43
  case 'ownership-basic':
26
44
  // Required: owner (must match request walletAddress).
27
45
  // Reference is optional when content/contentHash is provided.
28
46
  // If neither content nor contentHash is provided, reference.id is required (reference-only proof).
29
- if (!data.owner || !validateWalletAddress(data.owner)) {
30
- return { valid: false, error: 'owner (wallet address) is required' };
47
+ if (!data.owner || !validateUniversalAddress(data.owner, typeof data.chain === 'string' ? data.chain : undefined)) {
48
+ return { valid: false, error: 'owner (universal wallet address) is required' };
49
+ }
50
+ if (data.content !== undefined && data.content !== null) {
51
+ if (typeof data.content !== 'string') {
52
+ return { valid: false, error: 'content must be a string when provided' };
53
+ }
54
+ if (data.content.length > 50000) {
55
+ return { valid: false, error: 'content exceeds 50KB inline limit' };
56
+ }
57
+ }
58
+ if (data.contentHash !== undefined && data.contentHash !== null) {
59
+ if (typeof data.contentHash !== 'string' || !/^0x[a-fA-F0-9]{64}$/.test(data.contentHash)) {
60
+ return { valid: false, error: 'contentHash must be a 32-byte hex string (0x + 64 hex chars) when provided' };
61
+ }
62
+ }
63
+ if (data.contentType !== undefined && data.contentType !== null) {
64
+ if (typeof data.contentType !== 'string' || data.contentType.length > 100) {
65
+ return { valid: false, error: 'contentType must be a string (max 100 chars) when provided' };
66
+ }
67
+ const base = String(data.contentType).split(';')[0].trim().toLowerCase();
68
+ // Minimal MIME sanity check (server validates more precisely).
69
+ if (!base || base.includes(' ') || !base.includes('/')) {
70
+ return { valid: false, error: 'contentType must be a valid MIME type when provided' };
71
+ }
72
+ }
73
+ if (data.provenance !== undefined && data.provenance !== null) {
74
+ if (!data.provenance || typeof data.provenance !== 'object' || Array.isArray(data.provenance)) {
75
+ return { valid: false, error: 'provenance must be an object when provided' };
76
+ }
77
+ const dk = data.provenance.declaredKind;
78
+ if (dk !== undefined && dk !== null) {
79
+ const allowed = ['human', 'ai', 'mixed', 'unknown'];
80
+ if (typeof dk !== 'string' || !allowed.includes(dk)) {
81
+ return { valid: false, error: `provenance.declaredKind must be one of: ${allowed.join(', ')}` };
82
+ }
83
+ }
84
+ const ai = data.provenance.aiContext;
85
+ if (ai !== undefined && ai !== null) {
86
+ if (typeof ai !== 'object' || Array.isArray(ai)) {
87
+ return { valid: false, error: 'provenance.aiContext must be an object when provided' };
88
+ }
89
+ if (ai.generatorType !== undefined && ai.generatorType !== null) {
90
+ const allowed = ['local', 'saas', 'agent'];
91
+ if (typeof ai.generatorType !== 'string' || !allowed.includes(ai.generatorType)) {
92
+ return { valid: false, error: `provenance.aiContext.generatorType must be one of: ${allowed.join(', ')}` };
93
+ }
94
+ }
95
+ if (ai.provider !== undefined && ai.provider !== null) {
96
+ if (typeof ai.provider !== 'string' || ai.provider.length > 64) {
97
+ return { valid: false, error: 'provenance.aiContext.provider must be a string (max 64 chars) when provided' };
98
+ }
99
+ }
100
+ if (ai.model !== undefined && ai.model !== null) {
101
+ if (typeof ai.model !== 'string' || ai.model.length > 128) {
102
+ return { valid: false, error: 'provenance.aiContext.model must be a string (max 128 chars) when provided' };
103
+ }
104
+ }
105
+ if (ai.runId !== undefined && ai.runId !== null) {
106
+ if (typeof ai.runId !== 'string' || ai.runId.length > 128) {
107
+ return { valid: false, error: 'provenance.aiContext.runId must be a string (max 128 chars) when provided' };
108
+ }
109
+ }
110
+ }
31
111
  }
32
112
  if (data.reference !== undefined) {
33
113
  if (!data.reference || typeof data.reference !== 'object') {
@@ -106,17 +186,20 @@ const validateVerifierData = (verifierId, data) => {
106
186
  }
107
187
  break;
108
188
  case 'wallet-link':
109
- if (!data.primaryWalletAddress || !validateWalletAddress(data.primaryWalletAddress)) {
189
+ if (!data.primaryWalletAddress || !validateUniversalAddress(data.primaryWalletAddress, data.chain)) {
110
190
  return { valid: false, error: 'primaryWalletAddress is required' };
111
191
  }
112
- if (!data.secondaryWalletAddress || !validateWalletAddress(data.secondaryWalletAddress)) {
192
+ if (!data.secondaryWalletAddress || !validateUniversalAddress(data.secondaryWalletAddress, data.chain)) {
113
193
  return { valid: false, error: 'secondaryWalletAddress is required' };
114
194
  }
115
195
  if (!data.signature || typeof data.signature !== 'string') {
116
196
  return { valid: false, error: 'signature is required (signed by secondary wallet)' };
117
197
  }
118
- if (typeof data.chainId !== 'number') {
119
- return { valid: false, error: 'chainId is required' };
198
+ if (typeof data.chain !== 'string' || !/^[a-z0-9]+:[^\s]+$/.test(data.chain)) {
199
+ return { valid: false, error: 'chain is required (namespace:reference)' };
200
+ }
201
+ if (typeof data.signatureMethod !== 'string' || !data.signatureMethod.trim()) {
202
+ return { valid: false, error: 'signatureMethod is required' };
120
203
  }
121
204
  if (typeof data.signedTimestamp !== 'number') {
122
205
  return { valid: false, error: 'signedTimestamp is required' };
@@ -182,6 +265,21 @@ const validateVerifierData = (verifierId, data) => {
182
265
  if (!validTypes.includes(contentType)) {
183
266
  return { valid: false, error: `contentType must be one of: ${validTypes.join(', ')}` };
184
267
  }
268
+ const isTextual = contentType.startsWith('text/') || contentType.includes('markdown');
269
+ if (isTextual) {
270
+ // Backend enforces 50KB UTF-8 limit for textual moderation payloads.
271
+ try {
272
+ const maxBytes = 50 * 1024;
273
+ const bytes = (typeof TextEncoder !== 'undefined')
274
+ ? new TextEncoder().encode(data.content).length
275
+ : String(data.content).length;
276
+ if (bytes > maxBytes) {
277
+ return { valid: false, error: `content exceeds ${maxBytes} bytes limit for ai-content-moderation verifier (text)` };
278
+ }
279
+ } catch {
280
+ // If encoding fails, defer to server.
281
+ }
282
+ }
185
283
  }
186
284
  if (data.content.length > 13653334) {
187
285
  return { valid: false, error: 'content exceeds 10MB limit' };
@@ -204,7 +302,7 @@ const validateVerifierData = (verifierId, data) => {
204
302
  // Note: signature is not required - envelope signature provides authentication
205
303
  break;
206
304
  case 'wallet-risk':
207
- if (data.walletAddress && !validateWalletAddress(data.walletAddress)) {
305
+ if (data.walletAddress && !validateUniversalAddress(data.walletAddress, data.chain)) {
208
306
  return { valid: false, error: 'Invalid walletAddress' };
209
307
  }
210
308
  break;
@@ -247,6 +345,28 @@ export class NeusClient {
247
345
  if (typeof this.config.apiKey === 'string' && this.config.apiKey.trim().length > 0) {
248
346
  this.defaultHeaders['Authorization'] = `Bearer ${this.config.apiKey.trim()}`;
249
347
  }
348
+ // Public app attribution header (non-secret)
349
+ if (typeof this.config.appId === 'string' && this.config.appId.trim().length > 0) {
350
+ this.defaultHeaders['X-Neus-App'] = this.config.appId.trim();
351
+ }
352
+ // Ephemeral sponsor capability token
353
+ if (typeof this.config.sponsorGrant === 'string' && this.config.sponsorGrant.trim().length > 0) {
354
+ this.defaultHeaders['X-Sponsor-Grant'] = this.config.sponsorGrant.trim();
355
+ }
356
+ // x402 retry receipt header
357
+ if (typeof this.config.paymentSignature === 'string' && this.config.paymentSignature.trim().length > 0) {
358
+ this.defaultHeaders['PAYMENT-SIGNATURE'] = this.config.paymentSignature.trim();
359
+ }
360
+ // Optional caller-supplied passthrough headers.
361
+ if (this.config.extraHeaders && typeof this.config.extraHeaders === 'object') {
362
+ for (const [k, v] of Object.entries(this.config.extraHeaders)) {
363
+ if (!k || v === undefined || v === null) continue;
364
+ const key = String(k).trim();
365
+ const value = String(v).trim();
366
+ if (!key || !value) continue;
367
+ this.defaultHeaders[key] = value;
368
+ }
369
+ }
250
370
  try {
251
371
  // Attach origin in browser environments
252
372
  if (typeof window !== 'undefined' && window.location && window.location.origin) {
@@ -257,6 +377,135 @@ export class NeusClient {
257
377
  }
258
378
  }
259
379
 
380
+ _getHubChainId() {
381
+ const configured = Number(this.config?.hubChainId);
382
+ if (Number.isFinite(configured) && configured > 0) return Math.floor(configured);
383
+ return NEUS_CONSTANTS.HUB_CHAIN_ID;
384
+ }
385
+
386
+ _normalizeIdentity(value) {
387
+ let raw = String(value || '').trim();
388
+ if (!raw) return '';
389
+ const didMatch = raw.match(/^did:pkh:([^:]+):([^:]+):(.+)$/i);
390
+ if (didMatch && didMatch[3]) {
391
+ raw = String(didMatch[3]).trim();
392
+ }
393
+ return EVM_ADDRESS_RE.test(raw) ? raw.toLowerCase() : raw;
394
+ }
395
+
396
+ _inferChainForAddress(address, explicitChain) {
397
+ if (typeof explicitChain === 'string' && explicitChain.includes(':')) return explicitChain.trim();
398
+ const raw = String(address || '').trim();
399
+ const didMatch = raw.match(/^did:pkh:([^:]+):([^:]+):(.+)$/i);
400
+ if (didMatch && didMatch[1] && didMatch[2]) {
401
+ return `${didMatch[1]}:${didMatch[2]}`;
402
+ }
403
+ if (EVM_ADDRESS_RE.test(raw)) {
404
+ return `eip155:${this._getHubChainId()}`;
405
+ }
406
+ // Default non-EVM chain for universal wallet paths when caller omits chain.
407
+ return 'solana:mainnet';
408
+ }
409
+
410
+ async _resolveWalletSigner(wallet) {
411
+ if (!wallet) {
412
+ throw new ConfigurationError('No wallet provider available');
413
+ }
414
+
415
+ if (wallet.address) {
416
+ return { signerWalletAddress: wallet.address, provider: wallet };
417
+ }
418
+ if (wallet.publicKey && typeof wallet.publicKey.toBase58 === 'function') {
419
+ return { signerWalletAddress: wallet.publicKey.toBase58(), provider: wallet };
420
+ }
421
+ if (typeof wallet.getAddress === 'function') {
422
+ const signerWalletAddress = await wallet.getAddress().catch(() => null);
423
+ return { signerWalletAddress, provider: wallet };
424
+ }
425
+ if (wallet.selectedAddress || wallet.request) {
426
+ const provider = wallet;
427
+ if (wallet.selectedAddress) {
428
+ return { signerWalletAddress: wallet.selectedAddress, provider };
429
+ }
430
+ const accounts = await provider.request({ method: 'eth_accounts' });
431
+ if (!accounts || accounts.length === 0) {
432
+ throw new ConfigurationError('No wallet accounts available');
433
+ }
434
+ return { signerWalletAddress: accounts[0], provider };
435
+ }
436
+
437
+ throw new ConfigurationError('Invalid wallet provider');
438
+ }
439
+
440
+ _getDefaultBrowserWallet() {
441
+ if (typeof window === 'undefined') return null;
442
+ return window.ethereum || window.solana || (window.phantom && window.phantom.solana) || null;
443
+ }
444
+
445
+ async _buildPrivateGateAuth({ address, wallet, chain, signatureMethod } = {}) {
446
+ const providerWallet = wallet || this._getDefaultBrowserWallet();
447
+ const { signerWalletAddress, provider } = await this._resolveWalletSigner(providerWallet);
448
+ if (!signerWalletAddress || typeof signerWalletAddress !== 'string') {
449
+ throw new ConfigurationError('No wallet accounts available');
450
+ }
451
+
452
+ const normalizedSigner = this._normalizeIdentity(signerWalletAddress);
453
+ const normalizedAddress = this._normalizeIdentity(address);
454
+ if (!normalizedSigner || normalizedSigner !== normalizedAddress) {
455
+ throw new ValidationError('wallet must match address when includePrivate=true');
456
+ }
457
+
458
+ const signerIsEvm = EVM_ADDRESS_RE.test(normalizedSigner);
459
+ const resolvedChain = this._inferChainForAddress(normalizedSigner, chain);
460
+ const resolvedSignatureMethod = (typeof signatureMethod === 'string' && signatureMethod.trim())
461
+ ? signatureMethod.trim()
462
+ : (signerIsEvm ? 'eip191' : 'ed25519');
463
+
464
+ const signedTimestamp = Date.now();
465
+ const message = constructVerificationMessage({
466
+ walletAddress: signerWalletAddress,
467
+ signedTimestamp,
468
+ data: { action: 'gate_check_private_proofs', walletAddress: normalizedAddress },
469
+ verifierIds: ['ownership-basic'],
470
+ ...(signerIsEvm ? { chainId: this._getHubChainId() } : { chain: resolvedChain })
471
+ });
472
+
473
+ let signature;
474
+ try {
475
+ signature = await signMessage({
476
+ provider,
477
+ message,
478
+ walletAddress: signerWalletAddress,
479
+ ...(signerIsEvm ? {} : { chain: resolvedChain })
480
+ });
481
+ } catch (error) {
482
+ if (error.code === 4001) {
483
+ throw new ValidationError('User rejected signature request');
484
+ }
485
+ throw new ValidationError(`Failed to sign message: ${error.message}`);
486
+ }
487
+
488
+ return {
489
+ walletAddress: signerWalletAddress,
490
+ signature,
491
+ signedTimestamp,
492
+ ...(signerIsEvm ? {} : { chain: resolvedChain, signatureMethod: resolvedSignatureMethod })
493
+ };
494
+ }
495
+
496
+ async createGatePrivateAuth(params = {}) {
497
+ const address = (params.address || '').toString();
498
+ if (!validateUniversalAddress(address, params.chain)) {
499
+ throw new ValidationError('Valid address is required');
500
+ }
501
+ return this._buildPrivateGateAuth({
502
+ address,
503
+ wallet: params.wallet,
504
+ chain: params.chain,
505
+ signatureMethod: params.signatureMethod
506
+ });
507
+ }
508
+
260
509
  // ============================================================================
261
510
  // CORE VERIFICATION METHODS
262
511
  // ============================================================================
@@ -279,7 +528,7 @@ export class NeusClient {
279
528
  * @param {string} [params.verifier] - Verifier ID (auto path)
280
529
  * @param {string} [params.content] - Content/description (auto path)
281
530
  * @param {Object} [params.wallet] - Optional injected wallet/provider (auto path)
282
- * @returns {Promise<Object>} Verification result with qHash
531
+ * @returns {Promise<Object>} Verification result with proofId (qHash is a deprecated alias)
283
532
  *
284
533
  * @example
285
534
  * const proof = await client.verify({
@@ -287,7 +536,7 @@ export class NeusClient {
287
536
  * data: {
288
537
  * content: "My content",
289
538
  * owner: walletAddress, // or ownerAddress for nft-ownership/token-holding
290
- * reference: { type: 'content', id: 'my-unique-identifier' }
539
+ * reference: { type: 'other', id: 'my-unique-identifier' }
291
540
  * },
292
541
  * walletAddress: '0x...',
293
542
  * signature: '0x...',
@@ -304,7 +553,7 @@ export class NeusClient {
304
553
  * @param {Object} [params.data] - Structured verification data
305
554
  * @param {Object} [params.wallet] - Wallet provider
306
555
  * @param {Object} [params.options] - Additional options
307
- * @returns {Promise<Object>} Verification result with qHash
556
+ * @returns {Promise<Object>} Verification result with proofId (qHash is a deprecated alias)
308
557
  *
309
558
  * @example
310
559
  * // Simple ownership proof
@@ -324,23 +573,25 @@ export class NeusClient {
324
573
  throw new ValidationError('content is required and must be a string (or use data param with owner + reference)');
325
574
  }
326
575
 
327
- const validVerifiers = [
328
- 'ownership-basic',
329
- 'ownership-pseudonym', // Pseudonymous identity (public)
330
- 'nft-ownership',
331
- 'token-holding',
332
- 'ownership-dns-txt',
333
- 'wallet-link',
334
- 'contract-ownership',
335
- 'wallet-risk', // Wallet risk assessment (public)
336
- // AI & Agent verifiers (ERC-8004 aligned)
337
- 'agent-identity',
338
- 'agent-delegation',
339
- 'ai-content-moderation'
340
- ];
576
+ let validVerifiers = FALLBACK_PUBLIC_VERIFIERS;
577
+ try {
578
+ const discovered = await this.getVerifiers();
579
+ if (Array.isArray(discovered) && discovered.length > 0) {
580
+ validVerifiers = discovered;
581
+ }
582
+ } catch {
583
+ // Fallback keeps SDK usable if verifier catalog endpoint is temporarily unavailable.
584
+ }
341
585
  if (!validVerifiers.includes(verifier)) {
342
586
  throw new ValidationError(`Invalid verifier '${verifier}'. Must be one of: ${validVerifiers.join(', ')}.`);
343
587
  }
588
+
589
+ if (INTERACTIVE_VERIFIERS.has(verifier)) {
590
+ const hostedCheckoutUrl = options?.hostedCheckoutUrl || 'https://neus.network/verify';
591
+ throw new ValidationError(
592
+ `${verifier} requires hosted interactive checkout. Use VerifyGate or redirect to ${hostedCheckoutUrl}.`
593
+ );
594
+ }
344
595
 
345
596
  // These verifiers require explicit data parameter (no auto-path)
346
597
  const requiresDataParam = [
@@ -362,8 +613,16 @@ export class NeusClient {
362
613
  // Auto-detect wallet and get address
363
614
  let walletAddress, provider;
364
615
  if (wallet) {
365
- walletAddress = wallet.address || wallet.selectedAddress;
616
+ walletAddress =
617
+ wallet.address ||
618
+ wallet.selectedAddress ||
619
+ wallet.walletAddress ||
620
+ (typeof wallet.getAddress === 'function' ? await wallet.getAddress() : null);
366
621
  provider = wallet.provider || wallet;
622
+ if (!walletAddress && provider && typeof provider.request === 'function') {
623
+ const accounts = await provider.request({ method: 'eth_accounts' });
624
+ walletAddress = Array.isArray(accounts) ? accounts[0] : null;
625
+ }
367
626
  } else {
368
627
  if (typeof window === 'undefined' || !window.ethereum) {
369
628
  throw new ConfigurationError('No Web3 wallet detected. Please install MetaMask or provide wallet parameter.');
@@ -384,6 +643,7 @@ export class NeusClient {
384
643
  reference: data.reference,
385
644
  ...(data.content && { content: data.content }),
386
645
  ...(data.contentHash && { contentHash: data.contentHash }),
646
+ ...(data.contentType && { contentType: data.contentType }),
387
647
  ...(data.provenance && { provenance: data.provenance })
388
648
  };
389
649
  } else {
@@ -442,14 +702,18 @@ export class NeusClient {
442
702
  if (!data?.signature) {
443
703
  throw new ValidationError('wallet-link requires signature in data parameter (signed by secondary wallet)');
444
704
  }
445
- if (typeof data?.chainId !== 'number') {
446
- throw new ValidationError('wallet-link requires chainId (number) in data parameter');
705
+ if (typeof data?.chain !== 'string' || !/^[a-z0-9]+:[^\s]+$/.test(data.chain)) {
706
+ throw new ValidationError('wallet-link requires chain (namespace:reference) in data parameter');
707
+ }
708
+ if (typeof data?.signatureMethod !== 'string' || !data.signatureMethod.trim()) {
709
+ throw new ValidationError('wallet-link requires signatureMethod in data parameter');
447
710
  }
448
711
  verificationData = {
449
712
  primaryWalletAddress: walletAddress,
450
713
  secondaryWalletAddress: data.secondaryWalletAddress,
451
714
  signature: data.signature,
452
- chainId: data.chainId,
715
+ chain: data.chain,
716
+ signatureMethod: data.signatureMethod,
453
717
  signedTimestamp: data?.signedTimestamp || Date.now()
454
718
  };
455
719
  } else if (verifier === 'contract-ownership') {
@@ -541,7 +805,7 @@ export class NeusClient {
541
805
  signedTimestamp,
542
806
  data: verificationData,
543
807
  verifierIds,
544
- chainId: NEUS_CONSTANTS.HUB_CHAIN_ID // Protocol-managed chain
808
+ chainId: this._getHubChainId() // Protocol-managed chain
545
809
  });
546
810
 
547
811
  let signature;
@@ -751,18 +1015,18 @@ export class NeusClient {
751
1015
  /**
752
1016
  * Get verification status
753
1017
  *
754
- * @param {string} qHash - Verification ID (qHash or proofId)
1018
+ * @param {string} proofId - Proof ID (standard). `qHash` is a deprecated alias (same value).
755
1019
  * @returns {Promise<Object>} Verification status and data
756
1020
  *
757
1021
  * @example
758
1022
  * const result = await client.getStatus('0x...');
759
1023
  * console.log('Status:', result.status);
760
1024
  */
761
- async getStatus(qHash) {
762
- if (!qHash || typeof qHash !== 'string') {
763
- throw new ValidationError('qHash is required');
1025
+ async getStatus(proofId) {
1026
+ if (!proofId || typeof proofId !== 'string') {
1027
+ throw new ValidationError('proofId is required');
764
1028
  }
765
- const response = await this._makeRequest('GET', `/api/v1/verification/status/${qHash}`);
1029
+ const response = await this._makeRequest('GET', `/api/v1/verification/status/${proofId}`);
766
1030
 
767
1031
  if (!response.success) {
768
1032
  throw new ApiError(`Failed to get status: ${response.error?.message || 'Unknown error'}`, response.error);
@@ -774,74 +1038,73 @@ export class NeusClient {
774
1038
  /**
775
1039
  * Get private proof status with wallet signature
776
1040
  *
777
- * @param {string} qHash - Verification ID
1041
+ * @param {string} proofId - Proof ID (standard). `qHash` is a deprecated alias (same value).
778
1042
  * @param {Object} wallet - Wallet provider (window.ethereum or ethers Wallet)
779
1043
  * @returns {Promise<Object>} Private verification status and data
780
1044
  *
781
1045
  * @example
782
1046
  * // Access private proof
783
- * const privateData = await client.getPrivateStatus(qHash, window.ethereum);
1047
+ * const privateData = await client.getPrivateStatus(proofId, window.ethereum);
784
1048
  */
785
- async getPrivateStatus(qHash, wallet = null) {
786
- if (!qHash || typeof qHash !== 'string') {
787
- throw new ValidationError('qHash is required');
1049
+ async getPrivateStatus(proofId, wallet = null) {
1050
+ if (!proofId || typeof proofId !== 'string') {
1051
+ throw new ValidationError('proofId is required');
788
1052
  }
789
1053
 
790
- // Auto-detect wallet if not provided
791
- if (!wallet) {
792
- if (typeof window === 'undefined' || !window.ethereum) {
793
- throw new ConfigurationError('No wallet provider available');
1054
+ // Allow pre-signed universal owner auth (e.g. Solana) to avoid wallet-provider assumptions.
1055
+ const isPreSignedAuth = wallet &&
1056
+ typeof wallet === 'object' &&
1057
+ typeof wallet.walletAddress === 'string' &&
1058
+ typeof wallet.signature === 'string' &&
1059
+ typeof wallet.signedTimestamp === 'number';
1060
+ if (isPreSignedAuth) {
1061
+ const auth = wallet;
1062
+ const headers = {
1063
+ 'x-wallet-address': String(auth.walletAddress),
1064
+ 'x-signature': String(auth.signature),
1065
+ 'x-signed-timestamp': String(auth.signedTimestamp),
1066
+ ...(typeof auth.chain === 'string' && auth.chain.trim() ? { 'x-chain': auth.chain.trim() } : {}),
1067
+ ...(typeof auth.signatureMethod === 'string' && auth.signatureMethod.trim() ? { 'x-signature-method': auth.signatureMethod.trim() } : {})
1068
+ };
1069
+ const response = await this._makeRequest('GET', `/api/v1/verification/status/${proofId}`, null, headers);
1070
+ if (!response.success) {
1071
+ throw new ApiError(
1072
+ `Failed to access private proof: ${response.error?.message || 'Unauthorized'}`,
1073
+ response.error
1074
+ );
794
1075
  }
795
- wallet = window.ethereum;
1076
+ return this._formatResponse(response);
796
1077
  }
797
1078
 
798
- let walletAddress, provider;
799
-
800
- // Handle different wallet types
801
- if (wallet.address) {
802
- // ethers Wallet
803
- walletAddress = wallet.address;
804
- provider = wallet;
805
- } else if (wallet.selectedAddress || wallet.request) {
806
- // Browser provider (MetaMask, etc.)
807
- provider = wallet;
808
- if (wallet.selectedAddress) {
809
- walletAddress = wallet.selectedAddress;
810
- } else {
811
- const accounts = await provider.request({ method: 'eth_accounts' });
812
- if (!accounts || accounts.length === 0) {
813
- throw new ConfigurationError('No wallet accounts available');
814
- }
815
- walletAddress = accounts[0];
816
- }
817
- } else {
818
- throw new ConfigurationError('Invalid wallet provider');
1079
+ const providerWallet = wallet || this._getDefaultBrowserWallet();
1080
+ const { signerWalletAddress: walletAddress, provider } = await this._resolveWalletSigner(providerWallet);
1081
+ if (!walletAddress || typeof walletAddress !== 'string') {
1082
+ throw new ConfigurationError('No wallet accounts available');
819
1083
  }
1084
+ const signerIsEvm = EVM_ADDRESS_RE.test(this._normalizeIdentity(walletAddress));
1085
+ const chain = this._inferChainForAddress(walletAddress);
1086
+ const signatureMethod = signerIsEvm ? 'eip191' : 'ed25519';
820
1087
 
821
1088
  const signedTimestamp = Date.now();
822
1089
 
823
- // IMPORTANT: This must match the server's Standard Signing String owner-access check:
824
- // data.action='access_private_proof' + data.qHash, verifierIds=['ownership-basic'], chainId=default chainId.
1090
+ // IMPORTANT: This must match the server's Standard Signing String owner-access check.
1091
+ // Keep wire payload key `qHash` for backwards compatibility.
825
1092
  const message = constructVerificationMessage({
826
1093
  walletAddress,
827
1094
  signedTimestamp,
828
- data: { action: 'access_private_proof', qHash },
1095
+ data: { action: 'access_private_proof', qHash: proofId },
829
1096
  verifierIds: ['ownership-basic'],
830
- chainId: NEUS_CONSTANTS.HUB_CHAIN_ID
1097
+ ...(signerIsEvm ? { chainId: this._getHubChainId() } : { chain })
831
1098
  });
832
1099
 
833
1100
  let signature;
834
1101
  try {
835
- if (provider.signMessage) {
836
- // ethers Wallet
837
- signature = await provider.signMessage(message);
838
- } else {
839
- // Browser provider
840
- signature = await provider.request({
841
- method: 'personal_sign',
842
- params: [message, walletAddress]
843
- });
844
- }
1102
+ signature = await signMessage({
1103
+ provider,
1104
+ message,
1105
+ walletAddress,
1106
+ ...(signerIsEvm ? {} : { chain })
1107
+ });
845
1108
  } catch (error) {
846
1109
  if (error.code === 4001) {
847
1110
  throw new ValidationError('User rejected signature request');
@@ -850,10 +1113,11 @@ export class NeusClient {
850
1113
  }
851
1114
 
852
1115
  // Make request with signature headers (server reads x-wallet-address/x-signature/x-signed-timestamp)
853
- const response = await this._makeRequest('GET', `/api/v1/verification/status/${qHash}`, null, {
1116
+ const response = await this._makeRequest('GET', `/api/v1/verification/status/${proofId}`, null, {
854
1117
  'x-wallet-address': walletAddress,
855
1118
  'x-signature': signature,
856
- 'x-signed-timestamp': signedTimestamp.toString()
1119
+ 'x-signed-timestamp': signedTimestamp.toString(),
1120
+ ...(signerIsEvm ? {} : { 'x-chain': chain, 'x-signature-method': signatureMethod })
857
1121
  });
858
1122
 
859
1123
  if (!response.success) {
@@ -899,7 +1163,7 @@ export class NeusClient {
899
1163
  * Polls the verification status until it reaches a terminal state (completed or failed).
900
1164
  * Useful for providing real-time feedback to users during verification.
901
1165
  *
902
- * @param {string} qHash - Verification ID to poll
1166
+ * @param {string} proofId - Proof ID to poll (standard). `qHash` is a deprecated alias (same value).
903
1167
  * @param {Object} [options] - Polling options
904
1168
  * @param {number} [options.interval=5000] - Polling interval in ms
905
1169
  * @param {number} [options.timeout=120000] - Total timeout in ms
@@ -907,7 +1171,7 @@ export class NeusClient {
907
1171
  * @returns {Promise<Object>} Final verification status
908
1172
  *
909
1173
  * @example
910
- * const finalStatus = await client.pollProofStatus(qHash, {
1174
+ * const finalStatus = await client.pollProofStatus(proofId, {
911
1175
  * interval: 3000,
912
1176
  * timeout: 60000,
913
1177
  * onProgress: (status) => {
@@ -918,22 +1182,24 @@ export class NeusClient {
918
1182
  * }
919
1183
  * });
920
1184
  */
921
- async pollProofStatus(qHash, options = {}) {
1185
+ async pollProofStatus(proofId, options = {}) {
922
1186
  const {
923
1187
  interval = 5000,
924
1188
  timeout = 120000,
925
1189
  onProgress
926
1190
  } = options;
927
1191
 
928
- if (!qHash || typeof qHash !== 'string') {
929
- throw new ValidationError('qHash is required');
1192
+ if (!proofId || typeof proofId !== 'string') {
1193
+ throw new ValidationError('proofId is required');
930
1194
  }
931
1195
 
932
1196
  const startTime = Date.now();
1197
+ let consecutiveRateLimits = 0;
933
1198
 
934
1199
  while (Date.now() - startTime < timeout) {
935
1200
  try {
936
- const status = await this.getStatus(qHash);
1201
+ const status = await this.getStatus(proofId);
1202
+ consecutiveRateLimits = 0;
937
1203
 
938
1204
  // Call progress callback if provided
939
1205
  if (onProgress && typeof onProgress === 'function') {
@@ -956,7 +1222,19 @@ export class NeusClient {
956
1222
  if (error instanceof ValidationError) {
957
1223
  throw error;
958
1224
  }
959
- await new Promise(resolve => setTimeout(resolve, interval));
1225
+
1226
+ let nextDelay = interval;
1227
+ if (error instanceof ApiError && Number(error.statusCode) === 429) {
1228
+ consecutiveRateLimits += 1;
1229
+ const exp = Math.min(6, consecutiveRateLimits); // cap growth
1230
+ const base = Math.max(500, Number(interval) || 0);
1231
+ const max = 30000; // 30s cap to keep UX responsive
1232
+ const backoff = Math.min(max, base * Math.pow(2, exp));
1233
+ const jitter = Math.floor(backoff * (0.5 + Math.random() * 0.5)); // 50-100%
1234
+ nextDelay = jitter;
1235
+ }
1236
+
1237
+ await new Promise(resolve => setTimeout(resolve, nextDelay));
960
1238
  }
961
1239
  }
962
1240
 
@@ -982,62 +1260,37 @@ export class NeusClient {
982
1260
  }
983
1261
 
984
1262
  /** Revoke your own proof (owner-signed) */
985
- async revokeOwnProof(qHash, wallet) {
986
- if (!qHash || typeof qHash !== 'string') {
987
- throw new ValidationError('qHash is required');
1263
+ async revokeOwnProof(proofId, wallet) {
1264
+ if (!proofId || typeof proofId !== 'string') {
1265
+ throw new ValidationError('proofId is required');
988
1266
  }
989
- const address = wallet?.address || await this._getWalletAddress();
1267
+ const providerWallet = wallet || this._getDefaultBrowserWallet();
1268
+ const { signerWalletAddress: address, provider } = await this._resolveWalletSigner(providerWallet);
1269
+ if (!address || typeof address !== 'string') {
1270
+ throw new ConfigurationError('No wallet accounts available');
1271
+ }
1272
+ const signerIsEvm = EVM_ADDRESS_RE.test(this._normalizeIdentity(address));
1273
+ const chain = this._inferChainForAddress(address);
1274
+ const signatureMethod = signerIsEvm ? 'eip191' : 'ed25519';
990
1275
  const signedTimestamp = Date.now();
991
- const hubChainId = NEUS_CONSTANTS.HUB_CHAIN_ID;
992
1276
 
993
1277
  const message = constructVerificationMessage({
994
1278
  walletAddress: address,
995
1279
  signedTimestamp,
996
- data: { action: 'revoke_proof', qHash },
1280
+ // Keep wire payload key `qHash` for backwards compatibility.
1281
+ data: { action: 'revoke_proof', qHash: proofId },
997
1282
  verifierIds: ['ownership-basic'],
998
- chainId: hubChainId
1283
+ ...(signerIsEvm ? { chainId: this._getHubChainId() } : { chain })
999
1284
  });
1000
1285
 
1001
1286
  let signature;
1002
1287
  try {
1003
- // UNIFIED SIGNING: Match utils/core.ts fallback order
1004
- const toHexUtf8 = (s) => {
1005
- const enc = new TextEncoder();
1006
- const bytes = enc.encode(s);
1007
- let hex = '0x';
1008
- for (let i = 0; i < bytes.length; i++) hex += bytes[i].toString(16).padStart(2, '0');
1009
- return hex;
1010
- };
1011
-
1012
- // Detect Farcaster wallet - requires hex-encoded messages FIRST
1013
- const isFarcasterWallet = (() => {
1014
- if (typeof window === 'undefined') return false;
1015
- try {
1016
- const w = window;
1017
- const fc = w.farcaster;
1018
- if (!fc || !fc.context) return false;
1019
- const fcProvider = fc.provider || fc.walletProvider || (fc.context && fc.context.walletProvider);
1020
- if (fcProvider === w.ethereum) return true;
1021
- if (w.mini && w.mini.wallet === w.ethereum && fc && fc.context) return true;
1022
- if (w.ethereum && fc && fc.context) return true;
1023
- } catch {
1024
- // ignore: optional Farcaster detection
1025
- }
1026
- return false;
1027
- })();
1028
-
1029
- if (isFarcasterWallet) {
1030
- try {
1031
- const hexMsg = toHexUtf8(message);
1032
- signature = await window.ethereum.request({ method: 'personal_sign', params: [hexMsg, address] });
1033
- } catch {
1034
- // ignore: fall through to standard signing
1035
- }
1036
- }
1037
-
1038
- if (!signature) {
1039
- signature = await window.ethereum.request({ method: 'personal_sign', params: [message, address] });
1040
- }
1288
+ signature = await signMessage({
1289
+ provider,
1290
+ message,
1291
+ walletAddress: address,
1292
+ ...(signerIsEvm ? {} : { chain })
1293
+ });
1041
1294
  } catch (error) {
1042
1295
  if (error.code === 4001) {
1043
1296
  throw new ValidationError('User rejected revocation signature');
@@ -1045,11 +1298,16 @@ export class NeusClient {
1045
1298
  throw new ValidationError(`Failed to sign revocation: ${error.message}`);
1046
1299
  }
1047
1300
 
1048
- const res = await fetch(`${this.config.apiUrl}/api/v1/proofs/${qHash}/revoke-self`, {
1301
+ const res = await fetch(`${this.config.apiUrl}/api/v1/proofs/${proofId}/revoke-self`, {
1049
1302
  method: 'POST',
1050
1303
  // SECURITY: Do not put proof signatures into Authorization headers.
1051
1304
  headers: { 'Content-Type': 'application/json' },
1052
- body: JSON.stringify({ walletAddress: address, signature, signedTimestamp })
1305
+ body: JSON.stringify({
1306
+ walletAddress: address,
1307
+ signature,
1308
+ signedTimestamp,
1309
+ ...(signerIsEvm ? {} : { chain, signatureMethod })
1310
+ })
1053
1311
  });
1054
1312
  const json = await res.json();
1055
1313
  if (!json.success) {
@@ -1059,13 +1317,13 @@ export class NeusClient {
1059
1317
  }
1060
1318
 
1061
1319
  // ============================================================================
1062
- // GATE & LOOKUP METHODS
1320
+ // PROOFS & GATING METHODS
1063
1321
  // ============================================================================
1064
1322
 
1065
1323
  /**
1066
1324
  * GET PROOFS BY WALLET - Fetch proofs for a wallet address
1067
1325
  *
1068
- * @param {string} walletAddress - Wallet address (0x...) or DID (did:pkh:...)
1326
+ * @param {string} walletAddress - Wallet identity (EVM/Solana/DID)
1069
1327
  * @param {Object} [options] - Filter options
1070
1328
  * @param {number} [options.limit] - Max results (default: 50; higher limits require owner access)
1071
1329
  * @param {number} [options.offset] - Pagination offset (default: 0)
@@ -1115,7 +1373,7 @@ export class NeusClient {
1115
1373
  *
1116
1374
  * Signs an owner-access intent and requests private proofs via signature headers.
1117
1375
  *
1118
- * @param {string} walletAddress - Wallet address (0x...) or DID (did:pkh:...)
1376
+ * @param {string} walletAddress - Wallet identity (EVM/Solana/DID)
1119
1377
  * @param {Object} [options]
1120
1378
  * @param {number} [options.limit] - Max results (server enforces caps)
1121
1379
  * @param {number} [options.offset] - Pagination offset
@@ -1129,52 +1387,51 @@ export class NeusClient {
1129
1387
  const id = walletAddress.trim();
1130
1388
  const pathId = /^0x[a-fA-F0-9]{40}$/i.test(id) ? id.toLowerCase() : id;
1131
1389
 
1390
+ const requestedIdentity = this._normalizeIdentity(id);
1391
+
1132
1392
  // Auto-detect wallet if not provided
1133
1393
  if (!wallet) {
1134
- if (typeof window === 'undefined' || !window.ethereum) {
1394
+ const defaultWallet = this._getDefaultBrowserWallet();
1395
+ if (!defaultWallet) {
1135
1396
  throw new ConfigurationError('No wallet provider available');
1136
1397
  }
1137
- wallet = window.ethereum;
1398
+ wallet = defaultWallet;
1138
1399
  }
1139
1400
 
1140
- let signerWalletAddress, provider;
1141
- if (wallet.address) {
1142
- signerWalletAddress = wallet.address;
1143
- provider = wallet;
1144
- } else if (wallet.selectedAddress || wallet.request) {
1145
- provider = wallet;
1146
- if (wallet.selectedAddress) {
1147
- signerWalletAddress = wallet.selectedAddress;
1148
- } else {
1149
- const accounts = await provider.request({ method: 'eth_accounts' });
1150
- if (!accounts || accounts.length === 0) {
1151
- throw new ConfigurationError('No wallet accounts available');
1152
- }
1153
- signerWalletAddress = accounts[0];
1154
- }
1155
- } else {
1156
- throw new ConfigurationError('Invalid wallet provider');
1401
+ const { signerWalletAddress, provider } = await this._resolveWalletSigner(wallet);
1402
+
1403
+ if (!signerWalletAddress || typeof signerWalletAddress !== 'string') {
1404
+ throw new ConfigurationError('No wallet accounts available');
1157
1405
  }
1158
1406
 
1407
+ const normalizedSigner = this._normalizeIdentity(signerWalletAddress);
1408
+ if (!normalizedSigner || normalizedSigner !== requestedIdentity) {
1409
+ throw new ValidationError('wallet must match walletAddress for private proof access');
1410
+ }
1411
+
1412
+ const signerIsEvm = EVM_ADDRESS_RE.test(normalizedSigner);
1413
+ const chain = this._inferChainForAddress(normalizedSigner, options?.chain);
1414
+ const signatureMethod = (typeof options?.signatureMethod === 'string' && options.signatureMethod.trim())
1415
+ ? options.signatureMethod.trim()
1416
+ : (signerIsEvm ? 'eip191' : 'ed25519');
1417
+
1159
1418
  const signedTimestamp = Date.now();
1160
1419
  const message = constructVerificationMessage({
1161
1420
  walletAddress: signerWalletAddress,
1162
1421
  signedTimestamp,
1163
- data: { action: 'access_private_proofs_by_wallet', walletAddress: signerWalletAddress.toLowerCase() },
1422
+ data: { action: 'access_private_proofs_by_wallet', walletAddress: normalizedSigner },
1164
1423
  verifierIds: ['ownership-basic'],
1165
- chainId: NEUS_CONSTANTS.HUB_CHAIN_ID
1424
+ ...(signerIsEvm ? { chainId: this._getHubChainId() } : { chain })
1166
1425
  });
1167
1426
 
1168
1427
  let signature;
1169
1428
  try {
1170
- if (provider.signMessage) {
1171
- signature = await provider.signMessage(message);
1172
- } else {
1173
- signature = await provider.request({
1174
- method: 'personal_sign',
1175
- params: [message, signerWalletAddress]
1176
- });
1177
- }
1429
+ signature = await signMessage({
1430
+ provider,
1431
+ message,
1432
+ walletAddress: signerWalletAddress,
1433
+ ...(signerIsEvm ? {} : { chain })
1434
+ });
1178
1435
  } catch (error) {
1179
1436
  if (error.code === 4001) {
1180
1437
  throw new ValidationError('User rejected signature request');
@@ -1190,7 +1447,8 @@ export class NeusClient {
1190
1447
  const response = await this._makeRequest('GET', `/api/v1/proofs/byWallet/${encodeURIComponent(pathId)}${query}`, null, {
1191
1448
  'x-wallet-address': signerWalletAddress,
1192
1449
  'x-signature': signature,
1193
- 'x-signed-timestamp': signedTimestamp.toString()
1450
+ 'x-signed-timestamp': signedTimestamp.toString(),
1451
+ ...(signerIsEvm ? {} : { 'x-chain': chain, 'x-signature-method': signatureMethod })
1194
1452
  });
1195
1453
 
1196
1454
  if (!response.success) {
@@ -1207,77 +1465,34 @@ export class NeusClient {
1207
1465
  };
1208
1466
  }
1209
1467
 
1210
- /**
1211
- * LOOKUP MODE (API) - Non-persistent server-to-server checks
1212
- *
1213
- * Runs `external_lookup` verifiers without minting/storing a proof.
1214
- * Requires an enterprise API key (server-side only).
1215
- *
1216
- * @param {Object} params
1217
- * @param {string} params.apiKey - Enterprise API key (sk_live_... or sk_test_...)
1218
- * @param {Array<string>} params.verifierIds - Verifiers to run (external_lookup only)
1219
- * @param {string} params.targetWalletAddress - Wallet to evaluate
1220
- * @param {Object} [params.data] - Verifier input data (e.g., contractAddress/tokenId/chainId)
1221
- * @returns {Promise<Object>} API response ({ success, data })
1222
- */
1223
- async lookup(params = {}) {
1224
- const apiKey = (params.apiKey || '').toString().trim();
1225
- if (!apiKey || !(apiKey.startsWith('sk_live_') || apiKey.startsWith('sk_test_'))) {
1226
- throw new ValidationError('lookup requires apiKey (sk_live_* or sk_test_*)');
1227
- }
1228
-
1229
- const verifierIds = Array.isArray(params.verifierIds)
1230
- ? params.verifierIds.map(v => String(v).trim()).filter(Boolean)
1231
- : [];
1232
- if (verifierIds.length === 0) {
1233
- throw new ValidationError('lookup requires verifierIds (non-empty array)');
1234
- }
1235
-
1236
- const targetWalletAddress = (params.targetWalletAddress || '').toString().trim();
1237
- if (!targetWalletAddress || !/^0x[a-fA-F0-9]{40}$/i.test(targetWalletAddress)) {
1238
- throw new ValidationError('lookup requires a valid targetWalletAddress (0x...)');
1239
- }
1240
-
1241
- const body = {
1242
- verifierIds,
1243
- targetWalletAddress,
1244
- data: (params.data && typeof params.data === 'object') ? params.data : {}
1245
- };
1246
-
1247
- const response = await this._makeRequest('POST', '/api/v1/verification/lookup', body, {
1248
- Authorization: `Bearer ${apiKey}`
1249
- });
1250
-
1251
- if (!response.success) {
1252
- throw new ApiError(`Lookup failed: ${response.error?.message || 'Unknown error'}`, response.error);
1253
- }
1254
-
1255
- return response;
1256
- }
1257
-
1258
1468
  /**
1259
1469
  * GATE CHECK (API) - Minimal eligibility check
1260
1470
  *
1261
- * Calls the public gate endpoint and returns a **minimal** yes/no response
1262
- * against **public + discoverable** proofs only.
1471
+ * Calls the gate endpoint and returns a **minimal** yes/no response.
1472
+ * By default this checks **public + discoverable** proofs only.
1473
+ *
1474
+ * When `includePrivate=true`, this can perform owner-signed private checks
1475
+ * (no full proof payloads returned) by providing a wallet/provider.
1263
1476
  *
1264
- * Prefer this over `checkGate()` for server-side integrations that want the
1477
+ * Prefer this over `checkGate()` for integrations that want the
1265
1478
  * smallest, most stable surface area (and do NOT need full proof payloads).
1266
1479
  *
1267
1480
  * @param {Object} params - Gate check query params
1268
- * @param {string} params.address - Wallet address to check (0x...)
1481
+ * @param {string} params.address - Wallet identity to check (EVM/Solana/DID)
1269
1482
  * @param {Array<string>|string} [params.verifierIds] - Verifier IDs to match (array or comma-separated)
1270
1483
  * @param {boolean} [params.requireAll] - Require all verifierIds on a single proof
1271
1484
  * @param {number} [params.minCount] - Minimum number of matching proofs
1272
1485
  * @param {number} [params.sinceDays] - Optional time window in days
1273
1486
  * @param {number} [params.since] - Optional unix timestamp in ms (lower bound)
1274
1487
  * @param {number} [params.limit] - Max rows to scan (server may clamp)
1275
- * @param {string} [params.select] - Comma-separated projections (handle,provider,profileUrl,traits.<key>)
1488
+ * @param {boolean} [params.includePrivate] - Include private proofs for owner-authenticated requests
1489
+ * @param {boolean} [params.includeQHashes] - Include matched qHashes in response (minimal IDs only)
1490
+ * @param {Object} [params.wallet] - Optional wallet/provider used to sign includePrivate owner checks
1276
1491
  * @returns {Promise<Object>} API response ({ success, data })
1277
1492
  */
1278
1493
  async gateCheck(params = {}) {
1279
1494
  const address = (params.address || '').toString();
1280
- if (!address || !/^0x[a-fA-F0-9]{40}$/i.test(address)) {
1495
+ if (!validateUniversalAddress(address, params.chain)) {
1281
1496
  throw new ValidationError('Valid address is required');
1282
1497
  }
1283
1498
 
@@ -1314,7 +1529,8 @@ export class NeusClient {
1314
1529
  setIfPresent('sinceDays', params.sinceDays);
1315
1530
  setIfPresent('since', params.since);
1316
1531
  setIfPresent('limit', params.limit);
1317
- setCsvIfPresent('select', params.select);
1532
+ setBoolIfPresent('includePrivate', params.includePrivate);
1533
+ setBoolIfPresent('includeQHashes', params.includeQHashes);
1318
1534
 
1319
1535
  // Common match filters (optional)
1320
1536
  setIfPresent('referenceType', params.referenceType);
@@ -1332,9 +1548,8 @@ export class NeusClient {
1332
1548
  setIfPresent('domain', params.domain);
1333
1549
  setIfPresent('minBalance', params.minBalance);
1334
1550
 
1335
- // Social / identity / wallet filters
1551
+ // Wallet filters
1336
1552
  setIfPresent('provider', params.provider);
1337
- setIfPresent('handle', params.handle);
1338
1553
  setIfPresent('ownerAddress', params.ownerAddress);
1339
1554
  setIfPresent('riskLevel', params.riskLevel);
1340
1555
  setBoolIfPresent('sanctioned', params.sanctioned);
@@ -1343,11 +1558,44 @@ export class NeusClient {
1343
1558
  setIfPresent('secondaryWalletAddress', params.secondaryWalletAddress);
1344
1559
  setIfPresent('verificationMethod', params.verificationMethod);
1345
1560
 
1346
- // Trait checks
1347
- setIfPresent('traitPath', params.traitPath);
1348
- setIfPresent('traitGte', params.traitGte);
1561
+ let headersOverride = null;
1562
+ if (params.includePrivate === true) {
1563
+ const provided = params.privateAuth && typeof params.privateAuth === 'object' ? params.privateAuth : null;
1564
+ let auth = provided;
1565
+ if (!auth) {
1566
+ const walletCandidate = params.wallet || this._getDefaultBrowserWallet();
1567
+ if (walletCandidate) {
1568
+ auth = await this._buildPrivateGateAuth({
1569
+ address,
1570
+ wallet: walletCandidate,
1571
+ chain: params.chain,
1572
+ signatureMethod: params.signatureMethod
1573
+ });
1574
+ }
1575
+ }
1576
+ if (!auth) {
1577
+ // No signer context available - proceed as public/discoverable gate check.
1578
+ } else {
1579
+ const normalizedAuthWallet = this._normalizeIdentity(auth.walletAddress);
1580
+ const normalizedAddress = this._normalizeIdentity(address);
1581
+ if (!normalizedAuthWallet || normalizedAuthWallet !== normalizedAddress) {
1582
+ throw new ValidationError('privateAuth.walletAddress must match address when includePrivate=true');
1583
+ }
1584
+ const authChain = (typeof auth.chain === 'string' && auth.chain.includes(':')) ? auth.chain.trim() : null;
1585
+ const authSignatureMethod = (typeof auth.signatureMethod === 'string' && auth.signatureMethod.trim())
1586
+ ? auth.signatureMethod.trim()
1587
+ : null;
1588
+ headersOverride = {
1589
+ 'x-wallet-address': String(auth.walletAddress),
1590
+ 'x-signature': String(auth.signature),
1591
+ 'x-signed-timestamp': String(auth.signedTimestamp),
1592
+ ...(authChain ? { 'x-chain': authChain } : {}),
1593
+ ...(authSignatureMethod ? { 'x-signature-method': authSignatureMethod } : {})
1594
+ };
1595
+ }
1596
+ }
1349
1597
 
1350
- const response = await this._makeRequest('GET', `/api/v1/proofs/gate/check?${qs.toString()}`);
1598
+ const response = await this._makeRequest('GET', `/api/v1/proofs/check?${qs.toString()}`, null, headersOverride);
1351
1599
  if (!response.success) {
1352
1600
  throw new ApiError(`Gate check failed: ${response.error?.message || 'Unknown error'}`, response.error);
1353
1601
  }
@@ -1372,7 +1620,7 @@ export class NeusClient {
1372
1620
  * Supports verifier-specific:
1373
1621
  * - NFT/Token: 'contractAddress', 'tokenId', 'chainId', 'ownerAddress', 'minBalance'
1374
1622
  * - DNS: 'domain', 'walletAddress'
1375
- * - Wallet-link: 'primaryWalletAddress', 'secondaryWalletAddress', 'chainId'
1623
+ * - Wallet-link: 'primaryWalletAddress', 'secondaryWalletAddress', 'chain', 'signatureMethod'
1376
1624
  * - Contract-ownership: 'contractAddress', 'chainId', 'owner', 'verificationMethod'
1377
1625
  * Note: contentHash matching uses approximation in SDK; for exact SHA-256 matching, use backend API
1378
1626
  * @param {Array} [params.proofs] - Pre-fetched proofs (skip API call)
@@ -1390,7 +1638,7 @@ export class NeusClient {
1390
1638
  async checkGate(params) {
1391
1639
  const { walletAddress, requirements, proofs: preloadedProofs } = params;
1392
1640
 
1393
- if (!walletAddress || !/^0x[a-fA-F0-9]{40}$/i.test(walletAddress)) {
1641
+ if (!validateUniversalAddress(walletAddress)) {
1394
1642
  throw new ValidationError('Valid walletAddress is required');
1395
1643
  }
1396
1644
  if (!Array.isArray(requirements) || requirements.length === 0) {
@@ -1434,7 +1682,6 @@ export class NeusClient {
1434
1682
  if (match && typeof match === 'object') {
1435
1683
  const data = verifier.data || {};
1436
1684
  const input = data.input || {}; // NFT/token verifiers store fields in input
1437
- // No license support in public SDK
1438
1685
 
1439
1686
  for (const [key, expected] of Object.entries(match)) {
1440
1687
  let actualValue = null;
@@ -1632,10 +1879,20 @@ export class NeusClient {
1632
1879
  * @private
1633
1880
  */
1634
1881
  _formatResponse(response) {
1635
- const qHash = response?.data?.qHash ||
1636
- response?.qHash ||
1882
+ const proofId = response?.data?.proofId ||
1883
+ response?.proofId ||
1884
+ response?.data?.resource?.proofId ||
1885
+ response?.data?.qHash ||
1886
+ response?.qHash ||
1887
+ response?.data?.resource?.qHash ||
1888
+ response?.data?.id;
1889
+ const qHash = response?.data?.qHash ||
1890
+ response?.qHash ||
1637
1891
  response?.data?.resource?.qHash ||
1892
+ proofId ||
1638
1893
  response?.data?.id;
1894
+ const finalProofId = proofId || qHash || null;
1895
+ const finalQHash = qHash || proofId || finalProofId;
1639
1896
 
1640
1897
  const status = response?.data?.status ||
1641
1898
  response?.status ||
@@ -1644,12 +1901,13 @@ export class NeusClient {
1644
1901
 
1645
1902
  return {
1646
1903
  success: response.success,
1647
- qHash,
1904
+ proofId: finalProofId,
1905
+ qHash: finalQHash,
1648
1906
  status,
1649
1907
  data: response.data,
1650
1908
  message: response.message,
1651
1909
  timestamp: Date.now(),
1652
- statusUrl: qHash ? `${this.baseUrl}/api/v1/verification/status/${qHash}` : null
1910
+ statusUrl: finalProofId ? `${this.baseUrl}/api/v1/verification/status/${finalProofId}` : null
1653
1911
  };
1654
1912
  }
1655
1913