@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/README.md +150 -140
- package/cjs/client.cjs +634 -223
- package/cjs/errors.cjs +1 -0
- package/cjs/gates.cjs +1 -0
- package/cjs/index.cjs +840 -236
- package/cjs/utils.cjs +408 -12
- package/client.js +502 -244
- package/index.js +75 -64
- package/neus-logo.svg +3 -0
- package/package.json +9 -5
- package/types.d.ts +379 -82
- package/utils.js +443 -14
- package/widgets/README.md +64 -53
- package/widgets/index.js +9 -9
- package/widgets/verify-gate/dist/ProofBadge.js +51 -38
- package/widgets/verify-gate/dist/VerifyGate.js +284 -59
- package/widgets/verify-gate/index.js +13 -13
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 || !
|
|
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 || !
|
|
189
|
+
if (!data.primaryWalletAddress || !validateUniversalAddress(data.primaryWalletAddress, data.chain)) {
|
|
110
190
|
return { valid: false, error: 'primaryWalletAddress is required' };
|
|
111
191
|
}
|
|
112
|
-
if (!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.
|
|
119
|
-
return { valid: false, error: '
|
|
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 && !
|
|
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: '
|
|
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
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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 =
|
|
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?.
|
|
446
|
-
throw new ValidationError('wallet-link requires
|
|
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
|
-
|
|
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:
|
|
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}
|
|
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(
|
|
762
|
-
if (!
|
|
763
|
-
throw new ValidationError('
|
|
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/${
|
|
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}
|
|
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(
|
|
1047
|
+
* const privateData = await client.getPrivateStatus(proofId, window.ethereum);
|
|
784
1048
|
*/
|
|
785
|
-
async getPrivateStatus(
|
|
786
|
-
if (!
|
|
787
|
-
throw new ValidationError('
|
|
1049
|
+
async getPrivateStatus(proofId, wallet = null) {
|
|
1050
|
+
if (!proofId || typeof proofId !== 'string') {
|
|
1051
|
+
throw new ValidationError('proofId is required');
|
|
788
1052
|
}
|
|
789
1053
|
|
|
790
|
-
//
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
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
|
-
|
|
1076
|
+
return this._formatResponse(response);
|
|
796
1077
|
}
|
|
797
1078
|
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
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
|
-
//
|
|
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:
|
|
1097
|
+
...(signerIsEvm ? { chainId: this._getHubChainId() } : { chain })
|
|
831
1098
|
});
|
|
832
1099
|
|
|
833
1100
|
let signature;
|
|
834
1101
|
try {
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
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/${
|
|
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}
|
|
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(
|
|
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(
|
|
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 (!
|
|
929
|
-
throw new ValidationError('
|
|
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(
|
|
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
|
-
|
|
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(
|
|
986
|
-
if (!
|
|
987
|
-
throw new ValidationError('
|
|
1263
|
+
async revokeOwnProof(proofId, wallet) {
|
|
1264
|
+
if (!proofId || typeof proofId !== 'string') {
|
|
1265
|
+
throw new ValidationError('proofId is required');
|
|
988
1266
|
}
|
|
989
|
-
const
|
|
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
|
-
|
|
1280
|
+
// Keep wire payload key `qHash` for backwards compatibility.
|
|
1281
|
+
data: { action: 'revoke_proof', qHash: proofId },
|
|
997
1282
|
verifierIds: ['ownership-basic'],
|
|
998
|
-
chainId:
|
|
1283
|
+
...(signerIsEvm ? { chainId: this._getHubChainId() } : { chain })
|
|
999
1284
|
});
|
|
1000
1285
|
|
|
1001
1286
|
let signature;
|
|
1002
1287
|
try {
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
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/${
|
|
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({
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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
|
-
|
|
1394
|
+
const defaultWallet = this._getDefaultBrowserWallet();
|
|
1395
|
+
if (!defaultWallet) {
|
|
1135
1396
|
throw new ConfigurationError('No wallet provider available');
|
|
1136
1397
|
}
|
|
1137
|
-
wallet =
|
|
1398
|
+
wallet = defaultWallet;
|
|
1138
1399
|
}
|
|
1139
1400
|
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
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:
|
|
1422
|
+
data: { action: 'access_private_proofs_by_wallet', walletAddress: normalizedSigner },
|
|
1164
1423
|
verifierIds: ['ownership-basic'],
|
|
1165
|
-
chainId:
|
|
1424
|
+
...(signerIsEvm ? { chainId: this._getHubChainId() } : { chain })
|
|
1166
1425
|
});
|
|
1167
1426
|
|
|
1168
1427
|
let signature;
|
|
1169
1428
|
try {
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
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
|
|
1262
|
-
*
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
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/
|
|
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', '
|
|
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 (!
|
|
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
|
|
1636
|
-
|
|
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
|
-
|
|
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:
|
|
1910
|
+
statusUrl: finalProofId ? `${this.baseUrl}/api/v1/verification/status/${finalProofId}` : null
|
|
1653
1911
|
};
|
|
1654
1912
|
}
|
|
1655
1913
|
|